]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
Merge remote-tracking branch 'openstreetmap/pull/1344'
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /*
2  Leaflet 1.0.1, 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.1"
8 };
9
10 function expose() {
11         var oldL = window.L;
12
13         L.noConflict = function () {
14                 window.L = oldL;
15                 return this;
16         };
17
18         window.L = L;
19 }
20
21 // define Leaflet for Node module pattern loaders, including Browserify
22 if (typeof module === 'object' && typeof module.exports === 'object') {
23         module.exports = L;
24
25 // define Leaflet as an AMD module
26 } else if (typeof define === 'function' && define.amd) {
27         define(L);
28 }
29
30 // define Leaflet as a global L variable, saving the original L to restore later if needed
31 if (typeof window !== 'undefined') {
32         expose();
33 }
34
35
36
37 /*
38  * @namespace Util
39  *
40  * Various utility functions, used by Leaflet internally.
41  */
42
43 L.Util = {
44
45         // @function extend(dest: Object, src?: Object): Object
46         // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
47         extend: function (dest) {
48                 var i, j, len, src;
49
50                 for (j = 1, len = arguments.length; j < len; j++) {
51                         src = arguments[j];
52                         for (i in src) {
53                                 dest[i] = src[i];
54                         }
55                 }
56                 return dest;
57         },
58
59         // @function create(proto: Object, properties?: Object): Object
60         // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
61         create: Object.create || (function () {
62                 function F() {}
63                 return function (proto) {
64                         F.prototype = proto;
65                         return new F();
66                 };
67         })(),
68
69         // @function bind(fn: Function, …): Function
70         // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
71         // Has a `L.bind()` shortcut.
72         bind: function (fn, obj) {
73                 var slice = Array.prototype.slice;
74
75                 if (fn.bind) {
76                         return fn.bind.apply(fn, slice.call(arguments, 1));
77                 }
78
79                 var args = slice.call(arguments, 2);
80
81                 return function () {
82                         return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
83                 };
84         },
85
86         // @function stamp(obj: Object): Number
87         // Returns the unique ID of an object, assiging it one if it doesn't have it.
88         stamp: function (obj) {
89                 /*eslint-disable */
90                 obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
91                 return obj._leaflet_id;
92                 /*eslint-enable */
93         },
94
95         // @property lastId: Number
96         // Last unique ID used by [`stamp()`](#util-stamp)
97         lastId: 0,
98
99         // @function throttle(fn: Function, time: Number, context: Object): Function
100         // Returns a function which executes function `fn` with the given scope `context`
101         // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
102         // `fn` will be called no more than one time per given amount of `time`. The arguments
103         // received by the bound function will be any arguments passed when binding the
104         // function, followed by any arguments passed when invoking the bound function.
105         // Has an `L.bind` shortcut.
106         throttle: function (fn, time, context) {
107                 var lock, args, wrapperFn, later;
108
109                 later = function () {
110                         // reset lock and call if queued
111                         lock = false;
112                         if (args) {
113                                 wrapperFn.apply(context, args);
114                                 args = false;
115                         }
116                 };
117
118                 wrapperFn = function () {
119                         if (lock) {
120                                 // called too soon, queue to call later
121                                 args = arguments;
122
123                         } else {
124                                 // call and lock until later
125                                 fn.apply(context, arguments);
126                                 setTimeout(later, time);
127                                 lock = true;
128                         }
129                 };
130
131                 return wrapperFn;
132         },
133
134         // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
135         // Returns the number `num` modulo `range` in such a way so it lies within
136         // `range[0]` and `range[1]`. The returned value will be always smaller than
137         // `range[1]` unless `includeMax` is set to `true`.
138         wrapNum: function (x, range, includeMax) {
139                 var max = range[1],
140                     min = range[0],
141                     d = max - min;
142                 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
143         },
144
145         // @function falseFn(): Function
146         // Returns a function which always returns `false`.
147         falseFn: function () { return false; },
148
149         // @function formatNum(num: Number, digits?: Number): Number
150         // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
151         formatNum: function (num, digits) {
152                 var pow = Math.pow(10, digits || 5);
153                 return Math.round(num * pow) / pow;
154         },
155
156         // @function trim(str: String): String
157         // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
158         trim: function (str) {
159                 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
160         },
161
162         // @function splitWords(str: String): String[]
163         // Trims and splits the string on whitespace and returns the array of parts.
164         splitWords: function (str) {
165                 return L.Util.trim(str).split(/\s+/);
166         },
167
168         // @function setOptions(obj: Object, options: Object): Object
169         // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
170         setOptions: function (obj, options) {
171                 if (!obj.hasOwnProperty('options')) {
172                         obj.options = obj.options ? L.Util.create(obj.options) : {};
173                 }
174                 for (var i in options) {
175                         obj.options[i] = options[i];
176                 }
177                 return obj.options;
178         },
179
180         // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
181         // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
182         // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
183         // be appended at the end. If `uppercase` is `true`, the parameter names will
184         // be uppercased (e.g. `'?A=foo&B=bar'`)
185         getParamString: function (obj, existingUrl, uppercase) {
186                 var params = [];
187                 for (var i in obj) {
188                         params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
189                 }
190                 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
191         },
192
193         // @function template(str: String, data: Object): String
194         // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
195         // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
196         // `('Hello foo, bar')`. You can also specify functions instead of strings for
197         // data values — they will be evaluated passing `data` as an argument.
198         template: function (str, data) {
199                 return str.replace(L.Util.templateRe, function (str, key) {
200                         var value = data[key];
201
202                         if (value === undefined) {
203                                 throw new Error('No value provided for variable ' + str);
204
205                         } else if (typeof value === 'function') {
206                                 value = value(data);
207                         }
208                         return value;
209                 });
210         },
211
212         templateRe: /\{ *([\w_\-]+) *\}/g,
213
214         // @function isArray(obj): Boolean
215         // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
216         isArray: Array.isArray || function (obj) {
217                 return (Object.prototype.toString.call(obj) === '[object Array]');
218         },
219
220         // @function indexOf(array: Array, el: Object): Number
221         // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
222         indexOf: function (array, el) {
223                 for (var i = 0; i < array.length; i++) {
224                         if (array[i] === el) { return i; }
225                 }
226                 return -1;
227         },
228
229         // @property emptyImageUrl: String
230         // Data URI string containing a base64-encoded empty GIF image.
231         // Used as a hack to free memory from unused images on WebKit-powered
232         // mobile devices (by setting image `src` to this string).
233         emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
234 };
235
236 (function () {
237         // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
238
239         function getPrefixed(name) {
240                 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
241         }
242
243         var lastTime = 0;
244
245         // fallback for IE 7-8
246         function timeoutDefer(fn) {
247                 var time = +new Date(),
248                     timeToCall = Math.max(0, 16 - (time - lastTime));
249
250                 lastTime = time + timeToCall;
251                 return window.setTimeout(fn, timeToCall);
252         }
253
254         var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
255             cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
256                        getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
257
258
259         // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
260         // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
261         // `context` if given. When `immediate` is set, `fn` is called immediately if
262         // the browser doesn't have native support for
263         // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
264         // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
265         L.Util.requestAnimFrame = function (fn, context, immediate) {
266                 if (immediate && requestFn === timeoutDefer) {
267                         fn.call(context);
268                 } else {
269                         return requestFn.call(window, L.bind(fn, context));
270                 }
271         };
272
273         // @function cancelAnimFrame(id: Number): undefined
274         // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
275         L.Util.cancelAnimFrame = function (id) {
276                 if (id) {
277                         cancelFn.call(window, id);
278                 }
279         };
280 })();
281
282 // shortcuts for most used utility functions
283 L.extend = L.Util.extend;
284 L.bind = L.Util.bind;
285 L.stamp = L.Util.stamp;
286 L.setOptions = L.Util.setOptions;
287
288
289
290
291 // @class Class
292 // @aka L.Class
293
294 // @section
295 // @uninheritable
296
297 // Thanks to John Resig and Dean Edwards for inspiration!
298
299 L.Class = function () {};
300
301 L.Class.extend = function (props) {
302
303         // @function extend(props: Object): Function
304         // [Extends the current class](#class-inheritance) given the properties to be included.
305         // Returns a Javascript function that is a class constructor (to be called with `new`).
306         var NewClass = function () {
307
308                 // call the constructor
309                 if (this.initialize) {
310                         this.initialize.apply(this, arguments);
311                 }
312
313                 // call all constructor hooks
314                 this.callInitHooks();
315         };
316
317         var parentProto = NewClass.__super__ = this.prototype;
318
319         var proto = L.Util.create(parentProto);
320         proto.constructor = NewClass;
321
322         NewClass.prototype = proto;
323
324         // inherit parent's statics
325         for (var i in this) {
326                 if (this.hasOwnProperty(i) && i !== 'prototype') {
327                         NewClass[i] = this[i];
328                 }
329         }
330
331         // mix static properties into the class
332         if (props.statics) {
333                 L.extend(NewClass, props.statics);
334                 delete props.statics;
335         }
336
337         // mix includes into the prototype
338         if (props.includes) {
339                 L.Util.extend.apply(null, [proto].concat(props.includes));
340                 delete props.includes;
341         }
342
343         // merge options
344         if (proto.options) {
345                 props.options = L.Util.extend(L.Util.create(proto.options), props.options);
346         }
347
348         // mix given properties into the prototype
349         L.extend(proto, props);
350
351         proto._initHooks = [];
352
353         // add method for calling all hooks
354         proto.callInitHooks = function () {
355
356                 if (this._initHooksCalled) { return; }
357
358                 if (parentProto.callInitHooks) {
359                         parentProto.callInitHooks.call(this);
360                 }
361
362                 this._initHooksCalled = true;
363
364                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
365                         proto._initHooks[i].call(this);
366                 }
367         };
368
369         return NewClass;
370 };
371
372
373 // @function include(properties: Object): this
374 // [Includes a mixin](#class-includes) into the current class.
375 L.Class.include = function (props) {
376         L.extend(this.prototype, props);
377         return this;
378 };
379
380 // @function mergeOptions(options: Object): this
381 // [Merges `options`](#class-options) into the defaults of the class.
382 L.Class.mergeOptions = function (options) {
383         L.extend(this.prototype.options, options);
384         return this;
385 };
386
387 // @function addInitHook(fn: Function): this
388 // Adds a [constructor hook](#class-constructor-hooks) to the class.
389 L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
390         var args = Array.prototype.slice.call(arguments, 1);
391
392         var init = typeof fn === 'function' ? fn : function () {
393                 this[fn].apply(this, args);
394         };
395
396         this.prototype._initHooks = this.prototype._initHooks || [];
397         this.prototype._initHooks.push(init);
398         return this;
399 };
400
401
402
403 /*
404  * @class Evented
405  * @aka L.Evented
406  * @inherits Class
407  *
408  * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
409  *
410  * @example
411  *
412  * ```js
413  * map.on('click', function(e) {
414  *      alert(e.latlng);
415  * } );
416  * ```
417  *
418  * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
419  *
420  * ```js
421  * function onClick(e) { ... }
422  *
423  * map.on('click', onClick);
424  * map.off('click', onClick);
425  * ```
426  */
427
428
429 L.Evented = L.Class.extend({
430
431         /* @method on(type: String, fn: Function, context?: Object): this
432          * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
433          *
434          * @alternative
435          * @method on(eventMap: Object): this
436          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
437          */
438         on: function (types, fn, context) {
439
440                 // types can be a map of types/handlers
441                 if (typeof types === 'object') {
442                         for (var type in types) {
443                                 // we don't process space-separated events here for performance;
444                                 // it's a hot path since Layer uses the on(obj) syntax
445                                 this._on(type, types[type], fn);
446                         }
447
448                 } else {
449                         // types can be a string of space-separated words
450                         types = L.Util.splitWords(types);
451
452                         for (var i = 0, len = types.length; i < len; i++) {
453                                 this._on(types[i], fn, context);
454                         }
455                 }
456
457                 return this;
458         },
459
460         /* @method off(type: String, fn?: Function, context?: Object): this
461          * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
462          *
463          * @alternative
464          * @method off(eventMap: Object): this
465          * Removes a set of type/listener pairs.
466          *
467          * @alternative
468          * @method off: this
469          * Removes all listeners to all events on the object.
470          */
471         off: function (types, fn, context) {
472
473                 if (!types) {
474                         // clear all listeners if called without arguments
475                         delete this._events;
476
477                 } else if (typeof types === 'object') {
478                         for (var type in types) {
479                                 this._off(type, types[type], fn);
480                         }
481
482                 } else {
483                         types = L.Util.splitWords(types);
484
485                         for (var i = 0, len = types.length; i < len; i++) {
486                                 this._off(types[i], fn, context);
487                         }
488                 }
489
490                 return this;
491         },
492
493         // attach listener (without syntactic sugar now)
494         _on: function (type, fn, context) {
495                 this._events = this._events || {};
496
497                 /* get/init listeners for type */
498                 var typeListeners = this._events[type];
499                 if (!typeListeners) {
500                         typeListeners = [];
501                         this._events[type] = typeListeners;
502                 }
503
504                 if (context === this) {
505                         // Less memory footprint.
506                         context = undefined;
507                 }
508                 var newListener = {fn: fn, ctx: context},
509                     listeners = typeListeners;
510
511                 // check if fn already there
512                 for (var i = 0, len = listeners.length; i < len; i++) {
513                         if (listeners[i].fn === fn && listeners[i].ctx === context) {
514                                 return;
515                         }
516                 }
517
518                 listeners.push(newListener);
519                 typeListeners.count++;
520         },
521
522         _off: function (type, fn, context) {
523                 var listeners,
524                     i,
525                     len;
526
527                 if (!this._events) { return; }
528
529                 listeners = this._events[type];
530
531                 if (!listeners) {
532                         return;
533                 }
534
535                 if (!fn) {
536                         // Set all removed listeners to noop so they are not called if remove happens in fire
537                         for (i = 0, len = listeners.length; i < len; i++) {
538                                 listeners[i].fn = L.Util.falseFn;
539                         }
540                         // clear all listeners for a type if function isn't specified
541                         delete this._events[type];
542                         return;
543                 }
544
545                 if (context === this) {
546                         context = undefined;
547                 }
548
549                 if (listeners) {
550
551                         // find fn and remove it
552                         for (i = 0, len = listeners.length; i < len; i++) {
553                                 var l = listeners[i];
554                                 if (l.ctx !== context) { continue; }
555                                 if (l.fn === fn) {
556
557                                         // set the removed listener to noop so that's not called if remove happens in fire
558                                         l.fn = L.Util.falseFn;
559
560                                         if (this._firingCount) {
561                                                 /* copy array in case events are being fired */
562                                                 this._events[type] = listeners = listeners.slice();
563                                         }
564                                         listeners.splice(i, 1);
565
566                                         return;
567                                 }
568                         }
569                 }
570         },
571
572         // @method fire(type: String, data?: Object, propagate?: Boolean): this
573         // Fires an event of the specified type. You can optionally provide an data
574         // object — the first argument of the listener function will contain its
575         // properties. The event might can optionally be propagated to event parents.
576         fire: function (type, data, propagate) {
577                 if (!this.listens(type, propagate)) { return this; }
578
579                 var event = L.Util.extend({}, data, {type: type, target: this});
580
581                 if (this._events) {
582                         var listeners = this._events[type];
583
584                         if (listeners) {
585                                 this._firingCount = (this._firingCount + 1) || 1;
586                                 for (var i = 0, len = listeners.length; i < len; i++) {
587                                         var l = listeners[i];
588                                         l.fn.call(l.ctx || this, event);
589                                 }
590
591                                 this._firingCount--;
592                         }
593                 }
594
595                 if (propagate) {
596                         // propagate the event to parents (set with addEventParent)
597                         this._propagateEvent(event);
598                 }
599
600                 return this;
601         },
602
603         // @method listens(type: String): Boolean
604         // Returns `true` if a particular event type has any listeners attached to it.
605         listens: function (type, propagate) {
606                 var listeners = this._events && this._events[type];
607                 if (listeners && listeners.length) { return true; }
608
609                 if (propagate) {
610                         // also check parents for listeners if event propagates
611                         for (var id in this._eventParents) {
612                                 if (this._eventParents[id].listens(type, propagate)) { return true; }
613                         }
614                 }
615                 return false;
616         },
617
618         // @method once(…): this
619         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
620         once: function (types, fn, context) {
621
622                 if (typeof types === 'object') {
623                         for (var type in types) {
624                                 this.once(type, types[type], fn);
625                         }
626                         return this;
627                 }
628
629                 var handler = L.bind(function () {
630                         this
631                             .off(types, fn, context)
632                             .off(types, handler, context);
633                 }, this);
634
635                 // add a listener that's executed once and removed after that
636                 return this
637                     .on(types, fn, context)
638                     .on(types, handler, context);
639         },
640
641         // @method addEventParent(obj: Evented): this
642         // Adds an event parent - an `Evented` that will receive propagated events
643         addEventParent: function (obj) {
644                 this._eventParents = this._eventParents || {};
645                 this._eventParents[L.stamp(obj)] = obj;
646                 return this;
647         },
648
649         // @method removeEventParent(obj: Evented): this
650         // Removes an event parent, so it will stop receiving propagated events
651         removeEventParent: function (obj) {
652                 if (this._eventParents) {
653                         delete this._eventParents[L.stamp(obj)];
654                 }
655                 return this;
656         },
657
658         _propagateEvent: function (e) {
659                 for (var id in this._eventParents) {
660                         this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
661                 }
662         }
663 });
664
665 var proto = L.Evented.prototype;
666
667 // aliases; we should ditch those eventually
668
669 // @method addEventListener(…): this
670 // Alias to [`on(…)`](#evented-on)
671 proto.addEventListener = proto.on;
672
673 // @method removeEventListener(…): this
674 // Alias to [`off(…)`](#evented-off)
675
676 // @method clearAllEventListeners(…): this
677 // Alias to [`off()`](#evented-off)
678 proto.removeEventListener = proto.clearAllEventListeners = proto.off;
679
680 // @method addOneTimeEventListener(…): this
681 // Alias to [`once(…)`](#evented-once)
682 proto.addOneTimeEventListener = proto.once;
683
684 // @method fireEvent(…): this
685 // Alias to [`fire(…)`](#evented-fire)
686 proto.fireEvent = proto.fire;
687
688 // @method hasEventListeners(…): Boolean
689 // Alias to [`listens(…)`](#evented-listens)
690 proto.hasEventListeners = proto.listens;
691
692 L.Mixin = {Events: proto};
693
694
695
696 /*
697  * @namespace Browser
698  * @aka L.Browser
699  *
700  * A namespace with static properties for browser/feature detection used by Leaflet internally.
701  *
702  * @example
703  *
704  * ```js
705  * if (L.Browser.ielt9) {
706  *   alert('Upgrade your browser, dude!');
707  * }
708  * ```
709  */
710
711 (function () {
712
713         var ua = navigator.userAgent.toLowerCase(),
714             doc = document.documentElement,
715
716             ie = 'ActiveXObject' in window,
717
718             webkit    = ua.indexOf('webkit') !== -1,
719             phantomjs = ua.indexOf('phantom') !== -1,
720             android23 = ua.search('android [23]') !== -1,
721             chrome    = ua.indexOf('chrome') !== -1,
722             gecko     = ua.indexOf('gecko') !== -1  && !webkit && !window.opera && !ie,
723
724             win = navigator.platform.indexOf('Win') === 0,
725
726             mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
727             msPointer = !window.PointerEvent && window.MSPointerEvent,
728             pointer = window.PointerEvent || msPointer,
729
730             ie3d = ie && ('transition' in doc.style),
731             webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
732             gecko3d = 'MozPerspective' in doc.style,
733             opera12 = 'OTransition' in doc.style;
734
735
736         var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
737                         (window.DocumentTouch && document instanceof window.DocumentTouch));
738
739         L.Browser = {
740
741                 // @property ie: Boolean
742                 // `true` for all Internet Explorer versions (not Edge).
743                 ie: ie,
744
745                 // @property ielt9: Boolean
746                 // `true` for Internet Explorer versions less than 9.
747                 ielt9: ie && !document.addEventListener,
748
749                 // @property edge: Boolean
750                 // `true` for the Edge web browser.
751                 edge: 'msLaunchUri' in navigator && !('documentMode' in document),
752
753                 // @property webkit: Boolean
754                 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
755                 webkit: webkit,
756
757                 // @property gecko: Boolean
758                 // `true` for gecko-based browsers like Firefox.
759                 gecko: gecko,
760
761                 // @property android: Boolean
762                 // `true` for any browser running on an Android platform.
763                 android: ua.indexOf('android') !== -1,
764
765                 // @property android23: Boolean
766                 // `true` for browsers running on Android 2 or Android 3.
767                 android23: android23,
768
769                 // @property chrome: Boolean
770                 // `true` for the Chrome browser.
771                 chrome: chrome,
772
773                 // @property safari: Boolean
774                 // `true` for the Safari browser.
775                 safari: !chrome && ua.indexOf('safari') !== -1,
776
777
778                 // @property win: Boolean
779                 // `true` when the browser is running in a Windows platform
780                 win: win,
781
782
783                 // @property ie3d: Boolean
784                 // `true` for all Internet Explorer versions supporting CSS transforms.
785                 ie3d: ie3d,
786
787                 // @property webkit3d: Boolean
788                 // `true` for webkit-based browsers supporting CSS transforms.
789                 webkit3d: webkit3d,
790
791                 // @property gecko3d: Boolean
792                 // `true` for gecko-based browsers supporting CSS transforms.
793                 gecko3d: gecko3d,
794
795                 // @property opera12: Boolean
796                 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
797                 opera12: opera12,
798
799                 // @property any3d: Boolean
800                 // `true` for all browsers supporting CSS transforms.
801                 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
802
803
804                 // @property mobile: Boolean
805                 // `true` for all browsers running in a mobile device.
806                 mobile: mobile,
807
808                 // @property mobileWebkit: Boolean
809                 // `true` for all webkit-based browsers in a mobile device.
810                 mobileWebkit: mobile && webkit,
811
812                 // @property mobileWebkit3d: Boolean
813                 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
814                 mobileWebkit3d: mobile && webkit3d,
815
816                 // @property mobileOpera: Boolean
817                 // `true` for the Opera browser in a mobile device.
818                 mobileOpera: mobile && window.opera,
819
820                 // @property mobileGecko: Boolean
821                 // `true` for gecko-based browsers running in a mobile device.
822                 mobileGecko: mobile && gecko,
823
824
825                 // @property touch: Boolean
826                 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
827                 touch: !!touch,
828
829                 // @property msPointer: Boolean
830                 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
831                 msPointer: !!msPointer,
832
833                 // @property pointer: Boolean
834                 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
835                 pointer: !!pointer,
836
837
838                 // @property retina: Boolean
839                 // `true` for browsers on a high-resolution "retina" screen.
840                 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
841         };
842
843 }());
844
845
846
847 /*
848  * @class Point
849  * @aka L.Point
850  *
851  * Represents a point with `x` and `y` coordinates in pixels.
852  *
853  * @example
854  *
855  * ```js
856  * var point = L.point(200, 300);
857  * ```
858  *
859  * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
860  *
861  * ```js
862  * map.panBy([200, 300]);
863  * map.panBy(L.point(200, 300));
864  * ```
865  */
866
867 L.Point = function (x, y, round) {
868         this.x = (round ? Math.round(x) : x);
869         this.y = (round ? Math.round(y) : y);
870 };
871
872 L.Point.prototype = {
873
874         // @method clone(): Point
875         // Returns a copy of the current point.
876         clone: function () {
877                 return new L.Point(this.x, this.y);
878         },
879
880         // @method add(otherPoint: Point): Point
881         // Returns the result of addition of the current and the given points.
882         add: function (point) {
883                 // non-destructive, returns a new point
884                 return this.clone()._add(L.point(point));
885         },
886
887         _add: function (point) {
888                 // destructive, used directly for performance in situations where it's safe to modify existing point
889                 this.x += point.x;
890                 this.y += point.y;
891                 return this;
892         },
893
894         // @method subtract(otherPoint: Point): Point
895         // Returns the result of subtraction of the given point from the current.
896         subtract: function (point) {
897                 return this.clone()._subtract(L.point(point));
898         },
899
900         _subtract: function (point) {
901                 this.x -= point.x;
902                 this.y -= point.y;
903                 return this;
904         },
905
906         // @method divideBy(num: Number): Point
907         // Returns the result of division of the current point by the given number.
908         divideBy: function (num) {
909                 return this.clone()._divideBy(num);
910         },
911
912         _divideBy: function (num) {
913                 this.x /= num;
914                 this.y /= num;
915                 return this;
916         },
917
918         // @method multiplyBy(num: Number): Point
919         // Returns the result of multiplication of the current point by the given number.
920         multiplyBy: function (num) {
921                 return this.clone()._multiplyBy(num);
922         },
923
924         _multiplyBy: function (num) {
925                 this.x *= num;
926                 this.y *= num;
927                 return this;
928         },
929
930         // @method scaleBy(scale: Point): Point
931         // Multiply each coordinate of the current point by each coordinate of
932         // `scale`. In linear algebra terms, multiply the point by the
933         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
934         // defined by `scale`.
935         scaleBy: function (point) {
936                 return new L.Point(this.x * point.x, this.y * point.y);
937         },
938
939         // @method unscaleBy(scale: Point): Point
940         // Inverse of `scaleBy`. Divide each coordinate of the current point by
941         // each coordinate of `scale`.
942         unscaleBy: function (point) {
943                 return new L.Point(this.x / point.x, this.y / point.y);
944         },
945
946         // @method round(): Point
947         // Returns a copy of the current point with rounded coordinates.
948         round: function () {
949                 return this.clone()._round();
950         },
951
952         _round: function () {
953                 this.x = Math.round(this.x);
954                 this.y = Math.round(this.y);
955                 return this;
956         },
957
958         // @method floor(): Point
959         // Returns a copy of the current point with floored coordinates (rounded down).
960         floor: function () {
961                 return this.clone()._floor();
962         },
963
964         _floor: function () {
965                 this.x = Math.floor(this.x);
966                 this.y = Math.floor(this.y);
967                 return this;
968         },
969
970         // @method ceil(): Point
971         // Returns a copy of the current point with ceiled coordinates (rounded up).
972         ceil: function () {
973                 return this.clone()._ceil();
974         },
975
976         _ceil: function () {
977                 this.x = Math.ceil(this.x);
978                 this.y = Math.ceil(this.y);
979                 return this;
980         },
981
982         // @method distanceTo(otherPoint: Point): Number
983         // Returns the cartesian distance between the current and the given points.
984         distanceTo: function (point) {
985                 point = L.point(point);
986
987                 var x = point.x - this.x,
988                     y = point.y - this.y;
989
990                 return Math.sqrt(x * x + y * y);
991         },
992
993         // @method equals(otherPoint: Point): Boolean
994         // Returns `true` if the given point has the same coordinates.
995         equals: function (point) {
996                 point = L.point(point);
997
998                 return point.x === this.x &&
999                        point.y === this.y;
1000         },
1001
1002         // @method contains(otherPoint: Point): Boolean
1003         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1004         contains: function (point) {
1005                 point = L.point(point);
1006
1007                 return Math.abs(point.x) <= Math.abs(this.x) &&
1008                        Math.abs(point.y) <= Math.abs(this.y);
1009         },
1010
1011         // @method toString(): String
1012         // Returns a string representation of the point for debugging purposes.
1013         toString: function () {
1014                 return 'Point(' +
1015                         L.Util.formatNum(this.x) + ', ' +
1016                         L.Util.formatNum(this.y) + ')';
1017         }
1018 };
1019
1020 // @factory L.point(x: Number, y: Number, round?: Boolean)
1021 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1022
1023 // @alternative
1024 // @factory L.point(coords: Number[])
1025 // Expects an array of the form `[x, y]` instead.
1026
1027 // @alternative
1028 // @factory L.point(coords: Object)
1029 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1030 L.point = function (x, y, round) {
1031         if (x instanceof L.Point) {
1032                 return x;
1033         }
1034         if (L.Util.isArray(x)) {
1035                 return new L.Point(x[0], x[1]);
1036         }
1037         if (x === undefined || x === null) {
1038                 return x;
1039         }
1040         if (typeof x === 'object' && 'x' in x && 'y' in x) {
1041                 return new L.Point(x.x, x.y);
1042         }
1043         return new L.Point(x, y, round);
1044 };
1045
1046
1047
1048 /*
1049  * @class Bounds
1050  * @aka L.Bounds
1051  *
1052  * Represents a rectangular area in pixel coordinates.
1053  *
1054  * @example
1055  *
1056  * ```js
1057  * var p1 = L.point(10, 10),
1058  * p2 = L.point(40, 60),
1059  * bounds = L.bounds(p1, p2);
1060  * ```
1061  *
1062  * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1063  *
1064  * ```js
1065  * otherBounds.intersects([[10, 10], [40, 60]]);
1066  * ```
1067  */
1068
1069 L.Bounds = function (a, b) {
1070         if (!a) { return; }
1071
1072         var points = b ? [a, b] : a;
1073
1074         for (var i = 0, len = points.length; i < len; i++) {
1075                 this.extend(points[i]);
1076         }
1077 };
1078
1079 L.Bounds.prototype = {
1080         // @method extend(point: Point): this
1081         // Extends the bounds to contain the given point.
1082         extend: function (point) { // (Point)
1083                 point = L.point(point);
1084
1085                 // @property min: Point
1086                 // The top left corner of the rectangle.
1087                 // @property max: Point
1088                 // The bottom right corner of the rectangle.
1089                 if (!this.min && !this.max) {
1090                         this.min = point.clone();
1091                         this.max = point.clone();
1092                 } else {
1093                         this.min.x = Math.min(point.x, this.min.x);
1094                         this.max.x = Math.max(point.x, this.max.x);
1095                         this.min.y = Math.min(point.y, this.min.y);
1096                         this.max.y = Math.max(point.y, this.max.y);
1097                 }
1098                 return this;
1099         },
1100
1101         // @method getCenter(round?: Boolean): Point
1102         // Returns the center point of the bounds.
1103         getCenter: function (round) {
1104                 return new L.Point(
1105                         (this.min.x + this.max.x) / 2,
1106                         (this.min.y + this.max.y) / 2, round);
1107         },
1108
1109         // @method getBottomLeft(): Point
1110         // Returns the bottom-left point of the bounds.
1111         getBottomLeft: function () {
1112                 return new L.Point(this.min.x, this.max.y);
1113         },
1114
1115         // @method getTopRight(): Point
1116         // Returns the top-right point of the bounds.
1117         getTopRight: function () { // -> Point
1118                 return new L.Point(this.max.x, this.min.y);
1119         },
1120
1121         // @method getSize(): Point
1122         // Returns the size of the given bounds
1123         getSize: function () {
1124                 return this.max.subtract(this.min);
1125         },
1126
1127         // @method contains(otherBounds: Bounds): Boolean
1128         // Returns `true` if the rectangle contains the given one.
1129         // @alternative
1130         // @method contains(point: Point): Boolean
1131         // Returns `true` if the rectangle contains the given point.
1132         contains: function (obj) {
1133                 var min, max;
1134
1135                 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1136                         obj = L.point(obj);
1137                 } else {
1138                         obj = L.bounds(obj);
1139                 }
1140
1141                 if (obj instanceof L.Bounds) {
1142                         min = obj.min;
1143                         max = obj.max;
1144                 } else {
1145                         min = max = obj;
1146                 }
1147
1148                 return (min.x >= this.min.x) &&
1149                        (max.x <= this.max.x) &&
1150                        (min.y >= this.min.y) &&
1151                        (max.y <= this.max.y);
1152         },
1153
1154         // @method intersects(otherBounds: Bounds): Boolean
1155         // Returns `true` if the rectangle intersects the given bounds. Two bounds
1156         // intersect if they have at least one point in common.
1157         intersects: function (bounds) { // (Bounds) -> Boolean
1158                 bounds = L.bounds(bounds);
1159
1160                 var min = this.min,
1161                     max = this.max,
1162                     min2 = bounds.min,
1163                     max2 = bounds.max,
1164                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1165                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1166
1167                 return xIntersects && yIntersects;
1168         },
1169
1170         // @method overlaps(otherBounds: Bounds): Boolean
1171         // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1172         // overlap if their intersection is an area.
1173         overlaps: function (bounds) { // (Bounds) -> Boolean
1174                 bounds = L.bounds(bounds);
1175
1176                 var min = this.min,
1177                     max = this.max,
1178                     min2 = bounds.min,
1179                     max2 = bounds.max,
1180                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1181                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1182
1183                 return xOverlaps && yOverlaps;
1184         },
1185
1186         isValid: function () {
1187                 return !!(this.min && this.max);
1188         }
1189 };
1190
1191
1192 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1193 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1194 // @alternative
1195 // @factory L.bounds(points: Point[])
1196 // Creates a Bounds object from the points it contains
1197 L.bounds = function (a, b) {
1198         if (!a || a instanceof L.Bounds) {
1199                 return a;
1200         }
1201         return new L.Bounds(a, b);
1202 };
1203
1204
1205
1206 /*
1207  * @class Transformation
1208  * @aka L.Transformation
1209  *
1210  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1211  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1212  * the reverse. Used by Leaflet in its projections code.
1213  *
1214  * @example
1215  *
1216  * ```js
1217  * var transformation = new L.Transformation(2, 5, -1, 10),
1218  *      p = L.point(1, 2),
1219  *      p2 = transformation.transform(p), //  L.point(7, 8)
1220  *      p3 = transformation.untransform(p2); //  L.point(1, 2)
1221  * ```
1222  */
1223
1224
1225 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1226 // Creates a `Transformation` object with the given coefficients.
1227 L.Transformation = function (a, b, c, d) {
1228         this._a = a;
1229         this._b = b;
1230         this._c = c;
1231         this._d = d;
1232 };
1233
1234 L.Transformation.prototype = {
1235         // @method transform(point: Point, scale?: Number): Point
1236         // Returns a transformed point, optionally multiplied by the given scale.
1237         // Only accepts real `L.Point` instances, not arrays.
1238         transform: function (point, scale) { // (Point, Number) -> Point
1239                 return this._transform(point.clone(), scale);
1240         },
1241
1242         // destructive transform (faster)
1243         _transform: function (point, scale) {
1244                 scale = scale || 1;
1245                 point.x = scale * (this._a * point.x + this._b);
1246                 point.y = scale * (this._c * point.y + this._d);
1247                 return point;
1248         },
1249
1250         // @method untransform(point: Point, scale?: Number): Point
1251         // Returns the reverse transformation of the given point, optionally divided
1252         // by the given scale. Only accepts real `L.Point` instances, not arrays.
1253         untransform: function (point, scale) {
1254                 scale = scale || 1;
1255                 return new L.Point(
1256                         (point.x / scale - this._b) / this._a,
1257                         (point.y / scale - this._d) / this._c);
1258         }
1259 };
1260
1261
1262
1263 /*
1264  * @namespace DomUtil
1265  *
1266  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1267  * tree, used by Leaflet internally.
1268  *
1269  * Most functions expecting or returning a `HTMLElement` also work for
1270  * SVG elements. The only difference is that classes refer to CSS classes
1271  * in HTML and SVG classes in SVG.
1272  */
1273
1274 L.DomUtil = {
1275
1276         // @function get(id: String|HTMLElement): HTMLElement
1277         // Returns an element given its DOM id, or returns the element itself
1278         // if it was passed directly.
1279         get: function (id) {
1280                 return typeof id === 'string' ? document.getElementById(id) : id;
1281         },
1282
1283         // @function getStyle(el: HTMLElement, styleAttrib: String): String
1284         // Returns the value for a certain style attribute on an element,
1285         // including computed values or values set through CSS.
1286         getStyle: function (el, style) {
1287
1288                 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1289
1290                 if ((!value || value === 'auto') && document.defaultView) {
1291                         var css = document.defaultView.getComputedStyle(el, null);
1292                         value = css ? css[style] : null;
1293                 }
1294
1295                 return value === 'auto' ? null : value;
1296         },
1297
1298         // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1299         // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1300         create: function (tagName, className, container) {
1301
1302                 var el = document.createElement(tagName);
1303                 el.className = className || '';
1304
1305                 if (container) {
1306                         container.appendChild(el);
1307                 }
1308
1309                 return el;
1310         },
1311
1312         // @function remove(el: HTMLElement)
1313         // Removes `el` from its parent element
1314         remove: function (el) {
1315                 var parent = el.parentNode;
1316                 if (parent) {
1317                         parent.removeChild(el);
1318                 }
1319         },
1320
1321         // @function empty(el: HTMLElement)
1322         // Removes all of `el`'s children elements from `el`
1323         empty: function (el) {
1324                 while (el.firstChild) {
1325                         el.removeChild(el.firstChild);
1326                 }
1327         },
1328
1329         // @function toFront(el: HTMLElement)
1330         // Makes `el` the last children of its parent, so it renders in front of the other children.
1331         toFront: function (el) {
1332                 el.parentNode.appendChild(el);
1333         },
1334
1335         // @function toBack(el: HTMLElement)
1336         // Makes `el` the first children of its parent, so it renders back from the other children.
1337         toBack: function (el) {
1338                 var parent = el.parentNode;
1339                 parent.insertBefore(el, parent.firstChild);
1340         },
1341
1342         // @function hasClass(el: HTMLElement, name: String): Boolean
1343         // Returns `true` if the element's class attribute contains `name`.
1344         hasClass: function (el, name) {
1345                 if (el.classList !== undefined) {
1346                         return el.classList.contains(name);
1347                 }
1348                 var className = L.DomUtil.getClass(el);
1349                 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1350         },
1351
1352         // @function addClass(el: HTMLElement, name: String)
1353         // Adds `name` to the element's class attribute.
1354         addClass: function (el, name) {
1355                 if (el.classList !== undefined) {
1356                         var classes = L.Util.splitWords(name);
1357                         for (var i = 0, len = classes.length; i < len; i++) {
1358                                 el.classList.add(classes[i]);
1359                         }
1360                 } else if (!L.DomUtil.hasClass(el, name)) {
1361                         var className = L.DomUtil.getClass(el);
1362                         L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1363                 }
1364         },
1365
1366         // @function removeClass(el: HTMLElement, name: String)
1367         // Removes `name` from the element's class attribute.
1368         removeClass: function (el, name) {
1369                 if (el.classList !== undefined) {
1370                         el.classList.remove(name);
1371                 } else {
1372                         L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1373                 }
1374         },
1375
1376         // @function setClass(el: HTMLElement, name: String)
1377         // Sets the element's class.
1378         setClass: function (el, name) {
1379                 if (el.className.baseVal === undefined) {
1380                         el.className = name;
1381                 } else {
1382                         // in case of SVG element
1383                         el.className.baseVal = name;
1384                 }
1385         },
1386
1387         // @function getClass(el: HTMLElement): String
1388         // Returns the element's class.
1389         getClass: function (el) {
1390                 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1391         },
1392
1393         // @function setOpacity(el: HTMLElement, opacity: Number)
1394         // Set the opacity of an element (including old IE support).
1395         // `opacity` must be a number from `0` to `1`.
1396         setOpacity: function (el, value) {
1397
1398                 if ('opacity' in el.style) {
1399                         el.style.opacity = value;
1400
1401                 } else if ('filter' in el.style) {
1402                         L.DomUtil._setOpacityIE(el, value);
1403                 }
1404         },
1405
1406         _setOpacityIE: function (el, value) {
1407                 var filter = false,
1408                     filterName = 'DXImageTransform.Microsoft.Alpha';
1409
1410                 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1411                 try {
1412                         filter = el.filters.item(filterName);
1413                 } catch (e) {
1414                         // don't set opacity to 1 if we haven't already set an opacity,
1415                         // it isn't needed and breaks transparent pngs.
1416                         if (value === 1) { return; }
1417                 }
1418
1419                 value = Math.round(value * 100);
1420
1421                 if (filter) {
1422                         filter.Enabled = (value !== 100);
1423                         filter.Opacity = value;
1424                 } else {
1425                         el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1426                 }
1427         },
1428
1429         // @function testProp(props: String[]): String|false
1430         // Goes through the array of style names and returns the first name
1431         // that is a valid style name for an element. If no such name is found,
1432         // it returns false. Useful for vendor-prefixed styles like `transform`.
1433         testProp: function (props) {
1434
1435                 var style = document.documentElement.style;
1436
1437                 for (var i = 0; i < props.length; i++) {
1438                         if (props[i] in style) {
1439                                 return props[i];
1440                         }
1441                 }
1442                 return false;
1443         },
1444
1445         // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1446         // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1447         // and optionally scaled by `scale`. Does not have an effect if the
1448         // browser doesn't support 3D CSS transforms.
1449         setTransform: function (el, offset, scale) {
1450                 var pos = offset || new L.Point(0, 0);
1451
1452                 el.style[L.DomUtil.TRANSFORM] =
1453                         (L.Browser.ie3d ?
1454                                 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1455                                 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1456                         (scale ? ' scale(' + scale + ')' : '');
1457         },
1458
1459         // @function setPosition(el: HTMLElement, position: Point)
1460         // Sets the position of `el` to coordinates specified by `position`,
1461         // using CSS translate or top/left positioning depending on the browser
1462         // (used by Leaflet internally to position its layers).
1463         setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1464
1465                 /*eslint-disable */
1466                 el._leaflet_pos = point;
1467                 /*eslint-enable */
1468
1469                 if (L.Browser.any3d) {
1470                         L.DomUtil.setTransform(el, point);
1471                 } else {
1472                         el.style.left = point.x + 'px';
1473                         el.style.top = point.y + 'px';
1474                 }
1475         },
1476
1477         // @function getPosition(el: HTMLElement): Point
1478         // Returns the coordinates of an element previously positioned with setPosition.
1479         getPosition: function (el) {
1480                 // this method is only used for elements previously positioned using setPosition,
1481                 // so it's safe to cache the position for performance
1482
1483                 return el._leaflet_pos || new L.Point(0, 0);
1484         }
1485 };
1486
1487
1488 (function () {
1489         // prefix style property names
1490
1491         // @property TRANSFORM: String
1492         // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1493         L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1494                         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1495
1496
1497         // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1498         // the same for the transitionend event, in particular the Android 4.1 stock browser
1499
1500         // @property TRANSITION: String
1501         // Vendor-prefixed transform style name.
1502         var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1503                         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1504
1505         L.DomUtil.TRANSITION_END =
1506                         transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1507
1508         // @function disableTextSelection()
1509         // Prevents the user from generating `selectstart` DOM events, usually generated
1510         // when the user drags the mouse through a page with text. Used internally
1511         // by Leaflet to override the behaviour of any click-and-drag interaction on
1512         // the map. Affects drag interactions on the whole document.
1513
1514         // @function enableTextSelection()
1515         // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1516         if ('onselectstart' in document) {
1517                 L.DomUtil.disableTextSelection = function () {
1518                         L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1519                 };
1520                 L.DomUtil.enableTextSelection = function () {
1521                         L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1522                 };
1523
1524         } else {
1525                 var userSelectProperty = L.DomUtil.testProp(
1526                         ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1527
1528                 L.DomUtil.disableTextSelection = function () {
1529                         if (userSelectProperty) {
1530                                 var style = document.documentElement.style;
1531                                 this._userSelect = style[userSelectProperty];
1532                                 style[userSelectProperty] = 'none';
1533                         }
1534                 };
1535                 L.DomUtil.enableTextSelection = function () {
1536                         if (userSelectProperty) {
1537                                 document.documentElement.style[userSelectProperty] = this._userSelect;
1538                                 delete this._userSelect;
1539                         }
1540                 };
1541         }
1542
1543         // @function disableImageDrag()
1544         // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1545         // for `dragstart` DOM events, usually generated when the user drags an image.
1546         L.DomUtil.disableImageDrag = function () {
1547                 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1548         };
1549
1550         // @function enableImageDrag()
1551         // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1552         L.DomUtil.enableImageDrag = function () {
1553                 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1554         };
1555
1556         // @function preventOutline(el: HTMLElement)
1557         // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1558         // of the element `el` invisible. Used internally by Leaflet to prevent
1559         // focusable elements from displaying an outline when the user performs a
1560         // drag interaction on them.
1561         L.DomUtil.preventOutline = function (element) {
1562                 while (element.tabIndex === -1) {
1563                         element = element.parentNode;
1564                 }
1565                 if (!element || !element.style) { return; }
1566                 L.DomUtil.restoreOutline();
1567                 this._outlineElement = element;
1568                 this._outlineStyle = element.style.outline;
1569                 element.style.outline = 'none';
1570                 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1571         };
1572
1573         // @function restoreOutline()
1574         // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1575         L.DomUtil.restoreOutline = function () {
1576                 if (!this._outlineElement) { return; }
1577                 this._outlineElement.style.outline = this._outlineStyle;
1578                 delete this._outlineElement;
1579                 delete this._outlineStyle;
1580                 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1581         };
1582 })();
1583
1584
1585
1586 /* @class LatLng
1587  * @aka L.LatLng
1588  *
1589  * Represents a geographical point with a certain latitude and longitude.
1590  *
1591  * @example
1592  *
1593  * ```
1594  * var latlng = L.latLng(50.5, 30.5);
1595  * ```
1596  *
1597  * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
1598  *
1599  * ```
1600  * map.panTo([50, 30]);
1601  * map.panTo({lon: 30, lat: 50});
1602  * map.panTo({lat: 50, lng: 30});
1603  * map.panTo(L.latLng(50, 30));
1604  * ```
1605  */
1606
1607 L.LatLng = function (lat, lng, alt) {
1608         if (isNaN(lat) || isNaN(lng)) {
1609                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1610         }
1611
1612         // @property lat: Number
1613         // Latitude in degrees
1614         this.lat = +lat;
1615
1616         // @property lng: Number
1617         // Longitude in degrees
1618         this.lng = +lng;
1619
1620         // @property alt: Number
1621         // Altitude in meters (optional)
1622         if (alt !== undefined) {
1623                 this.alt = +alt;
1624         }
1625 };
1626
1627 L.LatLng.prototype = {
1628         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1629         // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
1630         equals: function (obj, maxMargin) {
1631                 if (!obj) { return false; }
1632
1633                 obj = L.latLng(obj);
1634
1635                 var margin = Math.max(
1636                         Math.abs(this.lat - obj.lat),
1637                         Math.abs(this.lng - obj.lng));
1638
1639                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1640         },
1641
1642         // @method toString(): String
1643         // Returns a string representation of the point (for debugging purposes).
1644         toString: function (precision) {
1645                 return 'LatLng(' +
1646                         L.Util.formatNum(this.lat, precision) + ', ' +
1647                         L.Util.formatNum(this.lng, precision) + ')';
1648         },
1649
1650         // @method distanceTo(otherLatLng: LatLng): Number
1651         // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1652         distanceTo: function (other) {
1653                 return L.CRS.Earth.distance(this, L.latLng(other));
1654         },
1655
1656         // @method wrap(): LatLng
1657         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1658         wrap: function () {
1659                 return L.CRS.Earth.wrapLatLng(this);
1660         },
1661
1662         // @method toBounds(sizeInMeters: Number): LatLngBounds
1663         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters` meters apart from the `LatLng`.
1664         toBounds: function (sizeInMeters) {
1665                 var latAccuracy = 180 * sizeInMeters / 40075017,
1666                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1667
1668                 return L.latLngBounds(
1669                         [this.lat - latAccuracy, this.lng - lngAccuracy],
1670                         [this.lat + latAccuracy, this.lng + lngAccuracy]);
1671         },
1672
1673         clone: function () {
1674                 return new L.LatLng(this.lat, this.lng, this.alt);
1675         }
1676 };
1677
1678
1679
1680 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1681 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1682
1683 // @alternative
1684 // @factory L.latLng(coords: Array): LatLng
1685 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1686
1687 // @alternative
1688 // @factory L.latLng(coords: Object): LatLng
1689 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1690
1691 L.latLng = function (a, b, c) {
1692         if (a instanceof L.LatLng) {
1693                 return a;
1694         }
1695         if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1696                 if (a.length === 3) {
1697                         return new L.LatLng(a[0], a[1], a[2]);
1698                 }
1699                 if (a.length === 2) {
1700                         return new L.LatLng(a[0], a[1]);
1701                 }
1702                 return null;
1703         }
1704         if (a === undefined || a === null) {
1705                 return a;
1706         }
1707         if (typeof a === 'object' && 'lat' in a) {
1708                 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1709         }
1710         if (b === undefined) {
1711                 return null;
1712         }
1713         return new L.LatLng(a, b, c);
1714 };
1715
1716
1717
1718 /*
1719  * @class LatLngBounds
1720  * @aka L.LatLngBounds
1721  *
1722  * Represents a rectangular geographical area on a map.
1723  *
1724  * @example
1725  *
1726  * ```js
1727  * var southWest = L.latLng(40.712, -74.227),
1728  * northEast = L.latLng(40.774, -74.125),
1729  * bounds = L.latLngBounds(southWest, northEast);
1730  * ```
1731  *
1732  * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1733  *
1734  * ```js
1735  * map.fitBounds([
1736  *      [40.712, -74.227],
1737  *      [40.774, -74.125]
1738  * ]);
1739  * ```
1740  */
1741
1742 L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
1743         if (!southWest) { return; }
1744
1745         var latlngs = northEast ? [southWest, northEast] : southWest;
1746
1747         for (var i = 0, len = latlngs.length; i < len; i++) {
1748                 this.extend(latlngs[i]);
1749         }
1750 };
1751
1752 L.LatLngBounds.prototype = {
1753
1754         // @method extend(latlng: LatLng): this
1755         // Extend the bounds to contain the given point
1756
1757         // @alternative
1758         // @method extend(otherBounds: LatLngBounds): this
1759         // Extend the bounds to contain the given bounds
1760         extend: function (obj) {
1761                 var sw = this._southWest,
1762                     ne = this._northEast,
1763                     sw2, ne2;
1764
1765                 if (obj instanceof L.LatLng) {
1766                         sw2 = obj;
1767                         ne2 = obj;
1768
1769                 } else if (obj instanceof L.LatLngBounds) {
1770                         sw2 = obj._southWest;
1771                         ne2 = obj._northEast;
1772
1773                         if (!sw2 || !ne2) { return this; }
1774
1775                 } else {
1776                         return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1777                 }
1778
1779                 if (!sw && !ne) {
1780                         this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1781                         this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1782                 } else {
1783                         sw.lat = Math.min(sw2.lat, sw.lat);
1784                         sw.lng = Math.min(sw2.lng, sw.lng);
1785                         ne.lat = Math.max(ne2.lat, ne.lat);
1786                         ne.lng = Math.max(ne2.lng, ne.lng);
1787                 }
1788
1789                 return this;
1790         },
1791
1792         // @method pad(bufferRatio: Number): LatLngBounds
1793         // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1794         pad: function (bufferRatio) {
1795                 var sw = this._southWest,
1796                     ne = this._northEast,
1797                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1798                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1799
1800                 return new L.LatLngBounds(
1801                         new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1802                         new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1803         },
1804
1805         // @method getCenter(): LatLng
1806         // Returns the center point of the bounds.
1807         getCenter: function () {
1808                 return new L.LatLng(
1809                         (this._southWest.lat + this._northEast.lat) / 2,
1810                         (this._southWest.lng + this._northEast.lng) / 2);
1811         },
1812
1813         // @method getSouthWest(): LatLng
1814         // Returns the south-west point of the bounds.
1815         getSouthWest: function () {
1816                 return this._southWest;
1817         },
1818
1819         // @method getNorthEast(): LatLng
1820         // Returns the north-east point of the bounds.
1821         getNorthEast: function () {
1822                 return this._northEast;
1823         },
1824
1825         // @method getNorthWest(): LatLng
1826         // Returns the north-west point of the bounds.
1827         getNorthWest: function () {
1828                 return new L.LatLng(this.getNorth(), this.getWest());
1829         },
1830
1831         // @method getSouthEast(): LatLng
1832         // Returns the south-east point of the bounds.
1833         getSouthEast: function () {
1834                 return new L.LatLng(this.getSouth(), this.getEast());
1835         },
1836
1837         // @method getWest(): Number
1838         // Returns the west longitude of the bounds
1839         getWest: function () {
1840                 return this._southWest.lng;
1841         },
1842
1843         // @method getSouth(): Number
1844         // Returns the south latitude of the bounds
1845         getSouth: function () {
1846                 return this._southWest.lat;
1847         },
1848
1849         // @method getEast(): Number
1850         // Returns the east longitude of the bounds
1851         getEast: function () {
1852                 return this._northEast.lng;
1853         },
1854
1855         // @method getNorth(): Number
1856         // Returns the north latitude of the bounds
1857         getNorth: function () {
1858                 return this._northEast.lat;
1859         },
1860
1861         // @method contains(otherBounds: LatLngBounds): Boolean
1862         // Returns `true` if the rectangle contains the given one.
1863
1864         // @alternative
1865         // @method contains (latlng: LatLng): Boolean
1866         // Returns `true` if the rectangle contains the given point.
1867         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1868                 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1869                         obj = L.latLng(obj);
1870                 } else {
1871                         obj = L.latLngBounds(obj);
1872                 }
1873
1874                 var sw = this._southWest,
1875                     ne = this._northEast,
1876                     sw2, ne2;
1877
1878                 if (obj instanceof L.LatLngBounds) {
1879                         sw2 = obj.getSouthWest();
1880                         ne2 = obj.getNorthEast();
1881                 } else {
1882                         sw2 = ne2 = obj;
1883                 }
1884
1885                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1886                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1887         },
1888
1889         // @method intersects(otherBounds: LatLngBounds): Boolean
1890         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1891         intersects: function (bounds) {
1892                 bounds = L.latLngBounds(bounds);
1893
1894                 var sw = this._southWest,
1895                     ne = this._northEast,
1896                     sw2 = bounds.getSouthWest(),
1897                     ne2 = bounds.getNorthEast(),
1898
1899                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1900                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1901
1902                 return latIntersects && lngIntersects;
1903         },
1904
1905         // @method overlaps(otherBounds: Bounds): Boolean
1906         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1907         overlaps: function (bounds) {
1908                 bounds = L.latLngBounds(bounds);
1909
1910                 var sw = this._southWest,
1911                     ne = this._northEast,
1912                     sw2 = bounds.getSouthWest(),
1913                     ne2 = bounds.getNorthEast(),
1914
1915                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1916                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1917
1918                 return latOverlaps && lngOverlaps;
1919         },
1920
1921         // @method toBBoxString(): String
1922         // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
1923         toBBoxString: function () {
1924                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1925         },
1926
1927         // @method equals(otherBounds: LatLngBounds): Boolean
1928         // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1929         equals: function (bounds) {
1930                 if (!bounds) { return false; }
1931
1932                 bounds = L.latLngBounds(bounds);
1933
1934                 return this._southWest.equals(bounds.getSouthWest()) &&
1935                        this._northEast.equals(bounds.getNorthEast());
1936         },
1937
1938         // @method isValid(): Boolean
1939         // Returns `true` if the bounds are properly initialized.
1940         isValid: function () {
1941                 return !!(this._southWest && this._northEast);
1942         }
1943 };
1944
1945 // TODO International date line?
1946
1947 // @factory L.latLngBounds(southWest: LatLng, northEast: LatLng)
1948 // Creates a `LatLngBounds` object by defining south-west and north-east corners of the rectangle.
1949
1950 // @alternative
1951 // @factory L.latLngBounds(latlngs: LatLng[])
1952 // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
1953 L.latLngBounds = function (a, b) {
1954         if (a instanceof L.LatLngBounds) {
1955                 return a;
1956         }
1957         return new L.LatLngBounds(a, b);
1958 };
1959
1960
1961
1962 /*
1963  * @namespace Projection
1964  * @section
1965  * Leaflet comes with a set of already defined Projections out of the box:
1966  *
1967  * @projection L.Projection.LonLat
1968  *
1969  * Equirectangular, or Plate Carree projection — the most simple projection,
1970  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1971  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1972  * `EPSG:3395` and `Simple` CRS.
1973  */
1974
1975 L.Projection = {};
1976
1977 L.Projection.LonLat = {
1978         project: function (latlng) {
1979                 return new L.Point(latlng.lng, latlng.lat);
1980         },
1981
1982         unproject: function (point) {
1983                 return new L.LatLng(point.y, point.x);
1984         },
1985
1986         bounds: L.bounds([-180, -90], [180, 90])
1987 };
1988
1989
1990
1991 /*
1992  * @namespace Projection
1993  * @projection L.Projection.SphericalMercator
1994  *
1995  * Spherical Mercator projection — the most common projection for online maps,
1996  * used by almost all free and commercial tile providers. Assumes that Earth is
1997  * a sphere. Used by the `EPSG:3857` CRS.
1998  */
1999
2000 L.Projection.SphericalMercator = {
2001
2002         R: 6378137,
2003         MAX_LATITUDE: 85.0511287798,
2004
2005         project: function (latlng) {
2006                 var d = Math.PI / 180,
2007                     max = this.MAX_LATITUDE,
2008                     lat = Math.max(Math.min(max, latlng.lat), -max),
2009                     sin = Math.sin(lat * d);
2010
2011                 return new L.Point(
2012                                 this.R * latlng.lng * d,
2013                                 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2014         },
2015
2016         unproject: function (point) {
2017                 var d = 180 / Math.PI;
2018
2019                 return new L.LatLng(
2020                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2021                         point.x * d / this.R);
2022         },
2023
2024         bounds: (function () {
2025                 var d = 6378137 * Math.PI;
2026                 return L.bounds([-d, -d], [d, d]);
2027         })()
2028 };
2029
2030
2031
2032 /*
2033  * @class CRS
2034  * @aka L.CRS
2035  * Abstract class that defines coordinate reference systems for projecting
2036  * geographical points into pixel (screen) coordinates and back (and to
2037  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2038  * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2039  *
2040  * Leaflet defines the most usual CRSs by default. If you want to use a
2041  * CRS not defined by default, take a look at the
2042  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2043  */
2044
2045 L.CRS = {
2046         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2047         // Projects geographical coordinates into pixel coordinates for a given zoom.
2048         latLngToPoint: function (latlng, zoom) {
2049                 var projectedPoint = this.projection.project(latlng),
2050                     scale = this.scale(zoom);
2051
2052                 return this.transformation._transform(projectedPoint, scale);
2053         },
2054
2055         // @method pointToLatLng(point: Point, zoom: Number): LatLng
2056         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2057         // zoom into geographical coordinates.
2058         pointToLatLng: function (point, zoom) {
2059                 var scale = this.scale(zoom),
2060                     untransformedPoint = this.transformation.untransform(point, scale);
2061
2062                 return this.projection.unproject(untransformedPoint);
2063         },
2064
2065         // @method project(latlng: LatLng): Point
2066         // Projects geographical coordinates into coordinates in units accepted for
2067         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2068         project: function (latlng) {
2069                 return this.projection.project(latlng);
2070         },
2071
2072         // @method unproject(point: Point): LatLng
2073         // Given a projected coordinate returns the corresponding LatLng.
2074         // The inverse of `project`.
2075         unproject: function (point) {
2076                 return this.projection.unproject(point);
2077         },
2078
2079         // @method scale(zoom: Number): Number
2080         // Returns the scale used when transforming projected coordinates into
2081         // pixel coordinates for a particular zoom. For example, it returns
2082         // `256 * 2^zoom` for Mercator-based CRS.
2083         scale: function (zoom) {
2084                 return 256 * Math.pow(2, zoom);
2085         },
2086
2087         // @method zoom(scale: Number): Number
2088         // Inverse of `scale()`, returns the zoom level corresponding to a scale
2089         // factor of `scale`.
2090         zoom: function (scale) {
2091                 return Math.log(scale / 256) / Math.LN2;
2092         },
2093
2094         // @method getProjectedBounds(zoom: Number): Bounds
2095         // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2096         getProjectedBounds: function (zoom) {
2097                 if (this.infinite) { return null; }
2098
2099                 var b = this.projection.bounds,
2100                     s = this.scale(zoom),
2101                     min = this.transformation.transform(b.min, s),
2102                     max = this.transformation.transform(b.max, s);
2103
2104                 return L.bounds(min, max);
2105         },
2106
2107         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2108         // Returns the distance between two geographical coordinates.
2109
2110         // @property code: String
2111         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2112         //
2113         // @property wrapLng: Number[]
2114         // An array of two numbers defining whether the longitude (horizontal) coordinate
2115         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2116         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2117         //
2118         // @property wrapLat: Number[]
2119         // Like `wrapLng`, but for the latitude (vertical) axis.
2120
2121         // wrapLng: [min, max],
2122         // wrapLat: [min, max],
2123
2124         // @property infinite: Boolean
2125         // If true, the coordinate space will be unbounded (infinite in both axes)
2126         infinite: false,
2127
2128         // @method wrapLatLng(latlng: LatLng): LatLng
2129         // Returns a `LatLng` where lat and lng has been wrapped according to the
2130         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2131         wrapLatLng: function (latlng) {
2132                 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2133                     lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2134                     alt = latlng.alt;
2135
2136                 return L.latLng(lat, lng, alt);
2137         }
2138 };
2139
2140
2141
2142 /*
2143  * @namespace CRS
2144  * @crs L.CRS.Simple
2145  *
2146  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2147  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2148  * axis should still be inverted (going from bottom to top). `distance()` returns
2149  * simple euclidean distance.
2150  */
2151
2152 L.CRS.Simple = L.extend({}, L.CRS, {
2153         projection: L.Projection.LonLat,
2154         transformation: new L.Transformation(1, 0, -1, 0),
2155
2156         scale: function (zoom) {
2157                 return Math.pow(2, zoom);
2158         },
2159
2160         zoom: function (scale) {
2161                 return Math.log(scale) / Math.LN2;
2162         },
2163
2164         distance: function (latlng1, latlng2) {
2165                 var dx = latlng2.lng - latlng1.lng,
2166                     dy = latlng2.lat - latlng1.lat;
2167
2168                 return Math.sqrt(dx * dx + dy * dy);
2169         },
2170
2171         infinite: true
2172 });
2173
2174
2175
2176 /*
2177  * @namespace CRS
2178  * @crs L.CRS.Earth
2179  *
2180  * Serves as the base for CRS that are global such that they cover the earth.
2181  * Can only be used as the base for other CRS and cannot be used directly,
2182  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2183  * meters.
2184  */
2185
2186 L.CRS.Earth = L.extend({}, L.CRS, {
2187         wrapLng: [-180, 180],
2188
2189         // Mean Earth Radius, as recommended for use by
2190         // the International Union of Geodesy and Geophysics,
2191         // see http://rosettacode.org/wiki/Haversine_formula
2192         R: 6371000,
2193
2194         // distance between two geographical points using spherical law of cosines approximation
2195         distance: function (latlng1, latlng2) {
2196                 var rad = Math.PI / 180,
2197                     lat1 = latlng1.lat * rad,
2198                     lat2 = latlng2.lat * rad,
2199                     a = Math.sin(lat1) * Math.sin(lat2) +
2200                         Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2201
2202                 return this.R * Math.acos(Math.min(a, 1));
2203         }
2204 });
2205
2206
2207
2208 /*
2209  * @namespace CRS
2210  * @crs L.CRS.EPSG3857
2211  *
2212  * The most common CRS for online maps, used by almost all free and commercial
2213  * tile providers. Uses Spherical Mercator projection. Set in by default in
2214  * Map's `crs` option.
2215  */
2216
2217 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2218         code: 'EPSG:3857',
2219         projection: L.Projection.SphericalMercator,
2220
2221         transformation: (function () {
2222                 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2223                 return new L.Transformation(scale, 0.5, -scale, 0.5);
2224         }())
2225 });
2226
2227 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2228         code: 'EPSG:900913'
2229 });
2230
2231
2232
2233 /*
2234  * @namespace CRS
2235  * @crs L.CRS.EPSG4326
2236  *
2237  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2238  */
2239
2240 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2241         code: 'EPSG:4326',
2242         projection: L.Projection.LonLat,
2243         transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2244 });
2245
2246
2247
2248 /*
2249  * @class Map
2250  * @aka L.Map
2251  * @inherits Evented
2252  *
2253  * The central class of the API — it is used to create a map on a page and manipulate it.
2254  *
2255  * @example
2256  *
2257  * ```js
2258  * // initialize the map on the "map" div with a given center and zoom
2259  * var map = L.map('map', {
2260  *      center: [51.505, -0.09],
2261  *      zoom: 13
2262  * });
2263  * ```
2264  *
2265  */
2266
2267 L.Map = L.Evented.extend({
2268
2269         options: {
2270                 // @section Map State Options
2271                 // @option crs: CRS = L.CRS.EPSG3857
2272                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2273                 // sure what it means.
2274                 crs: L.CRS.EPSG3857,
2275
2276                 // @option center: LatLng = undefined
2277                 // Initial geographic center of the map
2278                 center: undefined,
2279
2280                 // @option zoom: Number = undefined
2281                 // Initial map zoom level
2282                 zoom: undefined,
2283
2284                 // @option minZoom: Number = undefined
2285                 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2286                 minZoom: undefined,
2287
2288                 // @option maxZoom: Number = undefined
2289                 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2290                 maxZoom: undefined,
2291
2292                 // @option layers: Layer[] = []
2293                 // Array of layers that will be added to the map initially
2294                 layers: [],
2295
2296                 // @option maxBounds: LatLngBounds = null
2297                 // When this option is set, the map restricts the view to the given
2298                 // geographical bounds, bouncing the user back when he tries to pan
2299                 // outside the view. To set the restriction dynamically, use
2300                 // [`setMaxBounds`](#map-setmaxbounds) method.
2301                 maxBounds: undefined,
2302
2303                 // @option renderer: Renderer = *
2304                 // The default method for drawing vector layers on the map. `L.SVG`
2305                 // or `L.Canvas` by default depending on browser support.
2306                 renderer: undefined,
2307
2308
2309                 // @section Animation Options
2310                 // @option fadeAnimation: Boolean = true
2311                 // Whether the tile fade animation is enabled. By default it's enabled
2312                 // in all browsers that support CSS3 Transitions except Android.
2313                 fadeAnimation: true,
2314
2315                 // @option markerZoomAnimation: Boolean = true
2316                 // Whether markers animate their zoom with the zoom animation, if disabled
2317                 // they will disappear for the length of the animation. By default it's
2318                 // enabled in all browsers that support CSS3 Transitions except Android.
2319                 markerZoomAnimation: true,
2320
2321                 // @option transform3DLimit: Number = 2^23
2322                 // Defines the maximum size of a CSS translation transform. The default
2323                 // value should not be changed unless a web browser positions layers in
2324                 // the wrong place after doing a large `panBy`.
2325                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2326
2327                 // @section Interaction Options
2328                 // @option zoomSnap: Number = 1
2329                 // Forces the map's zoom level to always be a multiple of this, particularly
2330                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2331                 // By default, the zoom level snaps to the nearest integer; lower values
2332                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2333                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2334                 zoomSnap: 1,
2335
2336                 // @option zoomDelta: Number = 1
2337                 // Controls how much the map's zoom level will change after a
2338                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2339                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2340                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2341                 zoomDelta: 1,
2342
2343                 // @option trackResize: Boolean = true
2344                 // Whether the map automatically handles browser window resize to update itself.
2345                 trackResize: true
2346         },
2347
2348         initialize: function (id, options) { // (HTMLElement or String, Object)
2349                 options = L.setOptions(this, options);
2350
2351                 this._initContainer(id);
2352                 this._initLayout();
2353
2354                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2355                 this._onResize = L.bind(this._onResize, this);
2356
2357                 this._initEvents();
2358
2359                 if (options.maxBounds) {
2360                         this.setMaxBounds(options.maxBounds);
2361                 }
2362
2363                 if (options.zoom !== undefined) {
2364                         this._zoom = this._limitZoom(options.zoom);
2365                 }
2366
2367                 if (options.center && options.zoom !== undefined) {
2368                         this.setView(L.latLng(options.center), options.zoom, {reset: true});
2369                 }
2370
2371                 this._handlers = [];
2372                 this._layers = {};
2373                 this._zoomBoundLayers = {};
2374                 this._sizeChanged = true;
2375
2376                 this.callInitHooks();
2377
2378                 this._addLayers(this.options.layers);
2379         },
2380
2381
2382         // @section Methods for modifying map state
2383
2384         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2385         // Sets the view of the map (geographical center and zoom) with the given
2386         // animation options.
2387         setView: function (center, zoom) {
2388                 // replaced by animation-powered implementation in Map.PanAnimation.js
2389                 zoom = zoom === undefined ? this.getZoom() : zoom;
2390                 this._resetView(L.latLng(center), zoom);
2391                 return this;
2392         },
2393
2394         // @method setZoom(zoom: Number, options: Zoom/pan options): this
2395         // Sets the zoom of the map.
2396         setZoom: function (zoom, options) {
2397                 if (!this._loaded) {
2398                         this._zoom = zoom;
2399                         return this;
2400                 }
2401                 return this.setView(this.getCenter(), zoom, {zoom: options});
2402         },
2403
2404         // @method zoomIn(delta?: Number, options?: Zoom options): this
2405         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2406         zoomIn: function (delta, options) {
2407                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2408                 return this.setZoom(this._zoom + delta, options);
2409         },
2410
2411         // @method zoomOut(delta?: Number, options?: Zoom options): this
2412         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2413         zoomOut: function (delta, options) {
2414                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2415                 return this.setZoom(this._zoom - delta, options);
2416         },
2417
2418         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2419         // Zooms the map while keeping a specified geographical point on the map
2420         // stationary (e.g. used internally for scroll zoom and double-click zoom).
2421         // @alternative
2422         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2423         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2424         setZoomAround: function (latlng, zoom, options) {
2425                 var scale = this.getZoomScale(zoom),
2426                     viewHalf = this.getSize().divideBy(2),
2427                     containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2428
2429                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2430                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2431
2432                 return this.setView(newCenter, zoom, {zoom: options});
2433         },
2434
2435         _getBoundsCenterZoom: function (bounds, options) {
2436
2437                 options = options || {};
2438                 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2439
2440                 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2441                     paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2442
2443                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2444
2445                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2446
2447                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2448
2449                     swPoint = this.project(bounds.getSouthWest(), zoom),
2450                     nePoint = this.project(bounds.getNorthEast(), zoom),
2451                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2452
2453                 return {
2454                         center: center,
2455                         zoom: zoom
2456                 };
2457         },
2458
2459         // @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this
2460         // Sets a map view that contains the given geographical bounds with the
2461         // maximum zoom level possible.
2462         fitBounds: function (bounds, options) {
2463
2464                 bounds = L.latLngBounds(bounds);
2465
2466                 if (!bounds.isValid()) {
2467                         throw new Error('Bounds are not valid.');
2468                 }
2469
2470                 var target = this._getBoundsCenterZoom(bounds, options);
2471                 return this.setView(target.center, target.zoom, options);
2472         },
2473
2474         // @method fitWorld(options?: fitBounds options): this
2475         // Sets a map view that mostly contains the whole world with the maximum
2476         // zoom level possible.
2477         fitWorld: function (options) {
2478                 return this.fitBounds([[-90, -180], [90, 180]], options);
2479         },
2480
2481         // @method panTo(latlng: LatLng, options?: Pan options): this
2482         // Pans the map to a given center.
2483         panTo: function (center, options) { // (LatLng)
2484                 return this.setView(center, this._zoom, {pan: options});
2485         },
2486
2487         // @method panBy(offset: Point): this
2488         // Pans the map by a given number of pixels (animated).
2489         panBy: function (offset) { // (Point)
2490                 // replaced with animated panBy in Map.PanAnimation.js
2491                 this.fire('movestart');
2492
2493                 this._rawPanBy(L.point(offset));
2494
2495                 this.fire('move');
2496                 return this.fire('moveend');
2497         },
2498
2499         // @method setMaxBounds(bounds: Bounds): this
2500         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2501         setMaxBounds: function (bounds) {
2502                 bounds = L.latLngBounds(bounds);
2503
2504                 if (!bounds.isValid()) {
2505                         this.options.maxBounds = null;
2506                         return this.off('moveend', this._panInsideMaxBounds);
2507                 } else if (this.options.maxBounds) {
2508                         this.off('moveend', this._panInsideMaxBounds);
2509                 }
2510
2511                 this.options.maxBounds = bounds;
2512
2513                 if (this._loaded) {
2514                         this._panInsideMaxBounds();
2515                 }
2516
2517                 return this.on('moveend', this._panInsideMaxBounds);
2518         },
2519
2520         // @method setMinZoom(zoom: Number): this
2521         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2522         setMinZoom: function (zoom) {
2523                 this.options.minZoom = zoom;
2524
2525                 if (this._loaded && this.getZoom() < this.options.minZoom) {
2526                         return this.setZoom(zoom);
2527                 }
2528
2529                 return this;
2530         },
2531
2532         // @method setMaxZoom(zoom: Number): this
2533         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2534         setMaxZoom: function (zoom) {
2535                 this.options.maxZoom = zoom;
2536
2537                 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2538                         return this.setZoom(zoom);
2539                 }
2540
2541                 return this;
2542         },
2543
2544         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2545         // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
2546         panInsideBounds: function (bounds, options) {
2547                 this._enforcingBounds = true;
2548                 var center = this.getCenter(),
2549                     newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2550
2551                 if (!center.equals(newCenter)) {
2552                         this.panTo(newCenter, options);
2553                 }
2554
2555                 this._enforcingBounds = false;
2556                 return this;
2557         },
2558
2559         // @method invalidateSize(options: Zoom/Pan options): this
2560         // Checks if the map container size changed and updates the map if so —
2561         // call it after you've changed the map size dynamically, also animating
2562         // pan by default. If `options.pan` is `false`, panning will not occur.
2563         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2564         // that it doesn't happen often even if the method is called many
2565         // times in a row.
2566
2567         // @alternative
2568         // @method invalidateSize(animate: Boolean): this
2569         // Checks if the map container size changed and updates the map if so —
2570         // call it after you've changed the map size dynamically, also animating
2571         // pan by default.
2572         invalidateSize: function (options) {
2573                 if (!this._loaded) { return this; }
2574
2575                 options = L.extend({
2576                         animate: false,
2577                         pan: true
2578                 }, options === true ? {animate: true} : options);
2579
2580                 var oldSize = this.getSize();
2581                 this._sizeChanged = true;
2582                 this._lastCenter = null;
2583
2584                 var newSize = this.getSize(),
2585                     oldCenter = oldSize.divideBy(2).round(),
2586                     newCenter = newSize.divideBy(2).round(),
2587                     offset = oldCenter.subtract(newCenter);
2588
2589                 if (!offset.x && !offset.y) { return this; }
2590
2591                 if (options.animate && options.pan) {
2592                         this.panBy(offset);
2593
2594                 } else {
2595                         if (options.pan) {
2596                                 this._rawPanBy(offset);
2597                         }
2598
2599                         this.fire('move');
2600
2601                         if (options.debounceMoveend) {
2602                                 clearTimeout(this._sizeTimer);
2603                                 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2604                         } else {
2605                                 this.fire('moveend');
2606                         }
2607                 }
2608
2609                 // @section Map state change events
2610                 // @event resize: ResizeEvent
2611                 // Fired when the map is resized.
2612                 return this.fire('resize', {
2613                         oldSize: oldSize,
2614                         newSize: newSize
2615                 });
2616         },
2617
2618         // @section Methods for modifying map state
2619         // @method stop(): this
2620         // Stops the currently running `panTo` or `flyTo` animation, if any.
2621         stop: function () {
2622                 this.setZoom(this._limitZoom(this._zoom));
2623                 if (!this.options.zoomSnap) {
2624                         this.fire('viewreset');
2625                 }
2626                 return this._stop();
2627         },
2628
2629
2630         // TODO handler.addTo
2631         // TODO Appropiate docs section?
2632         // @section Other Methods
2633         // @method addHandler(name: String, HandlerClass: Function): this
2634         // Adds a new `Handler` to the map, given its name and constructor function.
2635         addHandler: function (name, HandlerClass) {
2636                 if (!HandlerClass) { return this; }
2637
2638                 var handler = this[name] = new HandlerClass(this);
2639
2640                 this._handlers.push(handler);
2641
2642                 if (this.options[name]) {
2643                         handler.enable();
2644                 }
2645
2646                 return this;
2647         },
2648
2649         // @method remove(): this
2650         // Destroys the map and clears all related event listeners.
2651         remove: function () {
2652
2653                 this._initEvents(true);
2654
2655                 if (this._containerId !== this._container._leaflet_id) {
2656                         throw new Error('Map container is being reused by another instance');
2657                 }
2658
2659                 try {
2660                         // throws error in IE6-8
2661                         delete this._container._leaflet_id;
2662                         delete this._containerId;
2663                 } catch (e) {
2664                         /*eslint-disable */
2665                         this._container._leaflet_id = undefined;
2666                         /*eslint-enable */
2667                         this._containerId = undefined;
2668                 }
2669
2670                 L.DomUtil.remove(this._mapPane);
2671
2672                 if (this._clearControlPos) {
2673                         this._clearControlPos();
2674                 }
2675
2676                 this._clearHandlers();
2677
2678                 if (this._loaded) {
2679                         // @section Map state change events
2680                         // @event unload: Event
2681                         // Fired when the map is destroyed with [remove](#map-remove) method.
2682                         this.fire('unload');
2683                 }
2684
2685                 for (var i in this._layers) {
2686                         this._layers[i].remove();
2687                 }
2688
2689                 return this;
2690         },
2691
2692         // @section Other Methods
2693         // @method createPane(name: String, container?: HTMLElement): HTMLElement
2694         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2695         // then returns it. The pane is created as a children of `container`, or
2696         // as a children of the main map pane if not set.
2697         createPane: function (name, container) {
2698                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
2699                     pane = L.DomUtil.create('div', className, container || this._mapPane);
2700
2701                 if (name) {
2702                         this._panes[name] = pane;
2703                 }
2704                 return pane;
2705         },
2706
2707         // @section Methods for Getting Map State
2708
2709         // @method getCenter(): LatLng
2710         // Returns the geographical center of the map view
2711         getCenter: function () {
2712                 this._checkIfLoaded();
2713
2714                 if (this._lastCenter && !this._moved()) {
2715                         return this._lastCenter;
2716                 }
2717                 return this.layerPointToLatLng(this._getCenterLayerPoint());
2718         },
2719
2720         // @method getZoom(): Number
2721         // Returns the current zoom level of the map view
2722         getZoom: function () {
2723                 return this._zoom;
2724         },
2725
2726         // @method getBounds(): LatLngBounds
2727         // Returns the geographical bounds visible in the current map view
2728         getBounds: function () {
2729                 var bounds = this.getPixelBounds(),
2730                     sw = this.unproject(bounds.getBottomLeft()),
2731                     ne = this.unproject(bounds.getTopRight());
2732
2733                 return new L.LatLngBounds(sw, ne);
2734         },
2735
2736         // @method getMinZoom(): Number
2737         // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
2738         getMinZoom: function () {
2739                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
2740         },
2741
2742         // @method getMaxZoom(): Number
2743         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
2744         getMaxZoom: function () {
2745                 return this.options.maxZoom === undefined ?
2746                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
2747                         this.options.maxZoom;
2748         },
2749
2750         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
2751         // Returns the maximum zoom level on which the given bounds fit to the map
2752         // view in its entirety. If `inside` (optional) is set to `true`, the method
2753         // instead returns the minimum zoom level on which the map view fits into
2754         // the given bounds in its entirety.
2755         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
2756                 bounds = L.latLngBounds(bounds);
2757                 padding = L.point(padding || [0, 0]);
2758
2759                 var zoom = this.getZoom() || 0,
2760                     min = this.getMinZoom(),
2761                     max = this.getMaxZoom(),
2762                     nw = bounds.getNorthWest(),
2763                     se = bounds.getSouthEast(),
2764                     size = this.getSize().subtract(padding),
2765                     boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)),
2766                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
2767
2768                 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
2769                 zoom = this.getScaleZoom(scale, zoom);
2770
2771                 if (snap) {
2772                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
2773                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
2774                 }
2775
2776                 return Math.max(min, Math.min(max, zoom));
2777         },
2778
2779         // @method getSize(): Point
2780         // Returns the current size of the map container (in pixels).
2781         getSize: function () {
2782                 if (!this._size || this._sizeChanged) {
2783                         this._size = new L.Point(
2784                                 this._container.clientWidth,
2785                                 this._container.clientHeight);
2786
2787                         this._sizeChanged = false;
2788                 }
2789                 return this._size.clone();
2790         },
2791
2792         // @method getPixelBounds(): Bounds
2793         // Returns the bounds of the current map view in projected pixel
2794         // coordinates (sometimes useful in layer and overlay implementations).
2795         getPixelBounds: function (center, zoom) {
2796                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
2797                 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
2798         },
2799
2800         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
2801         // the map pane? "left point of the map layer" can be confusing, specially
2802         // since there can be negative offsets.
2803         // @method getPixelOrigin(): Point
2804         // Returns the projected pixel coordinates of the top left point of
2805         // the map layer (useful in custom layer and overlay implementations).
2806         getPixelOrigin: function () {
2807                 this._checkIfLoaded();
2808                 return this._pixelOrigin;
2809         },
2810
2811         // @method getPixelWorldBounds(zoom?: Number): Bounds
2812         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
2813         // If `zoom` is omitted, the map's current zoom level is used.
2814         getPixelWorldBounds: function (zoom) {
2815                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
2816         },
2817
2818         // @section Other Methods
2819
2820         // @method getPane(pane: String|HTMLElement): HTMLElement
2821         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
2822         getPane: function (pane) {
2823                 return typeof pane === 'string' ? this._panes[pane] : pane;
2824         },
2825
2826         // @method getPanes(): Object
2827         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
2828         // the panes as values.
2829         getPanes: function () {
2830                 return this._panes;
2831         },
2832
2833         // @method getContainer: HTMLElement
2834         // Returns the HTML element that contains the map.
2835         getContainer: function () {
2836                 return this._container;
2837         },
2838
2839
2840         // @section Conversion Methods
2841
2842         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
2843         // Returns the scale factor to be applied to a map transition from zoom level
2844         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
2845         getZoomScale: function (toZoom, fromZoom) {
2846                 // TODO replace with universal implementation after refactoring projections
2847                 var crs = this.options.crs;
2848                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2849                 return crs.scale(toZoom) / crs.scale(fromZoom);
2850         },
2851
2852         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
2853         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
2854         // level and everything is scaled by a factor of `scale`. Inverse of
2855         // [`getZoomScale`](#map-getZoomScale).
2856         getScaleZoom: function (scale, fromZoom) {
2857                 var crs = this.options.crs;
2858                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2859                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
2860                 return isNaN(zoom) ? Infinity : zoom;
2861         },
2862
2863         // @method project(latlng: LatLng, zoom: Number): Point
2864         // Projects a geographical coordinate `LatLng` according to the projection
2865         // of the map's CRS, then scales it according to `zoom` and the CRS's
2866         // `Transformation`. The result is pixel coordinate relative to
2867         // the CRS origin.
2868         project: function (latlng, zoom) {
2869                 zoom = zoom === undefined ? this._zoom : zoom;
2870                 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
2871         },
2872
2873         // @method unproject(point: Point, zoom: Number): LatLng
2874         // Inverse of [`project`](#map-project).
2875         unproject: function (point, zoom) {
2876                 zoom = zoom === undefined ? this._zoom : zoom;
2877                 return this.options.crs.pointToLatLng(L.point(point), zoom);
2878         },
2879
2880         // @method layerPointToLatLng(point: Point): LatLng
2881         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2882         // returns the corresponding geographical coordinate (for the current zoom level).
2883         layerPointToLatLng: function (point) {
2884                 var projectedPoint = L.point(point).add(this.getPixelOrigin());
2885                 return this.unproject(projectedPoint);
2886         },
2887
2888         // @method latLngToLayerPoint(latlng: LatLng): Point
2889         // Given a geographical coordinate, returns the corresponding pixel coordinate
2890         // relative to the [origin pixel](#map-getpixelorigin).
2891         latLngToLayerPoint: function (latlng) {
2892                 var projectedPoint = this.project(L.latLng(latlng))._round();
2893                 return projectedPoint._subtract(this.getPixelOrigin());
2894         },
2895
2896         // @method wrapLatLng(latlng: LatLng): LatLng
2897         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
2898         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
2899         // CRS's bounds.
2900         // By default this means longitude is wrapped around the dateline so its
2901         // value is between -180 and +180 degrees.
2902         wrapLatLng: function (latlng) {
2903                 return this.options.crs.wrapLatLng(L.latLng(latlng));
2904         },
2905
2906         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2907         // Returns the distance between two geographical coordinates according to
2908         // the map's CRS. By default this measures distance in meters.
2909         distance: function (latlng1, latlng2) {
2910                 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
2911         },
2912
2913         // @method containerPointToLayerPoint(point: Point): Point
2914         // Given a pixel coordinate relative to the map container, returns the corresponding
2915         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
2916         containerPointToLayerPoint: function (point) { // (Point)
2917                 return L.point(point).subtract(this._getMapPanePos());
2918         },
2919
2920         // @method layerPointToContainerPoint(point: Point): Point
2921         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2922         // returns the corresponding pixel coordinate relative to the map container.
2923         layerPointToContainerPoint: function (point) { // (Point)
2924                 return L.point(point).add(this._getMapPanePos());
2925         },
2926
2927         // @method containerPointToLatLng(point: Point): Point
2928         // Given a pixel coordinate relative to the map container, returns
2929         // the corresponding geographical coordinate (for the current zoom level).
2930         containerPointToLatLng: function (point) {
2931                 var layerPoint = this.containerPointToLayerPoint(L.point(point));
2932                 return this.layerPointToLatLng(layerPoint);
2933         },
2934
2935         // @method latLngToContainerPoint(latlng: LatLng): Point
2936         // Given a geographical coordinate, returns the corresponding pixel coordinate
2937         // relative to the map container.
2938         latLngToContainerPoint: function (latlng) {
2939                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
2940         },
2941
2942         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
2943         // Given a MouseEvent object, returns the pixel coordinate relative to the
2944         // map container where the event took place.
2945         mouseEventToContainerPoint: function (e) {
2946                 return L.DomEvent.getMousePosition(e, this._container);
2947         },
2948
2949         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
2950         // Given a MouseEvent object, returns the pixel coordinate relative to
2951         // the [origin pixel](#map-getpixelorigin) where the event took place.
2952         mouseEventToLayerPoint: function (e) {
2953                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
2954         },
2955
2956         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
2957         // Given a MouseEvent object, returns geographical coordinate where the
2958         // event took place.
2959         mouseEventToLatLng: function (e) { // (MouseEvent)
2960                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
2961         },
2962
2963
2964         // map initialization methods
2965
2966         _initContainer: function (id) {
2967                 var container = this._container = L.DomUtil.get(id);
2968
2969                 if (!container) {
2970                         throw new Error('Map container not found.');
2971                 } else if (container._leaflet_id) {
2972                         throw new Error('Map container is already initialized.');
2973                 }
2974
2975                 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
2976                 this._containerId = L.Util.stamp(container);
2977         },
2978
2979         _initLayout: function () {
2980                 var container = this._container;
2981
2982                 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
2983
2984                 L.DomUtil.addClass(container, 'leaflet-container' +
2985                         (L.Browser.touch ? ' leaflet-touch' : '') +
2986                         (L.Browser.retina ? ' leaflet-retina' : '') +
2987                         (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
2988                         (L.Browser.safari ? ' leaflet-safari' : '') +
2989                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
2990
2991                 var position = L.DomUtil.getStyle(container, 'position');
2992
2993                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
2994                         container.style.position = 'relative';
2995                 }
2996
2997                 this._initPanes();
2998
2999                 if (this._initControlPos) {
3000                         this._initControlPos();
3001                 }
3002         },
3003
3004         _initPanes: function () {
3005                 var panes = this._panes = {};
3006                 this._paneRenderers = {};
3007
3008                 // @section
3009                 //
3010                 // Panes are DOM elements used to control the ordering of layers on the map. You
3011                 // can access panes with [`map.getPane`](#map-getpane) or
3012                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3013                 // [`map.createPane`](#map-createpane) method.
3014                 //
3015                 // Every map has the following default panes that differ only in zIndex.
3016                 //
3017                 // @pane mapPane: HTMLElement = 'auto'
3018                 // Pane that contains all other map panes
3019
3020                 this._mapPane = this.createPane('mapPane', this._container);
3021                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3022
3023                 // @pane tilePane: HTMLElement = 200
3024                 // Pane for `GridLayer`s and `TileLayer`s
3025                 this.createPane('tilePane');
3026                 // @pane overlayPane: HTMLElement = 400
3027                 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3028                 this.createPane('shadowPane');
3029                 // @pane shadowPane: HTMLElement = 500
3030                 // Pane for overlay shadows (e.g. `Marker` shadows)
3031                 this.createPane('overlayPane');
3032                 // @pane markerPane: HTMLElement = 600
3033                 // Pane for `Icon`s of `Marker`s
3034                 this.createPane('markerPane');
3035                 // @pane tooltipPane: HTMLElement = 650
3036                 // Pane for tooltip.
3037                 this.createPane('tooltipPane');
3038                 // @pane popupPane: HTMLElement = 700
3039                 // Pane for `Popup`s.
3040                 this.createPane('popupPane');
3041
3042                 if (!this.options.markerZoomAnimation) {
3043                         L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3044                         L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3045                 }
3046         },
3047
3048
3049         // private methods that modify map state
3050
3051         // @section Map state change events
3052         _resetView: function (center, zoom) {
3053                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3054
3055                 var loading = !this._loaded;
3056                 this._loaded = true;
3057                 zoom = this._limitZoom(zoom);
3058
3059                 this.fire('viewprereset');
3060
3061                 var zoomChanged = this._zoom !== zoom;
3062                 this
3063                         ._moveStart(zoomChanged)
3064                         ._move(center, zoom)
3065                         ._moveEnd(zoomChanged);
3066
3067                 // @event viewreset: Event
3068                 // Fired when the map needs to redraw its content (this usually happens
3069                 // on map zoom or load). Very useful for creating custom overlays.
3070                 this.fire('viewreset');
3071
3072                 // @event load: Event
3073                 // Fired when the map is initialized (when its center and zoom are set
3074                 // for the first time).
3075                 if (loading) {
3076                         this.fire('load');
3077                 }
3078         },
3079
3080         _moveStart: function (zoomChanged) {
3081                 // @event zoomstart: Event
3082                 // Fired when the map zoom is about to change (e.g. before zoom animation).
3083                 // @event movestart: Event
3084                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3085                 if (zoomChanged) {
3086                         this.fire('zoomstart');
3087                 }
3088                 return this.fire('movestart');
3089         },
3090
3091         _move: function (center, zoom, data) {
3092                 if (zoom === undefined) {
3093                         zoom = this._zoom;
3094                 }
3095                 var zoomChanged = this._zoom !== zoom;
3096
3097                 this._zoom = zoom;
3098                 this._lastCenter = center;
3099                 this._pixelOrigin = this._getNewPixelOrigin(center);
3100
3101                 // @event zoom: Event
3102                 // Fired repeatedly during any change in zoom level, including zoom
3103                 // and fly animations.
3104                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
3105                         this.fire('zoom', data);
3106                 }
3107
3108                 // @event move: Event
3109                 // Fired repeatedly during any movement of the map, including pan and
3110                 // fly animations.
3111                 return this.fire('move', data);
3112         },
3113
3114         _moveEnd: function (zoomChanged) {
3115                 // @event zoomend: Event
3116                 // Fired when the map has changed, after any animations.
3117                 if (zoomChanged) {
3118                         this.fire('zoomend');
3119                 }
3120
3121                 // @event moveend: Event
3122                 // Fired when the center of the map stops changing (e.g. user stopped
3123                 // dragging the map).
3124                 return this.fire('moveend');
3125         },
3126
3127         _stop: function () {
3128                 L.Util.cancelAnimFrame(this._flyToFrame);
3129                 if (this._panAnim) {
3130                         this._panAnim.stop();
3131                 }
3132                 return this;
3133         },
3134
3135         _rawPanBy: function (offset) {
3136                 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3137         },
3138
3139         _getZoomSpan: function () {
3140                 return this.getMaxZoom() - this.getMinZoom();
3141         },
3142
3143         _panInsideMaxBounds: function () {
3144                 if (!this._enforcingBounds) {
3145                         this.panInsideBounds(this.options.maxBounds);
3146                 }
3147         },
3148
3149         _checkIfLoaded: function () {
3150                 if (!this._loaded) {
3151                         throw new Error('Set map center and zoom first.');
3152                 }
3153         },
3154
3155         // DOM event handling
3156
3157         // @section Interaction events
3158         _initEvents: function (remove) {
3159                 if (!L.DomEvent) { return; }
3160
3161                 this._targets = {};
3162                 this._targets[L.stamp(this._container)] = this;
3163
3164                 var onOff = remove ? 'off' : 'on';
3165
3166                 // @event click: MouseEvent
3167                 // Fired when the user clicks (or taps) the map.
3168                 // @event dblclick: MouseEvent
3169                 // Fired when the user double-clicks (or double-taps) the map.
3170                 // @event mousedown: MouseEvent
3171                 // Fired when the user pushes the mouse button on the map.
3172                 // @event mouseup: MouseEvent
3173                 // Fired when the user releases the mouse button on the map.
3174                 // @event mouseover: MouseEvent
3175                 // Fired when the mouse enters the map.
3176                 // @event mouseout: MouseEvent
3177                 // Fired when the mouse leaves the map.
3178                 // @event mousemove: MouseEvent
3179                 // Fired while the mouse moves over the map.
3180                 // @event contextmenu: MouseEvent
3181                 // Fired when the user pushes the right mouse button on the map, prevents
3182                 // default browser context menu from showing if there are listeners on
3183                 // this event. Also fired on mobile when the user holds a single touch
3184                 // for a second (also called long press).
3185                 // @event keypress: KeyboardEvent
3186                 // Fired when the user presses a key from the keyboard while the map is focused.
3187                 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3188                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3189
3190                 if (this.options.trackResize) {
3191                         L.DomEvent[onOff](window, 'resize', this._onResize, this);
3192                 }
3193
3194                 if (L.Browser.any3d && this.options.transform3DLimit) {
3195                         this[onOff]('moveend', this._onMoveEnd);
3196                 }
3197         },
3198
3199         _onResize: function () {
3200                 L.Util.cancelAnimFrame(this._resizeRequest);
3201                 this._resizeRequest = L.Util.requestAnimFrame(
3202                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
3203         },
3204
3205         _onScroll: function () {
3206                 this._container.scrollTop  = 0;
3207                 this._container.scrollLeft = 0;
3208         },
3209
3210         _onMoveEnd: function () {
3211                 var pos = this._getMapPanePos();
3212                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3213                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3214                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3215                         this._resetView(this.getCenter(), this.getZoom());
3216                 }
3217         },
3218
3219         _findEventTargets: function (e, type) {
3220                 var targets = [],
3221                     target,
3222                     isHover = type === 'mouseout' || type === 'mouseover',
3223                     src = e.target || e.srcElement,
3224                     dragging = false;
3225
3226                 while (src) {
3227                         target = this._targets[L.stamp(src)];
3228                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3229                                 // Prevent firing click after you just dragged an object.
3230                                 dragging = true;
3231                                 break;
3232                         }
3233                         if (target && target.listens(type, true)) {
3234                                 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3235                                 targets.push(target);
3236                                 if (isHover) { break; }
3237                         }
3238                         if (src === this._container) { break; }
3239                         src = src.parentNode;
3240                 }
3241                 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3242                         targets = [this];
3243                 }
3244                 return targets;
3245         },
3246
3247         _handleDOMEvent: function (e) {
3248                 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3249
3250                 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3251
3252                 if (type === 'mousedown') {
3253                         // prevents outline when clicking on keyboard-focusable element
3254                         L.DomUtil.preventOutline(e.target || e.srcElement);
3255                 }
3256
3257                 this._fireDOMEvent(e, type);
3258         },
3259
3260         _fireDOMEvent: function (e, type, targets) {
3261
3262                 if (e.type === 'click') {
3263                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3264                         // @event preclick: MouseEvent
3265                         // Fired before mouse click on the map (sometimes useful when you
3266                         // want something to happen on click before any existing click
3267                         // handlers start running).
3268                         var synth = L.Util.extend({}, e);
3269                         synth.type = 'preclick';
3270                         this._fireDOMEvent(synth, synth.type, targets);
3271                 }
3272
3273                 if (e._stopped) { return; }
3274
3275                 // Find the layer the event is propagating from and its parents.
3276                 targets = (targets || []).concat(this._findEventTargets(e, type));
3277
3278                 if (!targets.length) { return; }
3279
3280                 var target = targets[0];
3281                 if (type === 'contextmenu' && target.listens(type, true)) {
3282                         L.DomEvent.preventDefault(e);
3283                 }
3284
3285                 var data = {
3286                         originalEvent: e
3287                 };
3288
3289                 if (e.type !== 'keypress') {
3290                         var isMarker = target instanceof L.Marker;
3291                         data.containerPoint = isMarker ?
3292                                         this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3293                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3294                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3295                 }
3296
3297                 for (var i = 0; i < targets.length; i++) {
3298                         targets[i].fire(type, data, true);
3299                         if (data.originalEvent._stopped ||
3300                                 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3301                 }
3302         },
3303
3304         _draggableMoved: function (obj) {
3305                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3306                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3307         },
3308
3309         _clearHandlers: function () {
3310                 for (var i = 0, len = this._handlers.length; i < len; i++) {
3311                         this._handlers[i].disable();
3312                 }
3313         },
3314
3315         // @section Other Methods
3316
3317         // @method whenReady(fn: Function, context?: Object): this
3318         // Runs the given function `fn` when the map gets initialized with
3319         // a view (center and zoom) and at least one layer, or immediately
3320         // if it's already initialized, optionally passing a function context.
3321         whenReady: function (callback, context) {
3322                 if (this._loaded) {
3323                         callback.call(context || this, {target: this});
3324                 } else {
3325                         this.on('load', callback, context);
3326                 }
3327                 return this;
3328         },
3329
3330
3331         // private methods for getting map state
3332
3333         _getMapPanePos: function () {
3334                 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3335         },
3336
3337         _moved: function () {
3338                 var pos = this._getMapPanePos();
3339                 return pos && !pos.equals([0, 0]);
3340         },
3341
3342         _getTopLeftPoint: function (center, zoom) {
3343                 var pixelOrigin = center && zoom !== undefined ?
3344                         this._getNewPixelOrigin(center, zoom) :
3345                         this.getPixelOrigin();
3346                 return pixelOrigin.subtract(this._getMapPanePos());
3347         },
3348
3349         _getNewPixelOrigin: function (center, zoom) {
3350                 var viewHalf = this.getSize()._divideBy(2);
3351                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3352         },
3353
3354         _latLngToNewLayerPoint: function (latlng, zoom, center) {
3355                 var topLeft = this._getNewPixelOrigin(center, zoom);
3356                 return this.project(latlng, zoom)._subtract(topLeft);
3357         },
3358
3359         // layer point of the current center
3360         _getCenterLayerPoint: function () {
3361                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3362         },
3363
3364         // offset of the specified place to the current center in pixels
3365         _getCenterOffset: function (latlng) {
3366                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3367         },
3368
3369         // adjust center for view to get inside bounds
3370         _limitCenter: function (center, zoom, bounds) {
3371
3372                 if (!bounds) { return center; }
3373
3374                 var centerPoint = this.project(center, zoom),
3375                     viewHalf = this.getSize().divideBy(2),
3376                     viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3377                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3378
3379                 // If offset is less than a pixel, ignore.
3380                 // This prevents unstable projections from getting into
3381                 // an infinite loop of tiny offsets.
3382                 if (offset.round().equals([0, 0])) {
3383                         return center;
3384                 }
3385
3386                 return this.unproject(centerPoint.add(offset), zoom);
3387         },
3388
3389         // adjust offset for view to get inside bounds
3390         _limitOffset: function (offset, bounds) {
3391                 if (!bounds) { return offset; }
3392
3393                 var viewBounds = this.getPixelBounds(),
3394                     newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3395
3396                 return offset.add(this._getBoundsOffset(newBounds, bounds));
3397         },
3398
3399         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3400         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3401                 var projectedMaxBounds = L.bounds(
3402                         this.project(maxBounds.getNorthEast(), zoom),
3403                         this.project(maxBounds.getSouthWest(), zoom)
3404                     ),
3405                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3406                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3407
3408                     dx = this._rebound(minOffset.x, -maxOffset.x),
3409                     dy = this._rebound(minOffset.y, -maxOffset.y);
3410
3411                 return new L.Point(dx, dy);
3412         },
3413
3414         _rebound: function (left, right) {
3415                 return left + right > 0 ?
3416                         Math.round(left - right) / 2 :
3417                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3418         },
3419
3420         _limitZoom: function (zoom) {
3421                 var min = this.getMinZoom(),
3422                     max = this.getMaxZoom(),
3423                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3424                 if (snap) {
3425                         zoom = Math.round(zoom / snap) * snap;
3426                 }
3427                 return Math.max(min, Math.min(max, zoom));
3428         }
3429 });
3430
3431 // @section
3432
3433 // @factory L.map(id: String, options?: Map options)
3434 // Instantiates a map object given the DOM ID of a `<div>` element
3435 // and optionally an object literal with `Map options`.
3436 //
3437 // @alternative
3438 // @factory L.map(el: HTMLElement, options?: Map options)
3439 // Instantiates a map object given an instance of a `<div>` HTML element
3440 // and optionally an object literal with `Map options`.
3441 L.map = function (id, options) {
3442         return new L.Map(id, options);
3443 };
3444
3445
3446
3447
3448 /*
3449  * @class Layer
3450  * @inherits Evented
3451  * @aka L.Layer
3452  * @aka ILayer
3453  *
3454  * A set of methods from the Layer base class that all Leaflet layers use.
3455  * Inherits all methods, options and events from `L.Evented`.
3456  *
3457  * @example
3458  *
3459  * ```js
3460  * var layer = L.Marker(latlng).addTo(map);
3461  * layer.addTo(map);
3462  * layer.remove();
3463  * ```
3464  *
3465  * @event add: Event
3466  * Fired after the layer is added to a map
3467  *
3468  * @event remove: Event
3469  * Fired after the layer is removed from a map
3470  */
3471
3472
3473 L.Layer = L.Evented.extend({
3474
3475         // Classes extending `L.Layer` will inherit the following options:
3476         options: {
3477                 // @option pane: String = 'overlayPane'
3478                 // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
3479                 pane: 'overlayPane',
3480                 nonBubblingEvents: []  // Array of events that should not be bubbled to DOM parents (like the map)
3481         },
3482
3483         /* @section
3484          * Classes extending `L.Layer` will inherit the following methods:
3485          *
3486          * @method addTo(map: Map): this
3487          * Adds the layer to the given map
3488          */
3489         addTo: function (map) {
3490                 map.addLayer(this);
3491                 return this;
3492         },
3493
3494         // @method remove: this
3495         // Removes the layer from the map it is currently active on.
3496         remove: function () {
3497                 return this.removeFrom(this._map || this._mapToAdd);
3498         },
3499
3500         // @method removeFrom(map: Map): this
3501         // Removes the layer from the given map
3502         removeFrom: function (obj) {
3503                 if (obj) {
3504                         obj.removeLayer(this);
3505                 }
3506                 return this;
3507         },
3508
3509         // @method getPane(name? : String): HTMLElement
3510         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3511         getPane: function (name) {
3512                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3513         },
3514
3515         addInteractiveTarget: function (targetEl) {
3516                 this._map._targets[L.stamp(targetEl)] = this;
3517                 return this;
3518         },
3519
3520         removeInteractiveTarget: function (targetEl) {
3521                 delete this._map._targets[L.stamp(targetEl)];
3522                 return this;
3523         },
3524
3525         _layerAdd: function (e) {
3526                 var map = e.target;
3527
3528                 // check in case layer gets added and then removed before the map is ready
3529                 if (!map.hasLayer(this)) { return; }
3530
3531                 this._map = map;
3532                 this._zoomAnimated = map._zoomAnimated;
3533
3534                 if (this.getEvents) {
3535                         var events = this.getEvents();
3536                         map.on(events, this);
3537                         this.once('remove', function () {
3538                                 map.off(events, this);
3539                         }, this);
3540                 }
3541
3542                 this.onAdd(map);
3543
3544                 if (this.getAttribution && this._map.attributionControl) {
3545                         this._map.attributionControl.addAttribution(this.getAttribution());
3546                 }
3547
3548                 this.fire('add');
3549                 map.fire('layeradd', {layer: this});
3550         }
3551 });
3552
3553 /* @section Extension methods
3554  * @uninheritable
3555  *
3556  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
3557  *
3558  * @method onAdd(map: Map): this
3559  * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
3560  *
3561  * @method onRemove(map: Map): this
3562  * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
3563  *
3564  * @method getEvents(): Object
3565  * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
3566  *
3567  * @method getAttribution(): String
3568  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
3569  *
3570  * @method beforeAdd(map: Map): this
3571  * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
3572  */
3573
3574
3575 /* @namespace Map
3576  * @section Layer events
3577  *
3578  * @event layeradd: LayerEvent
3579  * Fired when a new layer is added to the map.
3580  *
3581  * @event layerremove: LayerEvent
3582  * Fired when some layer is removed from the map
3583  *
3584  * @section Methods for Layers and Controls
3585  */
3586 L.Map.include({
3587         // @method addLayer(layer: Layer): this
3588         // Adds the given layer to the map
3589         addLayer: function (layer) {
3590                 var id = L.stamp(layer);
3591                 if (this._layers[id]) { return this; }
3592                 this._layers[id] = layer;
3593
3594                 layer._mapToAdd = this;
3595
3596                 if (layer.beforeAdd) {
3597                         layer.beforeAdd(this);
3598                 }
3599
3600                 this.whenReady(layer._layerAdd, layer);
3601
3602                 return this;
3603         },
3604
3605         // @method removeLayer(layer: Layer): this
3606         // Removes the given layer from the map.
3607         removeLayer: function (layer) {
3608                 var id = L.stamp(layer);
3609
3610                 if (!this._layers[id]) { return this; }
3611
3612                 if (this._loaded) {
3613                         layer.onRemove(this);
3614                 }
3615
3616                 if (layer.getAttribution && this.attributionControl) {
3617                         this.attributionControl.removeAttribution(layer.getAttribution());
3618                 }
3619
3620                 delete this._layers[id];
3621
3622                 if (this._loaded) {
3623                         this.fire('layerremove', {layer: layer});
3624                         layer.fire('remove');
3625                 }
3626
3627                 layer._map = layer._mapToAdd = null;
3628
3629                 return this;
3630         },
3631
3632         // @method hasLayer(layer: Layer): Boolean
3633         // Returns `true` if the given layer is currently added to the map
3634         hasLayer: function (layer) {
3635                 return !!layer && (L.stamp(layer) in this._layers);
3636         },
3637
3638         /* @method eachLayer(fn: Function, context?: Object): this
3639          * Iterates over the layers of the map, optionally specifying context of the iterator function.
3640          * ```
3641          * map.eachLayer(function(layer){
3642          *     layer.bindPopup('Hello');
3643          * });
3644          * ```
3645          */
3646         eachLayer: function (method, context) {
3647                 for (var i in this._layers) {
3648                         method.call(context, this._layers[i]);
3649                 }
3650                 return this;
3651         },
3652
3653         _addLayers: function (layers) {
3654                 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
3655
3656                 for (var i = 0, len = layers.length; i < len; i++) {
3657                         this.addLayer(layers[i]);
3658                 }
3659         },
3660
3661         _addZoomLimit: function (layer) {
3662                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
3663                         this._zoomBoundLayers[L.stamp(layer)] = layer;
3664                         this._updateZoomLevels();
3665                 }
3666         },
3667
3668         _removeZoomLimit: function (layer) {
3669                 var id = L.stamp(layer);
3670
3671                 if (this._zoomBoundLayers[id]) {
3672                         delete this._zoomBoundLayers[id];
3673                         this._updateZoomLevels();
3674                 }
3675         },
3676
3677         _updateZoomLevels: function () {
3678                 var minZoom = Infinity,
3679                     maxZoom = -Infinity,
3680                     oldZoomSpan = this._getZoomSpan();
3681
3682                 for (var i in this._zoomBoundLayers) {
3683                         var options = this._zoomBoundLayers[i].options;
3684
3685                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
3686                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
3687                 }
3688
3689                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
3690                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
3691
3692                 // @section Map state change events
3693                 // @event zoomlevelschange: Event
3694                 // Fired when the number of zoomlevels on the map is changed due
3695                 // to adding or removing a layer.
3696                 if (oldZoomSpan !== this._getZoomSpan()) {
3697                         this.fire('zoomlevelschange');
3698                 }
3699         }
3700 });
3701
3702
3703
3704 /*
3705  * @namespace Projection
3706  * @projection L.Projection.Mercator
3707  *
3708  * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
3709  */
3710
3711 L.Projection.Mercator = {
3712         R: 6378137,
3713         R_MINOR: 6356752.314245179,
3714
3715         bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
3716
3717         project: function (latlng) {
3718                 var d = Math.PI / 180,
3719                     r = this.R,
3720                     y = latlng.lat * d,
3721                     tmp = this.R_MINOR / r,
3722                     e = Math.sqrt(1 - tmp * tmp),
3723                     con = e * Math.sin(y);
3724
3725                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
3726                 y = -r * Math.log(Math.max(ts, 1E-10));
3727
3728                 return new L.Point(latlng.lng * d * r, y);
3729         },
3730
3731         unproject: function (point) {
3732                 var d = 180 / Math.PI,
3733                     r = this.R,
3734                     tmp = this.R_MINOR / r,
3735                     e = Math.sqrt(1 - tmp * tmp),
3736                     ts = Math.exp(-point.y / r),
3737                     phi = Math.PI / 2 - 2 * Math.atan(ts);
3738
3739                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
3740                         con = e * Math.sin(phi);
3741                         con = Math.pow((1 - con) / (1 + con), e / 2);
3742                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
3743                         phi += dphi;
3744                 }
3745
3746                 return new L.LatLng(phi * d, point.x * d / r);
3747         }
3748 };
3749
3750
3751
3752 /*
3753  * @namespace CRS
3754  * @crs L.CRS.EPSG3395
3755  *
3756  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
3757  */
3758
3759 L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
3760         code: 'EPSG:3395',
3761         projection: L.Projection.Mercator,
3762
3763         transformation: (function () {
3764                 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
3765                 return new L.Transformation(scale, 0.5, -scale, 0.5);
3766         }())
3767 });
3768
3769
3770
3771 /*
3772  * @class GridLayer
3773  * @inherits Layer
3774  * @aka L.GridLayer
3775  *
3776  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
3777  * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
3778  *
3779  *
3780  * @section Synchronous usage
3781  * @example
3782  *
3783  * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
3784  *
3785  * ```js
3786  * var CanvasLayer = L.GridLayer.extend({
3787  *     createTile: function(coords){
3788  *         // create a <canvas> element for drawing
3789  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3790  *
3791  *         // setup tile width and height according to the options
3792  *         var size = this.getTileSize();
3793  *         tile.width = size.x;
3794  *         tile.height = size.y;
3795  *
3796  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
3797  *         var ctx = tile.getContext('2d');
3798  *
3799  *         // return the tile so it can be rendered on screen
3800  *         return tile;
3801  *     }
3802  * });
3803  * ```
3804  *
3805  * @section Asynchronous usage
3806  * @example
3807  *
3808  * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
3809  *
3810  * ```js
3811  * var CanvasLayer = L.GridLayer.extend({
3812  *     createTile: function(coords, done){
3813  *         var error;
3814  *
3815  *         // create a <canvas> element for drawing
3816  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3817  *
3818  *         // setup tile width and height according to the options
3819  *         var size = this.getTileSize();
3820  *         tile.width = size.x;
3821  *         tile.height = size.y;
3822  *
3823  *         // draw something asynchronously and pass the tile to the done() callback
3824  *         setTimeout(function() {
3825  *             done(error, tile);
3826  *         }, 1000);
3827  *
3828  *         return tile;
3829  *     }
3830  * });
3831  * ```
3832  *
3833  * @section
3834  */
3835
3836
3837 L.GridLayer = L.Layer.extend({
3838
3839         // @section
3840         // @aka GridLayer options
3841         options: {
3842                 // @option tileSize: Number|Point = 256
3843                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
3844                 tileSize: 256,
3845
3846                 // @option opacity: Number = 1.0
3847                 // Opacity of the tiles. Can be used in the `createTile()` function.
3848                 opacity: 1,
3849
3850                 // @option updateWhenIdle: Boolean = depends
3851                 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
3852                 updateWhenIdle: L.Browser.mobile,
3853
3854                 // @option updateWhenZooming: Boolean = true
3855                 // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
3856                 updateWhenZooming: true,
3857
3858                 // @option updateInterval: Number = 200
3859                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
3860                 updateInterval: 200,
3861
3862                 // @option attribution: String = null
3863                 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3864                 attribution: null,
3865
3866                 // @option zIndex: Number = 1
3867                 // The explicit zIndex of the tile layer.
3868                 zIndex: 1,
3869
3870                 // @option bounds: LatLngBounds = undefined
3871                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
3872                 bounds: null,
3873
3874                 // @option minZoom: Number = 0
3875                 // The minimum zoom level that tiles will be loaded at. By default the entire map.
3876                 minZoom: 0,
3877
3878                 // @option maxZoom: Number = undefined
3879                 // The maximum zoom level that tiles will be loaded at.
3880                 maxZoom: undefined,
3881
3882                 // @option noWrap: Boolean = false
3883                 // Whether the layer is wrapped around the antimeridian. If `true`, the
3884                 // GridLayer will only be displayed once at low zoom levels. Has no
3885                 // effect when the [map CRS](#map-crs) doesn't wrap around.
3886                 noWrap: false,
3887
3888                 // @option pane: String = 'tilePane'
3889                 // `Map pane` where the grid layer will be added.
3890                 pane: 'tilePane',
3891
3892                 // @option className: String = ''
3893                 // A custom class name to assign to the tile layer. Empty by default.
3894                 className: '',
3895
3896                 // @option keepBuffer: Number = 2
3897                 // When panning the map, keep this many rows and columns of tiles before unloading them.
3898                 keepBuffer: 2
3899         },
3900
3901         initialize: function (options) {
3902                 L.setOptions(this, options);
3903         },
3904
3905         onAdd: function () {
3906                 this._initContainer();
3907
3908                 this._levels = {};
3909                 this._tiles = {};
3910
3911                 this._resetView();
3912                 this._update();
3913         },
3914
3915         beforeAdd: function (map) {
3916                 map._addZoomLimit(this);
3917         },
3918
3919         onRemove: function (map) {
3920                 this._removeAllTiles();
3921                 L.DomUtil.remove(this._container);
3922                 map._removeZoomLimit(this);
3923                 this._container = null;
3924                 this._tileZoom = null;
3925         },
3926
3927         // @method bringToFront: this
3928         // Brings the tile layer to the top of all tile layers.
3929         bringToFront: function () {
3930                 if (this._map) {
3931                         L.DomUtil.toFront(this._container);
3932                         this._setAutoZIndex(Math.max);
3933                 }
3934                 return this;
3935         },
3936
3937         // @method bringToBack: this
3938         // Brings the tile layer to the bottom of all tile layers.
3939         bringToBack: function () {
3940                 if (this._map) {
3941                         L.DomUtil.toBack(this._container);
3942                         this._setAutoZIndex(Math.min);
3943                 }
3944                 return this;
3945         },
3946
3947         // @method getAttribution: String
3948         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3949         getAttribution: function () {
3950                 return this.options.attribution;
3951         },
3952
3953         // @method getContainer: HTMLElement
3954         // Returns the HTML element that contains the tiles for this layer.
3955         getContainer: function () {
3956                 return this._container;
3957         },
3958
3959         // @method setOpacity(opacity: Number): this
3960         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
3961         setOpacity: function (opacity) {
3962                 this.options.opacity = opacity;
3963                 this._updateOpacity();
3964                 return this;
3965         },
3966
3967         // @method setZIndex(zIndex: Number): this
3968         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
3969         setZIndex: function (zIndex) {
3970                 this.options.zIndex = zIndex;
3971                 this._updateZIndex();
3972
3973                 return this;
3974         },
3975
3976         // @method isLoading: Boolean
3977         // Returns `true` if any tile in the grid layer has not finished loading.
3978         isLoading: function () {
3979                 return this._loading;
3980         },
3981
3982         // @method redraw: this
3983         // Causes the layer to clear all the tiles and request them again.
3984         redraw: function () {
3985                 if (this._map) {
3986                         this._removeAllTiles();
3987                         this._update();
3988                 }
3989                 return this;
3990         },
3991
3992         getEvents: function () {
3993                 var events = {
3994                         viewprereset: this._invalidateAll,
3995                         viewreset: this._resetView,
3996                         zoom: this._resetView,
3997                         moveend: this._onMoveEnd
3998                 };
3999
4000                 if (!this.options.updateWhenIdle) {
4001                         // update tiles on move, but not more often than once per given interval
4002                         if (!this._onMove) {
4003                                 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4004                         }
4005
4006                         events.move = this._onMove;
4007                 }
4008
4009                 if (this._zoomAnimated) {
4010                         events.zoomanim = this._animateZoom;
4011                 }
4012
4013                 return events;
4014         },
4015
4016         // @section Extension methods
4017         // Layers extending `GridLayer` shall reimplement the following method.
4018         // @method createTile(coords: Object, done?: Function): HTMLElement
4019         // Called only internally, must be overriden by classes extending `GridLayer`.
4020         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4021         // is specified, it must be called when the tile has finished loading and drawing.
4022         createTile: function () {
4023                 return document.createElement('div');
4024         },
4025
4026         // @section
4027         // @method getTileSize: Point
4028         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4029         getTileSize: function () {
4030                 var s = this.options.tileSize;
4031                 return s instanceof L.Point ? s : new L.Point(s, s);
4032         },
4033
4034         _updateZIndex: function () {
4035                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4036                         this._container.style.zIndex = this.options.zIndex;
4037                 }
4038         },
4039
4040         _setAutoZIndex: function (compare) {
4041                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4042
4043                 var layers = this.getPane().children,
4044                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4045
4046                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4047
4048                         zIndex = layers[i].style.zIndex;
4049
4050                         if (layers[i] !== this._container && zIndex) {
4051                                 edgeZIndex = compare(edgeZIndex, +zIndex);
4052                         }
4053                 }
4054
4055                 if (isFinite(edgeZIndex)) {
4056                         this.options.zIndex = edgeZIndex + compare(-1, 1);
4057                         this._updateZIndex();
4058                 }
4059         },
4060
4061         _updateOpacity: function () {
4062                 if (!this._map) { return; }
4063
4064                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4065                 if (L.Browser.ielt9) { return; }
4066
4067                 L.DomUtil.setOpacity(this._container, this.options.opacity);
4068
4069                 var now = +new Date(),
4070                     nextFrame = false,
4071                     willPrune = false;
4072
4073                 for (var key in this._tiles) {
4074                         var tile = this._tiles[key];
4075                         if (!tile.current || !tile.loaded) { continue; }
4076
4077                         var fade = Math.min(1, (now - tile.loaded) / 200);
4078
4079                         L.DomUtil.setOpacity(tile.el, fade);
4080                         if (fade < 1) {
4081                                 nextFrame = true;
4082                         } else {
4083                                 if (tile.active) { willPrune = true; }
4084                                 tile.active = true;
4085                         }
4086                 }
4087
4088                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
4089
4090                 if (nextFrame) {
4091                         L.Util.cancelAnimFrame(this._fadeFrame);
4092                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4093                 }
4094         },
4095
4096         _initContainer: function () {
4097                 if (this._container) { return; }
4098
4099                 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4100                 this._updateZIndex();
4101
4102                 if (this.options.opacity < 1) {
4103                         this._updateOpacity();
4104                 }
4105
4106                 this.getPane().appendChild(this._container);
4107         },
4108
4109         _updateLevels: function () {
4110
4111                 var zoom = this._tileZoom,
4112                     maxZoom = this.options.maxZoom;
4113
4114                 if (zoom === undefined) { return undefined; }
4115
4116                 for (var z in this._levels) {
4117                         if (this._levels[z].el.children.length || z === zoom) {
4118                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4119                         } else {
4120                                 L.DomUtil.remove(this._levels[z].el);
4121                                 this._removeTilesAtZoom(z);
4122                                 delete this._levels[z];
4123                         }
4124                 }
4125
4126                 var level = this._levels[zoom],
4127                     map = this._map;
4128
4129                 if (!level) {
4130                         level = this._levels[zoom] = {};
4131
4132                         level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4133                         level.el.style.zIndex = maxZoom;
4134
4135                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
4136                         level.zoom = zoom;
4137
4138                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
4139
4140                         // force the browser to consider the newly added element for transition
4141                         L.Util.falseFn(level.el.offsetWidth);
4142                 }
4143
4144                 this._level = level;
4145
4146                 return level;
4147         },
4148
4149         _pruneTiles: function () {
4150                 if (!this._map) {
4151                         return;
4152                 }
4153
4154                 var key, tile;
4155
4156                 var zoom = this._map.getZoom();
4157                 if (zoom > this.options.maxZoom ||
4158                         zoom < this.options.minZoom) {
4159                         this._removeAllTiles();
4160                         return;
4161                 }
4162
4163                 for (key in this._tiles) {
4164                         tile = this._tiles[key];
4165                         tile.retain = tile.current;
4166                 }
4167
4168                 for (key in this._tiles) {
4169                         tile = this._tiles[key];
4170                         if (tile.current && !tile.active) {
4171                                 var coords = tile.coords;
4172                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
4173                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
4174                                 }
4175                         }
4176                 }
4177
4178                 for (key in this._tiles) {
4179                         if (!this._tiles[key].retain) {
4180                                 this._removeTile(key);
4181                         }
4182                 }
4183         },
4184
4185         _removeTilesAtZoom: function (zoom) {
4186                 for (var key in this._tiles) {
4187                         if (this._tiles[key].coords.z !== zoom) {
4188                                 continue;
4189                         }
4190                         this._removeTile(key);
4191                 }
4192         },
4193
4194         _removeAllTiles: function () {
4195                 for (var key in this._tiles) {
4196                         this._removeTile(key);
4197                 }
4198         },
4199
4200         _invalidateAll: function () {
4201                 for (var z in this._levels) {
4202                         L.DomUtil.remove(this._levels[z].el);
4203                         delete this._levels[z];
4204                 }
4205                 this._removeAllTiles();
4206
4207                 this._tileZoom = null;
4208         },
4209
4210         _retainParent: function (x, y, z, minZoom) {
4211                 var x2 = Math.floor(x / 2),
4212                     y2 = Math.floor(y / 2),
4213                     z2 = z - 1,
4214                     coords2 = new L.Point(+x2, +y2);
4215                 coords2.z = +z2;
4216
4217                 var key = this._tileCoordsToKey(coords2),
4218                     tile = this._tiles[key];
4219
4220                 if (tile && tile.active) {
4221                         tile.retain = true;
4222                         return true;
4223
4224                 } else if (tile && tile.loaded) {
4225                         tile.retain = true;
4226                 }
4227
4228                 if (z2 > minZoom) {
4229                         return this._retainParent(x2, y2, z2, minZoom);
4230                 }
4231
4232                 return false;
4233         },
4234
4235         _retainChildren: function (x, y, z, maxZoom) {
4236
4237                 for (var i = 2 * x; i < 2 * x + 2; i++) {
4238                         for (var j = 2 * y; j < 2 * y + 2; j++) {
4239
4240                                 var coords = new L.Point(i, j);
4241                                 coords.z = z + 1;
4242
4243                                 var key = this._tileCoordsToKey(coords),
4244                                     tile = this._tiles[key];
4245
4246                                 if (tile && tile.active) {
4247                                         tile.retain = true;
4248                                         continue;
4249
4250                                 } else if (tile && tile.loaded) {
4251                                         tile.retain = true;
4252                                 }
4253
4254                                 if (z + 1 < maxZoom) {
4255                                         this._retainChildren(i, j, z + 1, maxZoom);
4256                                 }
4257                         }
4258                 }
4259         },
4260
4261         _resetView: function (e) {
4262                 var animating = e && (e.pinch || e.flyTo);
4263                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
4264         },
4265
4266         _animateZoom: function (e) {
4267                 this._setView(e.center, e.zoom, true, e.noUpdate);
4268         },
4269
4270         _setView: function (center, zoom, noPrune, noUpdate) {
4271                 var tileZoom = Math.round(zoom);
4272                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
4273                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
4274                         tileZoom = undefined;
4275                 }
4276
4277                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
4278
4279                 if (!noUpdate || tileZoomChanged) {
4280
4281                         this._tileZoom = tileZoom;
4282
4283                         if (this._abortLoading) {
4284                                 this._abortLoading();
4285                         }
4286
4287                         this._updateLevels();
4288                         this._resetGrid();
4289
4290                         if (tileZoom !== undefined) {
4291                                 this._update(center);
4292                         }
4293
4294                         if (!noPrune) {
4295                                 this._pruneTiles();
4296                         }
4297
4298                         // Flag to prevent _updateOpacity from pruning tiles during
4299                         // a zoom anim or a pinch gesture
4300                         this._noPrune = !!noPrune;
4301                 }
4302
4303                 this._setZoomTransforms(center, zoom);
4304         },
4305
4306         _setZoomTransforms: function (center, zoom) {
4307                 for (var i in this._levels) {
4308                         this._setZoomTransform(this._levels[i], center, zoom);
4309                 }
4310         },
4311
4312         _setZoomTransform: function (level, center, zoom) {
4313                 var scale = this._map.getZoomScale(zoom, level.zoom),
4314                     translate = level.origin.multiplyBy(scale)
4315                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
4316
4317                 if (L.Browser.any3d) {
4318                         L.DomUtil.setTransform(level.el, translate, scale);
4319                 } else {
4320                         L.DomUtil.setPosition(level.el, translate);
4321                 }
4322         },
4323
4324         _resetGrid: function () {
4325                 var map = this._map,
4326                     crs = map.options.crs,
4327                     tileSize = this._tileSize = this.getTileSize(),
4328                     tileZoom = this._tileZoom;
4329
4330                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
4331                 if (bounds) {
4332                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
4333                 }
4334
4335                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
4336                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
4337                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
4338                 ];
4339                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
4340                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
4341                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
4342                 ];
4343         },
4344
4345         _onMoveEnd: function () {
4346                 if (!this._map || this._map._animatingZoom) { return; }
4347
4348                 this._update();
4349         },
4350
4351         _getTiledPixelBounds: function (center) {
4352                 var map = this._map,
4353                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
4354                     scale = map.getZoomScale(mapZoom, this._tileZoom),
4355                     pixelCenter = map.project(center, this._tileZoom).floor(),
4356                     halfSize = map.getSize().divideBy(scale * 2);
4357
4358                 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
4359         },
4360
4361         // Private method to load tiles in the grid's active zoom level according to map bounds
4362         _update: function (center) {
4363                 var map = this._map;
4364                 if (!map) { return; }
4365                 var zoom = map.getZoom();
4366
4367                 if (center === undefined) { center = map.getCenter(); }
4368                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
4369
4370                 var pixelBounds = this._getTiledPixelBounds(center),
4371                     tileRange = this._pxBoundsToTileRange(pixelBounds),
4372                     tileCenter = tileRange.getCenter(),
4373                     queue = [],
4374                     margin = this.options.keepBuffer,
4375                     noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
4376                                               tileRange.getTopRight().add([margin, -margin]));
4377
4378                 for (var key in this._tiles) {
4379                         var c = this._tiles[key].coords;
4380                         if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
4381                                 this._tiles[key].current = false;
4382                         }
4383                 }
4384
4385                 // _update just loads more tiles. If the tile zoom level differs too much
4386                 // from the map's, let _setView reset levels and prune old tiles.
4387                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
4388
4389                 // create a queue of coordinates to load tiles from
4390                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
4391                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
4392                                 var coords = new L.Point(i, j);
4393                                 coords.z = this._tileZoom;
4394
4395                                 if (!this._isValidTile(coords)) { continue; }
4396
4397                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
4398                                 if (tile) {
4399                                         tile.current = true;
4400                                 } else {
4401                                         queue.push(coords);
4402                                 }
4403                         }
4404                 }
4405
4406                 // sort tile queue to load tiles in order of their distance to center
4407                 queue.sort(function (a, b) {
4408                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
4409                 });
4410
4411                 if (queue.length !== 0) {
4412                         // if it's the first batch of tiles to load
4413                         if (!this._loading) {
4414                                 this._loading = true;
4415                                 // @event loading: Event
4416                                 // Fired when the grid layer starts loading tiles.
4417                                 this.fire('loading');
4418                         }
4419
4420                         // create DOM fragment to append tiles in one batch
4421                         var fragment = document.createDocumentFragment();
4422
4423                         for (i = 0; i < queue.length; i++) {
4424                                 this._addTile(queue[i], fragment);
4425                         }
4426
4427                         this._level.el.appendChild(fragment);
4428                 }
4429         },
4430
4431         _isValidTile: function (coords) {
4432                 var crs = this._map.options.crs;
4433
4434                 if (!crs.infinite) {
4435                         // don't load tile if it's out of bounds and not wrapped
4436                         var bounds = this._globalTileRange;
4437                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
4438                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
4439                 }
4440
4441                 if (!this.options.bounds) { return true; }
4442
4443                 // don't load tile if it doesn't intersect the bounds in options
4444                 var tileBounds = this._tileCoordsToBounds(coords);
4445                 return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
4446         },
4447
4448         _keyToBounds: function (key) {
4449                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
4450         },
4451
4452         // converts tile coordinates to its geographical bounds
4453         _tileCoordsToBounds: function (coords) {
4454
4455                 var map = this._map,
4456                     tileSize = this.getTileSize(),
4457
4458                     nwPoint = coords.scaleBy(tileSize),
4459                     sePoint = nwPoint.add(tileSize),
4460
4461                     nw = map.unproject(nwPoint, coords.z),
4462                     se = map.unproject(sePoint, coords.z);
4463
4464                 if (!this.options.noWrap) {
4465                         nw = map.wrapLatLng(nw);
4466                         se = map.wrapLatLng(se);
4467                 }
4468
4469                 return new L.LatLngBounds(nw, se);
4470         },
4471
4472         // converts tile coordinates to key for the tile cache
4473         _tileCoordsToKey: function (coords) {
4474                 return coords.x + ':' + coords.y + ':' + coords.z;
4475         },
4476
4477         // converts tile cache key to coordinates
4478         _keyToTileCoords: function (key) {
4479                 var k = key.split(':'),
4480                     coords = new L.Point(+k[0], +k[1]);
4481                 coords.z = +k[2];
4482                 return coords;
4483         },
4484
4485         _removeTile: function (key) {
4486                 var tile = this._tiles[key];
4487                 if (!tile) { return; }
4488
4489                 L.DomUtil.remove(tile.el);
4490
4491                 delete this._tiles[key];
4492
4493                 // @event tileunload: TileEvent
4494                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
4495                 this.fire('tileunload', {
4496                         tile: tile.el,
4497                         coords: this._keyToTileCoords(key)
4498                 });
4499         },
4500
4501         _initTile: function (tile) {
4502                 L.DomUtil.addClass(tile, 'leaflet-tile');
4503
4504                 var tileSize = this.getTileSize();
4505                 tile.style.width = tileSize.x + 'px';
4506                 tile.style.height = tileSize.y + 'px';
4507
4508                 tile.onselectstart = L.Util.falseFn;
4509                 tile.onmousemove = L.Util.falseFn;
4510
4511                 // update opacity on tiles in IE7-8 because of filter inheritance problems
4512                 if (L.Browser.ielt9 && this.options.opacity < 1) {
4513                         L.DomUtil.setOpacity(tile, this.options.opacity);
4514                 }
4515
4516                 // without this hack, tiles disappear after zoom on Chrome for Android
4517                 // https://github.com/Leaflet/Leaflet/issues/2078
4518                 if (L.Browser.android && !L.Browser.android23) {
4519                         tile.style.WebkitBackfaceVisibility = 'hidden';
4520                 }
4521         },
4522
4523         _addTile: function (coords, container) {
4524                 var tilePos = this._getTilePos(coords),
4525                     key = this._tileCoordsToKey(coords);
4526
4527                 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
4528
4529                 this._initTile(tile);
4530
4531                 // if createTile is defined with a second argument ("done" callback),
4532                 // we know that tile is async and will be ready later; otherwise
4533                 if (this.createTile.length < 2) {
4534                         // mark tile as ready, but delay one frame for opacity animation to happen
4535                         L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
4536                 }
4537
4538                 L.DomUtil.setPosition(tile, tilePos);
4539
4540                 // save tile in cache
4541                 this._tiles[key] = {
4542                         el: tile,
4543                         coords: coords,
4544                         current: true
4545                 };
4546
4547                 container.appendChild(tile);
4548                 // @event tileloadstart: TileEvent
4549                 // Fired when a tile is requested and starts loading.
4550                 this.fire('tileloadstart', {
4551                         tile: tile,
4552                         coords: coords
4553                 });
4554         },
4555
4556         _tileReady: function (coords, err, tile) {
4557                 if (!this._map) { return; }
4558
4559                 if (err) {
4560                         // @event tileerror: TileErrorEvent
4561                         // Fired when there is an error loading a tile.
4562                         this.fire('tileerror', {
4563                                 error: err,
4564                                 tile: tile,
4565                                 coords: coords
4566                         });
4567                 }
4568
4569                 var key = this._tileCoordsToKey(coords);
4570
4571                 tile = this._tiles[key];
4572                 if (!tile) { return; }
4573
4574                 tile.loaded = +new Date();
4575                 if (this._map._fadeAnimated) {
4576                         L.DomUtil.setOpacity(tile.el, 0);
4577                         L.Util.cancelAnimFrame(this._fadeFrame);
4578                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4579                 } else {
4580                         tile.active = true;
4581                         this._pruneTiles();
4582                 }
4583
4584                 if (!err) {
4585                         L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
4586
4587                         // @event tileload: TileEvent
4588                         // Fired when a tile loads.
4589                         this.fire('tileload', {
4590                                 tile: tile.el,
4591                                 coords: coords
4592                         });
4593                 }
4594
4595                 if (this._noTilesToLoad()) {
4596                         this._loading = false;
4597                         // @event load: Event
4598                         // Fired when the grid layer loaded all visible tiles.
4599                         this.fire('load');
4600
4601                         if (L.Browser.ielt9 || !this._map._fadeAnimated) {
4602                                 L.Util.requestAnimFrame(this._pruneTiles, this);
4603                         } else {
4604                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
4605                                 // to trigger a pruning.
4606                                 setTimeout(L.bind(this._pruneTiles, this), 250);
4607                         }
4608                 }
4609         },
4610
4611         _getTilePos: function (coords) {
4612                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
4613         },
4614
4615         _wrapCoords: function (coords) {
4616                 var newCoords = new L.Point(
4617                         this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
4618                         this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
4619                 newCoords.z = coords.z;
4620                 return newCoords;
4621         },
4622
4623         _pxBoundsToTileRange: function (bounds) {
4624                 var tileSize = this.getTileSize();
4625                 return new L.Bounds(
4626                         bounds.min.unscaleBy(tileSize).floor(),
4627                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
4628         },
4629
4630         _noTilesToLoad: function () {
4631                 for (var key in this._tiles) {
4632                         if (!this._tiles[key].loaded) { return false; }
4633                 }
4634                 return true;
4635         }
4636 });
4637
4638 // @factory L.gridLayer(options?: GridLayer options)
4639 // Creates a new instance of GridLayer with the supplied options.
4640 L.gridLayer = function (options) {
4641         return new L.GridLayer(options);
4642 };
4643
4644
4645
4646 /*
4647  * @class TileLayer
4648  * @inherits GridLayer
4649  * @aka L.TileLayer
4650  * Used to load and display tile layers on the map. Extends `GridLayer`.
4651  *
4652  * @example
4653  *
4654  * ```js
4655  * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
4656  * ```
4657  *
4658  * @section URL template
4659  * @example
4660  *
4661  * A string of the following form:
4662  *
4663  * ```
4664  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
4665  * ```
4666  *
4667  * `{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.
4668  *
4669  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
4670  *
4671  * ```
4672  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
4673  * ```
4674  */
4675
4676
4677 L.TileLayer = L.GridLayer.extend({
4678
4679         // @section
4680         // @aka TileLayer options
4681         options: {
4682                 // @option minZoom: Number = 0
4683                 // Minimum zoom number.
4684                 minZoom: 0,
4685
4686                 // @option maxZoom: Number = 18
4687                 // Maximum zoom number.
4688                 maxZoom: 18,
4689
4690                 // @option maxNativeZoom: Number = null
4691                 // Maximum zoom number the tile source has available. If it is specified,
4692                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
4693                 // from `maxNativeZoom` level and auto-scaled.
4694                 maxNativeZoom: null,
4695
4696                 // @option subdomains: String|String[] = 'abc'
4697                 // 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.
4698                 subdomains: 'abc',
4699
4700                 // @option errorTileUrl: String = ''
4701                 // URL to the tile image to show in place of the tile that failed to load.
4702                 errorTileUrl: '',
4703
4704                 // @option zoomOffset: Number = 0
4705                 // The zoom number used in tile URLs will be offset with this value.
4706                 zoomOffset: 0,
4707
4708                 // @option tms: Boolean = false
4709                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
4710                 tms: false,
4711
4712                 // @option zoomReverse: Boolean = false
4713                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
4714                 zoomReverse: false,
4715
4716                 // @option detectRetina: Boolean = false
4717                 // 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.
4718                 detectRetina: false,
4719
4720                 // @option crossOrigin: Boolean = false
4721                 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
4722                 crossOrigin: false
4723         },
4724
4725         initialize: function (url, options) {
4726
4727                 this._url = url;
4728
4729                 options = L.setOptions(this, options);
4730
4731                 // detecting retina displays, adjusting tileSize and zoom levels
4732                 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
4733
4734                         options.tileSize = Math.floor(options.tileSize / 2);
4735
4736                         if (!options.zoomReverse) {
4737                                 options.zoomOffset++;
4738                                 options.maxZoom--;
4739                         } else {
4740                                 options.zoomOffset--;
4741                                 options.minZoom++;
4742                         }
4743
4744                         options.minZoom = Math.max(0, options.minZoom);
4745                 }
4746
4747                 if (typeof options.subdomains === 'string') {
4748                         options.subdomains = options.subdomains.split('');
4749                 }
4750
4751                 // for https://github.com/Leaflet/Leaflet/issues/137
4752                 if (!L.Browser.android) {
4753                         this.on('tileunload', this._onTileRemove);
4754                 }
4755         },
4756
4757         // @method setUrl(url: String, noRedraw?: Boolean): this
4758         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
4759         setUrl: function (url, noRedraw) {
4760                 this._url = url;
4761
4762                 if (!noRedraw) {
4763                         this.redraw();
4764                 }
4765                 return this;
4766         },
4767
4768         // @method createTile(coords: Object, done?: Function): HTMLElement
4769         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
4770         // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
4771         // callback is called when the tile has been loaded.
4772         createTile: function (coords, done) {
4773                 var tile = document.createElement('img');
4774
4775                 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
4776                 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
4777
4778                 if (this.options.crossOrigin) {
4779                         tile.crossOrigin = '';
4780                 }
4781
4782                 /*
4783                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
4784                  http://www.w3.org/TR/WCAG20-TECHS/H67
4785                 */
4786                 tile.alt = '';
4787
4788                 tile.src = this.getTileUrl(coords);
4789
4790                 return tile;
4791         },
4792
4793         // @section Extension methods
4794         // @uninheritable
4795         // Layers extending `TileLayer` might reimplement the following method.
4796         // @method getTileUrl(coords: Object): String
4797         // Called only internally, returns the URL for a tile given its coordinates.
4798         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
4799         getTileUrl: function (coords) {
4800                 var data = {
4801                         r: L.Browser.retina ? '@2x' : '',
4802                         s: this._getSubdomain(coords),
4803                         x: coords.x,
4804                         y: coords.y,
4805                         z: this._getZoomForUrl()
4806                 };
4807                 if (this._map && !this._map.options.crs.infinite) {
4808                         var invertedY = this._globalTileRange.max.y - coords.y;
4809                         if (this.options.tms) {
4810                                 data['y'] = invertedY;
4811                         }
4812                         data['-y'] = invertedY;
4813                 }
4814
4815                 return L.Util.template(this._url, L.extend(data, this.options));
4816         },
4817
4818         _tileOnLoad: function (done, tile) {
4819                 // For https://github.com/Leaflet/Leaflet/issues/3332
4820                 if (L.Browser.ielt9) {
4821                         setTimeout(L.bind(done, this, null, tile), 0);
4822                 } else {
4823                         done(null, tile);
4824                 }
4825         },
4826
4827         _tileOnError: function (done, tile, e) {
4828                 var errorUrl = this.options.errorTileUrl;
4829                 if (errorUrl) {
4830                         tile.src = errorUrl;
4831                 }
4832                 done(e, tile);
4833         },
4834
4835         getTileSize: function () {
4836                 var map = this._map,
4837                     tileSize = L.GridLayer.prototype.getTileSize.call(this),
4838                     zoom = this._tileZoom + this.options.zoomOffset,
4839                     zoomN = this.options.maxNativeZoom;
4840
4841                 // increase tile size when overscaling
4842                 return zoomN !== null && zoom > zoomN ?
4843                                 tileSize.divideBy(map.getZoomScale(zoomN, zoom)).round() :
4844                                 tileSize;
4845         },
4846
4847         _onTileRemove: function (e) {
4848                 e.tile.onload = null;
4849         },
4850
4851         _getZoomForUrl: function () {
4852
4853                 var options = this.options,
4854                     zoom = this._tileZoom;
4855
4856                 if (options.zoomReverse) {
4857                         zoom = options.maxZoom - zoom;
4858                 }
4859
4860                 zoom += options.zoomOffset;
4861
4862                 return options.maxNativeZoom !== null ? Math.min(zoom, options.maxNativeZoom) : zoom;
4863         },
4864
4865         _getSubdomain: function (tilePoint) {
4866                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
4867                 return this.options.subdomains[index];
4868         },
4869
4870         // stops loading all tiles in the background layer
4871         _abortLoading: function () {
4872                 var i, tile;
4873                 for (i in this._tiles) {
4874                         if (this._tiles[i].coords.z !== this._tileZoom) {
4875                                 tile = this._tiles[i].el;
4876
4877                                 tile.onload = L.Util.falseFn;
4878                                 tile.onerror = L.Util.falseFn;
4879
4880                                 if (!tile.complete) {
4881                                         tile.src = L.Util.emptyImageUrl;
4882                                         L.DomUtil.remove(tile);
4883                                 }
4884                         }
4885                 }
4886         }
4887 });
4888
4889
4890 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
4891 // Instantiates a tile layer object given a `URL template` and optionally an options object.
4892
4893 L.tileLayer = function (url, options) {
4894         return new L.TileLayer(url, options);
4895 };
4896
4897
4898
4899 /*
4900  * @class TileLayer.WMS
4901  * @inherits TileLayer
4902  * @aka L.TileLayer.WMS
4903  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
4904  *
4905  * @example
4906  *
4907  * ```js
4908  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
4909  *      layers: 'nexrad-n0r-900913',
4910  *      format: 'image/png',
4911  *      transparent: true,
4912  *      attribution: "Weather data © 2012 IEM Nexrad"
4913  * });
4914  * ```
4915  */
4916
4917 L.TileLayer.WMS = L.TileLayer.extend({
4918
4919         // @section
4920         // @aka TileLayer.WMS options
4921         // If any custom options not documented here are used, they will be sent to the
4922         // WMS server as extra parameters in each request URL. This can be useful for
4923         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
4924         defaultWmsParams: {
4925                 service: 'WMS',
4926                 request: 'GetMap',
4927
4928                 // @option layers: String = ''
4929                 // **(required)** Comma-separated list of WMS layers to show.
4930                 layers: '',
4931
4932                 // @option styles: String = ''
4933                 // Comma-separated list of WMS styles.
4934                 styles: '',
4935
4936                 // @option format: String = 'image/jpeg'
4937                 // WMS image format (use `'image/png'` for layers with transparency).
4938                 format: 'image/jpeg',
4939
4940                 // @option transparent: Boolean = false
4941                 // If `true`, the WMS service will return images with transparency.
4942                 transparent: false,
4943
4944                 // @option version: String = '1.1.1'
4945                 // Version of the WMS service to use
4946                 version: '1.1.1'
4947         },
4948
4949         options: {
4950                 // @option crs: CRS = null
4951                 // Coordinate Reference System to use for the WMS requests, defaults to
4952                 // map CRS. Don't change this if you're not sure what it means.
4953                 crs: null,
4954
4955                 // @option uppercase: Boolean = false
4956                 // If `true`, WMS request parameter keys will be uppercase.
4957                 uppercase: false
4958         },
4959
4960         initialize: function (url, options) {
4961
4962                 this._url = url;
4963
4964                 var wmsParams = L.extend({}, this.defaultWmsParams);
4965
4966                 // all keys that are not TileLayer options go to WMS params
4967                 for (var i in options) {
4968                         if (!(i in this.options)) {
4969                                 wmsParams[i] = options[i];
4970                         }
4971                 }
4972
4973                 options = L.setOptions(this, options);
4974
4975                 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
4976
4977                 this.wmsParams = wmsParams;
4978         },
4979
4980         onAdd: function (map) {
4981
4982                 this._crs = this.options.crs || map.options.crs;
4983                 this._wmsVersion = parseFloat(this.wmsParams.version);
4984
4985                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
4986                 this.wmsParams[projectionKey] = this._crs.code;
4987
4988                 L.TileLayer.prototype.onAdd.call(this, map);
4989         },
4990
4991         getTileUrl: function (coords) {
4992
4993                 var tileBounds = this._tileCoordsToBounds(coords),
4994                     nw = this._crs.project(tileBounds.getNorthWest()),
4995                     se = this._crs.project(tileBounds.getSouthEast()),
4996
4997                     bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
4998                             [se.y, nw.x, nw.y, se.x] :
4999                             [nw.x, se.y, se.x, nw.y]).join(','),
5000
5001                     url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5002
5003                 return url +
5004                         L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5005                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5006         },
5007
5008         // @method setParams(params: Object, noRedraw?: Boolean): this
5009         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5010         setParams: function (params, noRedraw) {
5011
5012                 L.extend(this.wmsParams, params);
5013
5014                 if (!noRedraw) {
5015                         this.redraw();
5016                 }
5017
5018                 return this;
5019         }
5020 });
5021
5022
5023 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5024 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5025 L.tileLayer.wms = function (url, options) {
5026         return new L.TileLayer.WMS(url, options);
5027 };
5028
5029
5030
5031 /*
5032  * @class ImageOverlay
5033  * @aka L.ImageOverlay
5034  * @inherits Interactive layer
5035  *
5036  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5037  *
5038  * @example
5039  *
5040  * ```js
5041  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5042  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5043  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5044  * ```
5045  */
5046
5047 L.ImageOverlay = L.Layer.extend({
5048
5049         // @section
5050         // @aka ImageOverlay options
5051         options: {
5052                 // @option opacity: Number = 1.0
5053                 // The opacity of the image overlay.
5054                 opacity: 1,
5055
5056                 // @option alt: String = ''
5057                 // Text for the `alt` attribute of the image (useful for accessibility).
5058                 alt: '',
5059
5060                 // @option interactive: Boolean = false
5061                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5062                 interactive: false,
5063
5064                 // @option attribution: String = null
5065                 // An optional string containing HTML to be shown on the `Attribution control`
5066                 attribution: null,
5067
5068                 // @option crossOrigin: Boolean = false
5069                 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5070                 crossOrigin: false
5071         },
5072
5073         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5074                 this._url = url;
5075                 this._bounds = L.latLngBounds(bounds);
5076
5077                 L.setOptions(this, options);
5078         },
5079
5080         onAdd: function () {
5081                 if (!this._image) {
5082                         this._initImage();
5083
5084                         if (this.options.opacity < 1) {
5085                                 this._updateOpacity();
5086                         }
5087                 }
5088
5089                 if (this.options.interactive) {
5090                         L.DomUtil.addClass(this._image, 'leaflet-interactive');
5091                         this.addInteractiveTarget(this._image);
5092                 }
5093
5094                 this.getPane().appendChild(this._image);
5095                 this._reset();
5096         },
5097
5098         onRemove: function () {
5099                 L.DomUtil.remove(this._image);
5100                 if (this.options.interactive) {
5101                         this.removeInteractiveTarget(this._image);
5102                 }
5103         },
5104
5105         // @method setOpacity(opacity: Number): this
5106         // Sets the opacity of the overlay.
5107         setOpacity: function (opacity) {
5108                 this.options.opacity = opacity;
5109
5110                 if (this._image) {
5111                         this._updateOpacity();
5112                 }
5113                 return this;
5114         },
5115
5116         setStyle: function (styleOpts) {
5117                 if (styleOpts.opacity) {
5118                         this.setOpacity(styleOpts.opacity);
5119                 }
5120                 return this;
5121         },
5122
5123         // @method bringToFront(): this
5124         // Brings the layer to the top of all overlays.
5125         bringToFront: function () {
5126                 if (this._map) {
5127                         L.DomUtil.toFront(this._image);
5128                 }
5129                 return this;
5130         },
5131
5132         // @method bringToBack(): this
5133         // Brings the layer to the bottom of all overlays.
5134         bringToBack: function () {
5135                 if (this._map) {
5136                         L.DomUtil.toBack(this._image);
5137                 }
5138                 return this;
5139         },
5140
5141         // @method setUrl(url: String): this
5142         // Changes the URL of the image.
5143         setUrl: function (url) {
5144                 this._url = url;
5145
5146                 if (this._image) {
5147                         this._image.src = url;
5148                 }
5149                 return this;
5150         },
5151
5152         setBounds: function (bounds) {
5153                 this._bounds = bounds;
5154
5155                 if (this._map) {
5156                         this._reset();
5157                 }
5158                 return this;
5159         },
5160
5161         getAttribution: function () {
5162                 return this.options.attribution;
5163         },
5164
5165         getEvents: function () {
5166                 var events = {
5167                         zoom: this._reset,
5168                         viewreset: this._reset
5169                 };
5170
5171                 if (this._zoomAnimated) {
5172                         events.zoomanim = this._animateZoom;
5173                 }
5174
5175                 return events;
5176         },
5177
5178         getBounds: function () {
5179                 return this._bounds;
5180         },
5181
5182         getElement: function () {
5183                 return this._image;
5184         },
5185
5186         _initImage: function () {
5187                 var img = this._image = L.DomUtil.create('img',
5188                                 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
5189
5190                 img.onselectstart = L.Util.falseFn;
5191                 img.onmousemove = L.Util.falseFn;
5192
5193                 img.onload = L.bind(this.fire, this, 'load');
5194
5195                 if (this.options.crossOrigin) {
5196                         img.crossOrigin = '';
5197                 }
5198
5199                 img.src = this._url;
5200                 img.alt = this.options.alt;
5201         },
5202
5203         _animateZoom: function (e) {
5204                 var scale = this._map.getZoomScale(e.zoom),
5205                     offset = this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), e.zoom, e.center);
5206
5207                 L.DomUtil.setTransform(this._image, offset, scale);
5208         },
5209
5210         _reset: function () {
5211                 var image = this._image,
5212                     bounds = new L.Bounds(
5213                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
5214                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
5215                     size = bounds.getSize();
5216
5217                 L.DomUtil.setPosition(image, bounds.min);
5218
5219                 image.style.width  = size.x + 'px';
5220                 image.style.height = size.y + 'px';
5221         },
5222
5223         _updateOpacity: function () {
5224                 L.DomUtil.setOpacity(this._image, this.options.opacity);
5225         }
5226 });
5227
5228 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
5229 // Instantiates an image overlay object given the URL of the image and the
5230 // geographical bounds it is tied to.
5231 L.imageOverlay = function (url, bounds, options) {
5232         return new L.ImageOverlay(url, bounds, options);
5233 };
5234
5235
5236
5237 /*
5238  * @class Icon
5239  * @aka L.Icon
5240  * @inherits Layer
5241  *
5242  * Represents an icon to provide when creating a marker.
5243  *
5244  * @example
5245  *
5246  * ```js
5247  * var myIcon = L.icon({
5248  *     iconUrl: 'my-icon.png',
5249  *     iconRetinaUrl: 'my-icon@2x.png',
5250  *     iconSize: [38, 95],
5251  *     iconAnchor: [22, 94],
5252  *     popupAnchor: [-3, -76],
5253  *     shadowUrl: 'my-icon-shadow.png',
5254  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
5255  *     shadowSize: [68, 95],
5256  *     shadowAnchor: [22, 94]
5257  * });
5258  *
5259  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5260  * ```
5261  *
5262  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
5263  *
5264  */
5265
5266 L.Icon = L.Class.extend({
5267
5268         /* @section
5269          * @aka Icon options
5270          *
5271          * @option iconUrl: String = null
5272          * **(required)** The URL to the icon image (absolute or relative to your script path).
5273          *
5274          * @option iconRetinaUrl: String = null
5275          * The URL to a retina sized version of the icon image (absolute or relative to your
5276          * script path). Used for Retina screen devices.
5277          *
5278          * @option iconSize: Point = null
5279          * Size of the icon image in pixels.
5280          *
5281          * @option iconAnchor: Point = null
5282          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
5283          * will be aligned so that this point is at the marker's geographical location. Centered
5284          * by default if size is specified, also can be set in CSS with negative margins.
5285          *
5286          * @option popupAnchor: Point = null
5287          * The coordinates of the point from which popups will "open", relative to the icon anchor.
5288          *
5289          * @option shadowUrl: String = null
5290          * The URL to the icon shadow image. If not specified, no shadow image will be created.
5291          *
5292          * @option shadowRetinaUrl: String = null
5293          *
5294          * @option shadowSize: Point = null
5295          * Size of the shadow image in pixels.
5296          *
5297          * @option shadowAnchor: Point = null
5298          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
5299          * as iconAnchor if not specified).
5300          *
5301          * @option className: String = ''
5302          * A custom class name to assign to both icon and shadow images. Empty by default.
5303          */
5304
5305         initialize: function (options) {
5306                 L.setOptions(this, options);
5307         },
5308
5309         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
5310         // Called internally when the icon has to be shown, returns a `<img>` HTML element
5311         // styled according to the options.
5312         createIcon: function (oldIcon) {
5313                 return this._createIcon('icon', oldIcon);
5314         },
5315
5316         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
5317         // As `createIcon`, but for the shadow beneath it.
5318         createShadow: function (oldIcon) {
5319                 return this._createIcon('shadow', oldIcon);
5320         },
5321
5322         _createIcon: function (name, oldIcon) {
5323                 var src = this._getIconUrl(name);
5324
5325                 if (!src) {
5326                         if (name === 'icon') {
5327                                 throw new Error('iconUrl not set in Icon options (see the docs).');
5328                         }
5329                         return null;
5330                 }
5331
5332                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
5333                 this._setIconStyles(img, name);
5334
5335                 return img;
5336         },
5337
5338         _setIconStyles: function (img, name) {
5339                 var options = this.options;
5340                 var sizeOption = options[name + 'Size'];
5341
5342                 if (typeof sizeOption === 'number') {
5343                         sizeOption = [sizeOption, sizeOption];
5344                 }
5345
5346                 var size = L.point(sizeOption),
5347                     anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
5348                             size && size.divideBy(2, true));
5349
5350                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
5351
5352                 if (anchor) {
5353                         img.style.marginLeft = (-anchor.x) + 'px';
5354                         img.style.marginTop  = (-anchor.y) + 'px';
5355                 }
5356
5357                 if (size) {
5358                         img.style.width  = size.x + 'px';
5359                         img.style.height = size.y + 'px';
5360                 }
5361         },
5362
5363         _createImg: function (src, el) {
5364                 el = el || document.createElement('img');
5365                 el.src = src;
5366                 return el;
5367         },
5368
5369         _getIconUrl: function (name) {
5370                 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
5371         }
5372 });
5373
5374
5375 // @factory L.icon(options: Icon options)
5376 // Creates an icon instance with the given options.
5377 L.icon = function (options) {
5378         return new L.Icon(options);
5379 };
5380
5381
5382
5383 /*
5384  * @miniclass Icon.Default (Icon)
5385  * @aka L.Icon.Default
5386  * @section
5387  *
5388  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
5389  * no icon is specified. Points to the blue marker image distributed with Leaflet
5390  * releases.
5391  *
5392  * In order to change the default icon, just change the properties of `L.Icon.Default.prototype.options`
5393  * (which is a set of `Icon options`).
5394  */
5395
5396 L.Icon.Default = L.Icon.extend({
5397
5398         options: {
5399                 iconUrl:       'marker-icon.png',
5400                 iconRetinaUrl: 'marker-icon-2x.png',
5401                 shadowUrl:     'marker-shadow.png',
5402                 iconSize:    [25, 41],
5403                 iconAnchor:  [12, 41],
5404                 popupAnchor: [1, -34],
5405                 tooltipAnchor: [16, -28],
5406                 shadowSize:  [41, 41]
5407         },
5408
5409         _getIconUrl: function (name) {
5410                 if (!L.Icon.Default.imagePath) {        // Deprecated, backwards-compatibility only
5411                         L.Icon.Default.imagePath = this._detectIconPath();
5412                 }
5413
5414                 // @option imagePath: String
5415                 // `L.Icon.Default` will try to auto-detect the absolute location of the
5416                 // blue icon images. If you are placing these images in a non-standard
5417                 // way, set this option to point to the right absolute path.
5418                 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
5419         },
5420
5421         _detectIconPath: function () {
5422                 var el = L.DomUtil.create('div',  'leaflet-default-icon-path', document.body);
5423                 var path = L.DomUtil.getStyle(el, 'background-image') ||
5424                            L.DomUtil.getStyle(el, 'backgroundImage');   // IE8
5425
5426                 document.body.removeChild(el);
5427
5428                 return path.indexOf('url') === 0 ?
5429                         path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
5430         }
5431 });
5432
5433
5434
5435 /*
5436  * @class Marker
5437  * @inherits Interactive layer
5438  * @aka L.Marker
5439  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
5440  *
5441  * @example
5442  *
5443  * ```js
5444  * L.marker([50.5, 30.5]).addTo(map);
5445  * ```
5446  */
5447
5448 L.Marker = L.Layer.extend({
5449
5450         // @section
5451         // @aka Marker options
5452         options: {
5453                 // @option icon: Icon = *
5454                 // 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.
5455                 icon: new L.Icon.Default(),
5456
5457                 // Option inherited from "Interactive layer" abstract class
5458                 interactive: true,
5459
5460                 // @option draggable: Boolean = false
5461                 // Whether the marker is draggable with mouse/touch or not.
5462                 draggable: false,
5463
5464                 // @option keyboard: Boolean = true
5465                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
5466                 keyboard: true,
5467
5468                 // @option title: String = ''
5469                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
5470                 title: '',
5471
5472                 // @option alt: String = ''
5473                 // Text for the `alt` attribute of the icon image (useful for accessibility).
5474                 alt: '',
5475
5476                 // @option zIndexOffset: Number = 0
5477                 // 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).
5478                 zIndexOffset: 0,
5479
5480                 // @option opacity: Number = 1.0
5481                 // The opacity of the marker.
5482                 opacity: 1,
5483
5484                 // @option riseOnHover: Boolean = false
5485                 // If `true`, the marker will get on top of others when you hover the mouse over it.
5486                 riseOnHover: false,
5487
5488                 // @option riseOffset: Number = 250
5489                 // The z-index offset used for the `riseOnHover` feature.
5490                 riseOffset: 250,
5491
5492                 // @option pane: String = 'markerPane'
5493                 // `Map pane` where the markers icon will be added.
5494                 pane: 'markerPane',
5495
5496                 // FIXME: shadowPane is no longer a valid option
5497                 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
5498         },
5499
5500         /* @section
5501          *
5502          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
5503          */
5504
5505         initialize: function (latlng, options) {
5506                 L.setOptions(this, options);
5507                 this._latlng = L.latLng(latlng);
5508         },
5509
5510         onAdd: function (map) {
5511                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
5512
5513                 if (this._zoomAnimated) {
5514                         map.on('zoomanim', this._animateZoom, this);
5515                 }
5516
5517                 this._initIcon();
5518                 this.update();
5519         },
5520
5521         onRemove: function (map) {
5522                 if (this.dragging && this.dragging.enabled()) {
5523                         this.options.draggable = true;
5524                         this.dragging.removeHooks();
5525                 }
5526
5527                 if (this._zoomAnimated) {
5528                         map.off('zoomanim', this._animateZoom, this);
5529                 }
5530
5531                 this._removeIcon();
5532                 this._removeShadow();
5533         },
5534
5535         getEvents: function () {
5536                 return {
5537                         zoom: this.update,
5538                         viewreset: this.update
5539                 };
5540         },
5541
5542         // @method getLatLng: LatLng
5543         // Returns the current geographical position of the marker.
5544         getLatLng: function () {
5545                 return this._latlng;
5546         },
5547
5548         // @method setLatLng(latlng: LatLng): this
5549         // Changes the marker position to the given point.
5550         setLatLng: function (latlng) {
5551                 var oldLatLng = this._latlng;
5552                 this._latlng = L.latLng(latlng);
5553                 this.update();
5554
5555                 // @event move: Event
5556                 // 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`.
5557                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
5558         },
5559
5560         // @method setZIndexOffset(offset: Number): this
5561         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
5562         setZIndexOffset: function (offset) {
5563                 this.options.zIndexOffset = offset;
5564                 return this.update();
5565         },
5566
5567         // @method setIcon(icon: Icon): this
5568         // Changes the marker icon.
5569         setIcon: function (icon) {
5570
5571                 this.options.icon = icon;
5572
5573                 if (this._map) {
5574                         this._initIcon();
5575                         this.update();
5576                 }
5577
5578                 if (this._popup) {
5579                         this.bindPopup(this._popup, this._popup.options);
5580                 }
5581
5582                 return this;
5583         },
5584
5585         getElement: function () {
5586                 return this._icon;
5587         },
5588
5589         update: function () {
5590
5591                 if (this._icon) {
5592                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
5593                         this._setPos(pos);
5594                 }
5595
5596                 return this;
5597         },
5598
5599         _initIcon: function () {
5600                 var options = this.options,
5601                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
5602
5603                 var icon = options.icon.createIcon(this._icon),
5604                     addIcon = false;
5605
5606                 // if we're not reusing the icon, remove the old one and init new one
5607                 if (icon !== this._icon) {
5608                         if (this._icon) {
5609                                 this._removeIcon();
5610                         }
5611                         addIcon = true;
5612
5613                         if (options.title) {
5614                                 icon.title = options.title;
5615                         }
5616                         if (options.alt) {
5617                                 icon.alt = options.alt;
5618                         }
5619                 }
5620
5621                 L.DomUtil.addClass(icon, classToAdd);
5622
5623                 if (options.keyboard) {
5624                         icon.tabIndex = '0';
5625                 }
5626
5627                 this._icon = icon;
5628
5629                 if (options.riseOnHover) {
5630                         this.on({
5631                                 mouseover: this._bringToFront,
5632                                 mouseout: this._resetZIndex
5633                         });
5634                 }
5635
5636                 var newShadow = options.icon.createShadow(this._shadow),
5637                     addShadow = false;
5638
5639                 if (newShadow !== this._shadow) {
5640                         this._removeShadow();
5641                         addShadow = true;
5642                 }
5643
5644                 if (newShadow) {
5645                         L.DomUtil.addClass(newShadow, classToAdd);
5646                 }
5647                 this._shadow = newShadow;
5648
5649
5650                 if (options.opacity < 1) {
5651                         this._updateOpacity();
5652                 }
5653
5654
5655                 if (addIcon) {
5656                         this.getPane().appendChild(this._icon);
5657                 }
5658                 this._initInteraction();
5659                 if (newShadow && addShadow) {
5660                         this.getPane('shadowPane').appendChild(this._shadow);
5661                 }
5662         },
5663
5664         _removeIcon: function () {
5665                 if (this.options.riseOnHover) {
5666                         this.off({
5667                                 mouseover: this._bringToFront,
5668                                 mouseout: this._resetZIndex
5669                         });
5670                 }
5671
5672                 L.DomUtil.remove(this._icon);
5673                 this.removeInteractiveTarget(this._icon);
5674
5675                 this._icon = null;
5676         },
5677
5678         _removeShadow: function () {
5679                 if (this._shadow) {
5680                         L.DomUtil.remove(this._shadow);
5681                 }
5682                 this._shadow = null;
5683         },
5684
5685         _setPos: function (pos) {
5686                 L.DomUtil.setPosition(this._icon, pos);
5687
5688                 if (this._shadow) {
5689                         L.DomUtil.setPosition(this._shadow, pos);
5690                 }
5691
5692                 this._zIndex = pos.y + this.options.zIndexOffset;
5693
5694                 this._resetZIndex();
5695         },
5696
5697         _updateZIndex: function (offset) {
5698                 this._icon.style.zIndex = this._zIndex + offset;
5699         },
5700
5701         _animateZoom: function (opt) {
5702                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
5703
5704                 this._setPos(pos);
5705         },
5706
5707         _initInteraction: function () {
5708
5709                 if (!this.options.interactive) { return; }
5710
5711                 L.DomUtil.addClass(this._icon, 'leaflet-interactive');
5712
5713                 this.addInteractiveTarget(this._icon);
5714
5715                 if (L.Handler.MarkerDrag) {
5716                         var draggable = this.options.draggable;
5717                         if (this.dragging) {
5718                                 draggable = this.dragging.enabled();
5719                                 this.dragging.disable();
5720                         }
5721
5722                         this.dragging = new L.Handler.MarkerDrag(this);
5723
5724                         if (draggable) {
5725                                 this.dragging.enable();
5726                         }
5727                 }
5728         },
5729
5730         // @method setOpacity(opacity: Number): this
5731         // Changes the opacity of the marker.
5732         setOpacity: function (opacity) {
5733                 this.options.opacity = opacity;
5734                 if (this._map) {
5735                         this._updateOpacity();
5736                 }
5737
5738                 return this;
5739         },
5740
5741         _updateOpacity: function () {
5742                 var opacity = this.options.opacity;
5743
5744                 L.DomUtil.setOpacity(this._icon, opacity);
5745
5746                 if (this._shadow) {
5747                         L.DomUtil.setOpacity(this._shadow, opacity);
5748                 }
5749         },
5750
5751         _bringToFront: function () {
5752                 this._updateZIndex(this.options.riseOffset);
5753         },
5754
5755         _resetZIndex: function () {
5756                 this._updateZIndex(0);
5757         }
5758 });
5759
5760
5761 // factory L.marker(latlng: LatLng, options? : Marker options)
5762
5763 // @factory L.marker(latlng: LatLng, options? : Marker options)
5764 // Instantiates a Marker object given a geographical point and optionally an options object.
5765 L.marker = function (latlng, options) {
5766         return new L.Marker(latlng, options);
5767 };
5768
5769
5770
5771 /*
5772  * @class DivIcon
5773  * @aka L.DivIcon
5774  * @inherits Icon
5775  *
5776  * Represents a lightweight icon for markers that uses a simple `<div>`
5777  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
5778  *
5779  * @example
5780  * ```js
5781  * var myIcon = L.divIcon({className: 'my-div-icon'});
5782  * // you can set .my-div-icon styles in CSS
5783  *
5784  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5785  * ```
5786  *
5787  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
5788  */
5789
5790 L.DivIcon = L.Icon.extend({
5791         options: {
5792                 // @section
5793                 // @aka DivIcon options
5794                 iconSize: [12, 12], // also can be set through CSS
5795
5796                 // iconAnchor: (Point),
5797                 // popupAnchor: (Point),
5798
5799                 // @option html: String = ''
5800                 // Custom HTML code to put inside the div element, empty by default.
5801                 html: false,
5802
5803                 // @option bgPos: Point = [0, 0]
5804                 // Optional relative position of the background, in pixels
5805                 bgPos: null,
5806
5807                 className: 'leaflet-div-icon'
5808         },
5809
5810         createIcon: function (oldIcon) {
5811                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
5812                     options = this.options;
5813
5814                 div.innerHTML = options.html !== false ? options.html : '';
5815
5816                 if (options.bgPos) {
5817                         var bgPos = L.point(options.bgPos);
5818                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
5819                 }
5820                 this._setIconStyles(div, 'icon');
5821
5822                 return div;
5823         },
5824
5825         createShadow: function () {
5826                 return null;
5827         }
5828 });
5829
5830 // @factory L.divIcon(options: DivIcon options)
5831 // Creates a `DivIcon` instance with the given options.
5832 L.divIcon = function (options) {
5833         return new L.DivIcon(options);
5834 };
5835
5836
5837
5838 /*
5839  * @class DivOverlay
5840  * @inherits Layer
5841  * @aka L.DivOverlay
5842  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
5843  */
5844
5845 // @namespace DivOverlay
5846 L.DivOverlay = L.Layer.extend({
5847
5848         // @section
5849         // @aka DivOverlay options
5850         options: {
5851                 // @option offset: Point = Point(0, 7)
5852                 // The offset of the popup position. Useful to control the anchor
5853                 // of the popup when opening it on some overlays.
5854                 offset: [0, 7],
5855
5856                 // @option className: String = ''
5857                 // A custom CSS class name to assign to the popup.
5858                 className: '',
5859
5860                 // @option pane: String = 'popupPane'
5861                 // `Map pane` where the popup will be added.
5862                 pane: 'popupPane'
5863         },
5864
5865         initialize: function (options, source) {
5866                 L.setOptions(this, options);
5867
5868                 this._source = source;
5869         },
5870
5871         onAdd: function (map) {
5872                 this._zoomAnimated = map._zoomAnimated;
5873
5874                 if (!this._container) {
5875                         this._initLayout();
5876                 }
5877
5878                 if (map._fadeAnimated) {
5879                         L.DomUtil.setOpacity(this._container, 0);
5880                 }
5881
5882                 clearTimeout(this._removeTimeout);
5883                 this.getPane().appendChild(this._container);
5884                 this.update();
5885
5886                 if (map._fadeAnimated) {
5887                         L.DomUtil.setOpacity(this._container, 1);
5888                 }
5889
5890                 this.bringToFront();
5891         },
5892
5893         onRemove: function (map) {
5894                 if (map._fadeAnimated) {
5895                         L.DomUtil.setOpacity(this._container, 0);
5896                         this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
5897                 } else {
5898                         L.DomUtil.remove(this._container);
5899                 }
5900         },
5901
5902         // @namespace Popup
5903         // @method getLatLng: LatLng
5904         // Returns the geographical point of popup.
5905         getLatLng: function () {
5906                 return this._latlng;
5907         },
5908
5909         // @method setLatLng(latlng: LatLng): this
5910         // Sets the geographical point where the popup will open.
5911         setLatLng: function (latlng) {
5912                 this._latlng = L.latLng(latlng);
5913                 if (this._map) {
5914                         this._updatePosition();
5915                         this._adjustPan();
5916                 }
5917                 return this;
5918         },
5919
5920         // @method getContent: String|HTMLElement
5921         // Returns the content of the popup.
5922         getContent: function () {
5923                 return this._content;
5924         },
5925
5926         // @method setContent(htmlContent: String|HTMLElement|Function): this
5927         // 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.
5928         setContent: function (content) {
5929                 this._content = content;
5930                 this.update();
5931                 return this;
5932         },
5933
5934         // @method getElement: String|HTMLElement
5935         // Alias for [getContent()](#popup-getcontent)
5936         getElement: function () {
5937                 return this._container;
5938         },
5939
5940         // @method update: null
5941         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
5942         update: function () {
5943                 if (!this._map) { return; }
5944
5945                 this._container.style.visibility = 'hidden';
5946
5947                 this._updateContent();
5948                 this._updateLayout();
5949                 this._updatePosition();
5950
5951                 this._container.style.visibility = '';
5952
5953                 this._adjustPan();
5954         },
5955
5956         getEvents: function () {
5957                 var events = {
5958                         zoom: this._updatePosition,
5959                         viewreset: this._updatePosition
5960                 };
5961
5962                 if (this._zoomAnimated) {
5963                         events.zoomanim = this._animateZoom;
5964                 }
5965                 return events;
5966         },
5967
5968         // @method isOpen: Boolean
5969         // Returns `true` when the popup is visible on the map.
5970         isOpen: function () {
5971                 return !!this._map && this._map.hasLayer(this);
5972         },
5973
5974         // @method bringToFront: this
5975         // Brings this popup in front of other popups (in the same map pane).
5976         bringToFront: function () {
5977                 if (this._map) {
5978                         L.DomUtil.toFront(this._container);
5979                 }
5980                 return this;
5981         },
5982
5983         // @method bringToBack: this
5984         // Brings this popup to the back of other popups (in the same map pane).
5985         bringToBack: function () {
5986                 if (this._map) {
5987                         L.DomUtil.toBack(this._container);
5988                 }
5989                 return this;
5990         },
5991
5992         _updateContent: function () {
5993                 if (!this._content) { return; }
5994
5995                 var node = this._contentNode;
5996                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
5997
5998                 if (typeof content === 'string') {
5999                         node.innerHTML = content;
6000                 } else {
6001                         while (node.hasChildNodes()) {
6002                                 node.removeChild(node.firstChild);
6003                         }
6004                         node.appendChild(content);
6005                 }
6006                 this.fire('contentupdate');
6007         },
6008
6009         _updatePosition: function () {
6010                 if (!this._map) { return; }
6011
6012                 var pos = this._map.latLngToLayerPoint(this._latlng),
6013                     offset = L.point(this.options.offset),
6014                     anchor = this._getAnchor();
6015
6016                 if (this._zoomAnimated) {
6017                         L.DomUtil.setPosition(this._container, pos.add(anchor));
6018                 } else {
6019                         offset = offset.add(pos).add(anchor);
6020                 }
6021
6022                 var bottom = this._containerBottom = -offset.y,
6023                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6024
6025                 // bottom position the popup in case the height of the popup changes (images loading etc)
6026                 this._container.style.bottom = bottom + 'px';
6027                 this._container.style.left = left + 'px';
6028         },
6029
6030         _getAnchor: function () {
6031                 return [0, 0];
6032         }
6033
6034 });
6035
6036
6037
6038 /*
6039  * @class Popup
6040  * @inherits DivOverlay
6041  * @aka L.Popup
6042  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6043  * open popups while making sure that only one popup is open at one time
6044  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6045  *
6046  * @example
6047  *
6048  * If you want to just bind a popup to marker click and then open it, it's really easy:
6049  *
6050  * ```js
6051  * marker.bindPopup(popupContent).openPopup();
6052  * ```
6053  * Path overlays like polylines also have a `bindPopup` method.
6054  * Here's a more complicated way to open a popup on a map:
6055  *
6056  * ```js
6057  * var popup = L.popup()
6058  *      .setLatLng(latlng)
6059  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
6060  *      .openOn(map);
6061  * ```
6062  */
6063
6064
6065 // @namespace Popup
6066 L.Popup = L.DivOverlay.extend({
6067
6068         // @section
6069         // @aka Popup options
6070         options: {
6071                 // @option maxWidth: Number = 300
6072                 // Max width of the popup, in pixels.
6073                 maxWidth: 300,
6074
6075                 // @option minWidth: Number = 50
6076                 // Min width of the popup, in pixels.
6077                 minWidth: 50,
6078
6079                 // @option maxHeight: Number = null
6080                 // If set, creates a scrollable container of the given height
6081                 // inside a popup if its content exceeds it.
6082                 maxHeight: null,
6083
6084                 // @option autoPan: Boolean = true
6085                 // Set it to `false` if you don't want the map to do panning animation
6086                 // to fit the opened popup.
6087                 autoPan: true,
6088
6089                 // @option autoPanPaddingTopLeft: Point = null
6090                 // The margin between the popup and the top left corner of the map
6091                 // view after autopanning was performed.
6092                 autoPanPaddingTopLeft: null,
6093
6094                 // @option autoPanPaddingBottomRight: Point = null
6095                 // The margin between the popup and the bottom right corner of the map
6096                 // view after autopanning was performed.
6097                 autoPanPaddingBottomRight: null,
6098
6099                 // @option autoPanPadding: Point = Point(5, 5)
6100                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
6101                 autoPanPadding: [5, 5],
6102
6103                 // @option keepInView: Boolean = false
6104                 // Set it to `true` if you want to prevent users from panning the popup
6105                 // off of the screen while it is open.
6106                 keepInView: false,
6107
6108                 // @option closeButton: Boolean = true
6109                 // Controls the presence of a close button in the popup.
6110                 closeButton: true,
6111
6112                 // @option autoClose: Boolean = true
6113                 // Set it to `false` if you want to override the default behavior of
6114                 // the popup closing when user clicks the map (set globally by
6115                 // the Map's [closePopupOnClick](#map-closepopuponclick) option).
6116                 autoClose: true,
6117
6118                 // @option className: String = ''
6119                 // A custom CSS class name to assign to the popup.
6120                 className: ''
6121         },
6122
6123         // @namespace Popup
6124         // @method openOn(map: Map): this
6125         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
6126         openOn: function (map) {
6127                 map.openPopup(this);
6128                 return this;
6129         },
6130
6131         onAdd: function (map) {
6132                 L.DivOverlay.prototype.onAdd.call(this, map);
6133
6134                 // @namespace Map
6135                 // @section Popup events
6136                 // @event popupopen: PopupEvent
6137                 // Fired when a popup is opened in the map
6138                 map.fire('popupopen', {popup: this});
6139
6140                 if (this._source) {
6141                         // @namespace Layer
6142                         // @section Popup events
6143                         // @event popupopen: PopupEvent
6144                         // Fired when a popup bound to this layer is opened
6145                         this._source.fire('popupopen', {popup: this}, true);
6146                         // For non-path layers, we toggle the popup when clicking
6147                         // again the layer, so prevent the map to reopen it.
6148                         if (!(this._source instanceof L.Path)) {
6149                                 this._source.on('preclick', L.DomEvent.stopPropagation);
6150                         }
6151                 }
6152         },
6153
6154         onRemove: function (map) {
6155                 L.DivOverlay.prototype.onRemove.call(this, map);
6156
6157                 // @namespace Map
6158                 // @section Popup events
6159                 // @event popupclose: PopupEvent
6160                 // Fired when a popup in the map is closed
6161                 map.fire('popupclose', {popup: this});
6162
6163                 if (this._source) {
6164                         // @namespace Layer
6165                         // @section Popup events
6166                         // @event popupclose: PopupEvent
6167                         // Fired when a popup bound to this layer is closed
6168                         this._source.fire('popupclose', {popup: this}, true);
6169                         if (!(this._source instanceof L.Path)) {
6170                                 this._source.off('preclick', L.DomEvent.stopPropagation);
6171                         }
6172                 }
6173         },
6174
6175         getEvents: function () {
6176                 var events = L.DivOverlay.prototype.getEvents.call(this);
6177
6178                 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
6179                         events.preclick = this._close;
6180                 }
6181
6182                 if (this.options.keepInView) {
6183                         events.moveend = this._adjustPan;
6184                 }
6185
6186                 return events;
6187         },
6188
6189         _close: function () {
6190                 if (this._map) {
6191                         this._map.closePopup(this);
6192                 }
6193         },
6194
6195         _initLayout: function () {
6196                 var prefix = 'leaflet-popup',
6197                     container = this._container = L.DomUtil.create('div',
6198                         prefix + ' ' + (this.options.className || '') +
6199                         ' leaflet-zoom-animated');
6200
6201                 if (this.options.closeButton) {
6202                         var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
6203                         closeButton.href = '#close';
6204                         closeButton.innerHTML = '&#215;';
6205
6206                         L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
6207                 }
6208
6209                 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
6210                 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
6211
6212                 L.DomEvent
6213                         .disableClickPropagation(wrapper)
6214                         .disableScrollPropagation(this._contentNode)
6215                         .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
6216
6217                 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
6218                 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
6219         },
6220
6221         _updateLayout: function () {
6222                 var container = this._contentNode,
6223                     style = container.style;
6224
6225                 style.width = '';
6226                 style.whiteSpace = 'nowrap';
6227
6228                 var width = container.offsetWidth;
6229                 width = Math.min(width, this.options.maxWidth);
6230                 width = Math.max(width, this.options.minWidth);
6231
6232                 style.width = (width + 1) + 'px';
6233                 style.whiteSpace = '';
6234
6235                 style.height = '';
6236
6237                 var height = container.offsetHeight,
6238                     maxHeight = this.options.maxHeight,
6239                     scrolledClass = 'leaflet-popup-scrolled';
6240
6241                 if (maxHeight && height > maxHeight) {
6242                         style.height = maxHeight + 'px';
6243                         L.DomUtil.addClass(container, scrolledClass);
6244                 } else {
6245                         L.DomUtil.removeClass(container, scrolledClass);
6246                 }
6247
6248                 this._containerWidth = this._container.offsetWidth;
6249         },
6250
6251         _animateZoom: function (e) {
6252                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
6253                     anchor = this._getAnchor();
6254                 L.DomUtil.setPosition(this._container, pos.add(anchor));
6255         },
6256
6257         _adjustPan: function () {
6258                 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
6259
6260                 var map = this._map,
6261                     marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
6262                     containerHeight = this._container.offsetHeight + marginBottom,
6263                     containerWidth = this._containerWidth,
6264                     layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
6265
6266                 layerPos._add(L.DomUtil.getPosition(this._container));
6267
6268                 var containerPos = map.layerPointToContainerPoint(layerPos),
6269                     padding = L.point(this.options.autoPanPadding),
6270                     paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
6271                     paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
6272                     size = map.getSize(),
6273                     dx = 0,
6274                     dy = 0;
6275
6276                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
6277                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
6278                 }
6279                 if (containerPos.x - dx - paddingTL.x < 0) { // left
6280                         dx = containerPos.x - paddingTL.x;
6281                 }
6282                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
6283                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
6284                 }
6285                 if (containerPos.y - dy - paddingTL.y < 0) { // top
6286                         dy = containerPos.y - paddingTL.y;
6287                 }
6288
6289                 // @namespace Map
6290                 // @section Popup events
6291                 // @event autopanstart: Event
6292                 // Fired when the map starts autopanning when opening a popup.
6293                 if (dx || dy) {
6294                         map
6295                             .fire('autopanstart')
6296                             .panBy([dx, dy]);
6297                 }
6298         },
6299
6300         _onCloseButtonClick: function (e) {
6301                 this._close();
6302                 L.DomEvent.stop(e);
6303         },
6304
6305         _getAnchor: function () {
6306                 // Where should we anchor the popup on the source layer?
6307                 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
6308         }
6309
6310 });
6311
6312 // @namespace Popup
6313 // @factory L.popup(options?: Popup options, source?: Layer)
6314 // 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.
6315 L.popup = function (options, source) {
6316         return new L.Popup(options, source);
6317 };
6318
6319
6320 /* @namespace Map
6321  * @section Interaction Options
6322  * @option closePopupOnClick: Boolean = true
6323  * Set it to `false` if you don't want popups to close when user clicks the map.
6324  */
6325 L.Map.mergeOptions({
6326         closePopupOnClick: true
6327 });
6328
6329
6330 // @namespace Map
6331 // @section Methods for Layers and Controls
6332 L.Map.include({
6333         // @method openPopup(popup: Popup): this
6334         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
6335         // @alternative
6336         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
6337         // Creates a popup with the specified content and options and opens it in the given point on a map.
6338         openPopup: function (popup, latlng, options) {
6339                 if (!(popup instanceof L.Popup)) {
6340                         popup = new L.Popup(options).setContent(popup);
6341                 }
6342
6343                 if (latlng) {
6344                         popup.setLatLng(latlng);
6345                 }
6346
6347                 if (this.hasLayer(popup)) {
6348                         return this;
6349                 }
6350
6351                 if (this._popup && this._popup.options.autoClose) {
6352                         this.closePopup();
6353                 }
6354
6355                 this._popup = popup;
6356                 return this.addLayer(popup);
6357         },
6358
6359         // @method closePopup(popup?: Popup): this
6360         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
6361         closePopup: function (popup) {
6362                 if (!popup || popup === this._popup) {
6363                         popup = this._popup;
6364                         this._popup = null;
6365                 }
6366                 if (popup) {
6367                         this.removeLayer(popup);
6368                 }
6369                 return this;
6370         }
6371 });
6372
6373
6374
6375 /*
6376  * @namespace Layer
6377  * @section Popup methods example
6378  *
6379  * All layers share a set of methods convenient for binding popups to it.
6380  *
6381  * ```js
6382  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
6383  * layer.openPopup();
6384  * layer.closePopup();
6385  * ```
6386  *
6387  * 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.
6388  */
6389
6390 // @section Popup methods
6391 L.Layer.include({
6392
6393         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
6394         // Binds a popup to the layer with the passed `content` and sets up the
6395         // neccessary event listeners. If a `Function` is passed it will receive
6396         // the layer as the first argument and should return a `String` or `HTMLElement`.
6397         bindPopup: function (content, options) {
6398
6399                 if (content instanceof L.Popup) {
6400                         L.setOptions(content, options);
6401                         this._popup = content;
6402                         content._source = this;
6403                 } else {
6404                         if (!this._popup || options) {
6405                                 this._popup = new L.Popup(options, this);
6406                         }
6407                         this._popup.setContent(content);
6408                 }
6409
6410                 if (!this._popupHandlersAdded) {
6411                         this.on({
6412                                 click: this._openPopup,
6413                                 remove: this.closePopup,
6414                                 move: this._movePopup
6415                         });
6416                         this._popupHandlersAdded = true;
6417                 }
6418
6419                 return this;
6420         },
6421
6422         // @method unbindPopup(): this
6423         // Removes the popup previously bound with `bindPopup`.
6424         unbindPopup: function () {
6425                 if (this._popup) {
6426                         this.off({
6427                                 click: this._openPopup,
6428                                 remove: this.closePopup,
6429                                 move: this._movePopup
6430                         });
6431                         this._popupHandlersAdded = false;
6432                         this._popup = null;
6433                 }
6434                 return this;
6435         },
6436
6437         // @method openPopup(latlng?: LatLng): this
6438         // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
6439         openPopup: function (layer, latlng) {
6440                 if (!(layer instanceof L.Layer)) {
6441                         latlng = layer;
6442                         layer = this;
6443                 }
6444
6445                 if (layer instanceof L.FeatureGroup) {
6446                         for (var id in this._layers) {
6447                                 layer = this._layers[id];
6448                                 break;
6449                         }
6450                 }
6451
6452                 if (!latlng) {
6453                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6454                 }
6455
6456                 if (this._popup && this._map) {
6457                         // set popup source to this layer
6458                         this._popup._source = layer;
6459
6460                         // update the popup (content, layout, ect...)
6461                         this._popup.update();
6462
6463                         // open the popup on the map
6464                         this._map.openPopup(this._popup, latlng);
6465                 }
6466
6467                 return this;
6468         },
6469
6470         // @method closePopup(): this
6471         // Closes the popup bound to this layer if it is open.
6472         closePopup: function () {
6473                 if (this._popup) {
6474                         this._popup._close();
6475                 }
6476                 return this;
6477         },
6478
6479         // @method togglePopup(): this
6480         // Opens or closes the popup bound to this layer depending on its current state.
6481         togglePopup: function (target) {
6482                 if (this._popup) {
6483                         if (this._popup._map) {
6484                                 this.closePopup();
6485                         } else {
6486                                 this.openPopup(target);
6487                         }
6488                 }
6489                 return this;
6490         },
6491
6492         // @method isPopupOpen(): boolean
6493         // Returns `true` if the popup bound to this layer is currently open.
6494         isPopupOpen: function () {
6495                 return this._popup.isOpen();
6496         },
6497
6498         // @method setPopupContent(content: String|HTMLElement|Popup): this
6499         // Sets the content of the popup bound to this layer.
6500         setPopupContent: function (content) {
6501                 if (this._popup) {
6502                         this._popup.setContent(content);
6503                 }
6504                 return this;
6505         },
6506
6507         // @method getPopup(): Popup
6508         // Returns the popup bound to this layer.
6509         getPopup: function () {
6510                 return this._popup;
6511         },
6512
6513         _openPopup: function (e) {
6514                 var layer = e.layer || e.target;
6515
6516                 if (!this._popup) {
6517                         return;
6518                 }
6519
6520                 if (!this._map) {
6521                         return;
6522                 }
6523
6524                 // prevent map click
6525                 L.DomEvent.stop(e);
6526
6527                 // if this inherits from Path its a vector and we can just
6528                 // open the popup at the new location
6529                 if (layer instanceof L.Path) {
6530                         this.openPopup(e.layer || e.target, e.latlng);
6531                         return;
6532                 }
6533
6534                 // otherwise treat it like a marker and figure out
6535                 // if we should toggle it open/closed
6536                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
6537                         this.closePopup();
6538                 } else {
6539                         this.openPopup(layer, e.latlng);
6540                 }
6541         },
6542
6543         _movePopup: function (e) {
6544                 this._popup.setLatLng(e.latlng);
6545         }
6546 });
6547
6548
6549
6550 /*
6551  * Popup extension to L.Marker, adding popup-related methods.
6552  */
6553
6554 L.Marker.include({
6555         _getPopupAnchor: function () {
6556                 return this.options.icon.options.popupAnchor || [0, 0];
6557         }
6558 });
6559
6560
6561
6562 /*
6563  * @class Tooltip
6564  * @inherits DivOverlay
6565  * @aka L.Tooltip
6566  * Used to display small texts on top of map layers.
6567  *
6568  * @example
6569  *
6570  * ```js
6571  * marker.bindTooltip("my tooltip text").openTooltip();
6572  * ```
6573  * Note about tooltip offset. Leaflet takes two options in consideration
6574  * for computing tooltip offseting:
6575  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
6576  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
6577  *   move it to the bottom. Negatives will move to the left and top.
6578  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
6579  *   should adapt this value if you use a custom icon.
6580  */
6581
6582
6583 // @namespace Tooltip
6584 L.Tooltip = L.DivOverlay.extend({
6585
6586         // @section
6587         // @aka Tooltip options
6588         options: {
6589                 // @option pane: String = 'tooltipPane'
6590                 // `Map pane` where the tooltip will be added.
6591                 pane: 'tooltipPane',
6592
6593                 // @option offset: Point = Point(0, 0)
6594                 // Optional offset of the tooltip position.
6595                 offset: [0, 0],
6596
6597                 // @option direction: String = 'auto'
6598                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
6599                 // `top`, `bottom`, `center`, `auto`.
6600                 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
6601                 // position on the map.
6602                 direction: 'auto',
6603
6604                 // @option permanent: Boolean = false
6605                 // Whether to open the tooltip permanently or only on mouseover.
6606                 permanent: false,
6607
6608                 // @option sticky: Boolean = false
6609                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
6610                 sticky: false,
6611
6612                 // @option interactive: Boolean = false
6613                 // If true, the tooltip will listen to the feature events.
6614                 interactive: false,
6615
6616                 // @option opacity: Number = 0.9
6617                 // Tooltip container opacity.
6618                 opacity: 0.9
6619         },
6620
6621         onAdd: function (map) {
6622                 L.DivOverlay.prototype.onAdd.call(this, map);
6623                 this.setOpacity(this.options.opacity);
6624
6625                 // @namespace Map
6626                 // @section Tooltip events
6627                 // @event tooltipopen: TooltipEvent
6628                 // Fired when a tooltip is opened in the map.
6629                 map.fire('tooltipopen', {tooltip: this});
6630
6631                 if (this._source) {
6632                         // @namespace Layer
6633                         // @section Tooltip events
6634                         // @event tooltipopen: TooltipEvent
6635                         // Fired when a tooltip bound to this layer is opened.
6636                         this._source.fire('tooltipopen', {tooltip: this}, true);
6637                 }
6638         },
6639
6640         onRemove: function (map) {
6641                 L.DivOverlay.prototype.onRemove.call(this, map);
6642
6643                 // @namespace Map
6644                 // @section Tooltip events
6645                 // @event tooltipclose: TooltipEvent
6646                 // Fired when a tooltip in the map is closed.
6647                 map.fire('tooltipclose', {tooltip: this});
6648
6649                 if (this._source) {
6650                         // @namespace Layer
6651                         // @section Tooltip events
6652                         // @event tooltipclose: TooltipEvent
6653                         // Fired when a tooltip bound to this layer is closed.
6654                         this._source.fire('tooltipclose', {tooltip: this}, true);
6655                 }
6656         },
6657
6658         getEvents: function () {
6659                 var events = L.DivOverlay.prototype.getEvents.call(this);
6660
6661                 if (L.Browser.touch && !this.options.permanent) {
6662                         events.preclick = this._close;
6663                 }
6664
6665                 return events;
6666         },
6667
6668         _close: function () {
6669                 if (this._map) {
6670                         this._map.closeTooltip(this);
6671                 }
6672         },
6673
6674         _initLayout: function () {
6675                 var prefix = 'leaflet-tooltip',
6676                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6677
6678                 this._contentNode = this._container = L.DomUtil.create('div', className);
6679         },
6680
6681         _updateLayout: function () {},
6682
6683         _adjustPan: function () {},
6684
6685         _setPosition: function (pos) {
6686                 var map = this._map,
6687                     container = this._container,
6688                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
6689                     tooltipPoint = map.layerPointToContainerPoint(pos),
6690                     direction = this.options.direction,
6691                     tooltipWidth = container.offsetWidth,
6692                     tooltipHeight = container.offsetHeight,
6693                     offset = L.point(this.options.offset),
6694                     anchor = this._getAnchor();
6695
6696                 if (direction === 'top') {
6697                         pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y));
6698                 } else if (direction === 'bottom') {
6699                         pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y));
6700                 } else if (direction === 'center') {
6701                         pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y));
6702                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
6703                         direction = 'right';
6704                         pos = pos.add([offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y]);
6705                 } else {
6706                         direction = 'left';
6707                         pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y));
6708                 }
6709
6710                 L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
6711                 L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
6712                 L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
6713                 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
6714                 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
6715                 L.DomUtil.setPosition(container, pos);
6716         },
6717
6718         _updatePosition: function () {
6719                 var pos = this._map.latLngToLayerPoint(this._latlng);
6720                 this._setPosition(pos);
6721         },
6722
6723         setOpacity: function (opacity) {
6724                 this.options.opacity = opacity;
6725
6726                 if (this._container) {
6727                         L.DomUtil.setOpacity(this._container, opacity);
6728                 }
6729         },
6730
6731         _animateZoom: function (e) {
6732                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
6733                 this._setPosition(pos);
6734         },
6735
6736         _getAnchor: function () {
6737                 // Where should we anchor the tooltip on the source layer?
6738                 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
6739         }
6740
6741 });
6742
6743 // @namespace Tooltip
6744 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
6745 // 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.
6746 L.tooltip = function (options, source) {
6747         return new L.Tooltip(options, source);
6748 };
6749
6750 // @namespace Map
6751 // @section Methods for Layers and Controls
6752 L.Map.include({
6753
6754         // @method openTooltip(tooltip: Tooltip): this
6755         // Opens the specified tooltip.
6756         // @alternative
6757         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
6758         // Creates a tooltip with the specified content and options and open it.
6759         openTooltip: function (tooltip, latlng, options) {
6760                 if (!(tooltip instanceof L.Tooltip)) {
6761                         tooltip = new L.Tooltip(options).setContent(tooltip);
6762                 }
6763
6764                 if (latlng) {
6765                         tooltip.setLatLng(latlng);
6766                 }
6767
6768                 if (this.hasLayer(tooltip)) {
6769                         return this;
6770                 }
6771
6772                 return this.addLayer(tooltip);
6773         },
6774
6775         // @method closeTooltip(tooltip?: Tooltip): this
6776         // Closes the tooltip given as parameter.
6777         closeTooltip: function (tooltip) {
6778                 if (tooltip) {
6779                         this.removeLayer(tooltip);
6780                 }
6781                 return this;
6782         }
6783
6784 });
6785
6786
6787
6788 /*
6789  * @namespace Layer
6790  * @section Tooltip methods example
6791  *
6792  * All layers share a set of methods convenient for binding tooltips to it.
6793  *
6794  * ```js
6795  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
6796  * layer.openTooltip();
6797  * layer.closeTooltip();
6798  * ```
6799  */
6800
6801 // @section Tooltip methods
6802 L.Layer.include({
6803
6804         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
6805         // Binds a tooltip to the layer with the passed `content` and sets up the
6806         // neccessary event listeners. If a `Function` is passed it will receive
6807         // the layer as the first argument and should return a `String` or `HTMLElement`.
6808         bindTooltip: function (content, options) {
6809
6810                 if (content instanceof L.Tooltip) {
6811                         L.setOptions(content, options);
6812                         this._tooltip = content;
6813                         content._source = this;
6814                 } else {
6815                         if (!this._tooltip || options) {
6816                                 this._tooltip = L.tooltip(options, this);
6817                         }
6818                         this._tooltip.setContent(content);
6819
6820                 }
6821
6822                 this._initTooltipInteractions();
6823
6824                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
6825                         this.openTooltip();
6826                 }
6827
6828                 return this;
6829         },
6830
6831         // @method unbindTooltip(): this
6832         // Removes the tooltip previously bound with `bindTooltip`.
6833         unbindTooltip: function () {
6834                 if (this._tooltip) {
6835                         this._initTooltipInteractions(true);
6836                         this.closeTooltip();
6837                         this._tooltip = null;
6838                 }
6839                 return this;
6840         },
6841
6842         _initTooltipInteractions: function (remove) {
6843                 if (!remove && this._tooltipHandlersAdded) { return; }
6844                 var onOff = remove ? 'off' : 'on',
6845                     events = {
6846                         remove: this.closeTooltip,
6847                         move: this._moveTooltip
6848                     };
6849                 if (!this._tooltip.options.permanent) {
6850                         events.mouseover = this._openTooltip;
6851                         events.mouseout = this.closeTooltip;
6852                         if (this._tooltip.options.sticky) {
6853                                 events.mousemove = this._moveTooltip;
6854                         }
6855                         if (L.Browser.touch) {
6856                                 events.click = this._openTooltip;
6857                         }
6858                 } else {
6859                         events.add = this._openTooltip;
6860                 }
6861                 this[onOff](events);
6862                 this._tooltipHandlersAdded = !remove;
6863         },
6864
6865         // @method openTooltip(latlng?: LatLng): this
6866         // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
6867         openTooltip: function (layer, latlng) {
6868                 if (!(layer instanceof L.Layer)) {
6869                         latlng = layer;
6870                         layer = this;
6871                 }
6872
6873                 if (layer instanceof L.FeatureGroup) {
6874                         for (var id in this._layers) {
6875                                 layer = this._layers[id];
6876                                 break;
6877                         }
6878                 }
6879
6880                 if (!latlng) {
6881                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6882                 }
6883
6884                 if (this._tooltip && this._map) {
6885
6886                         // set tooltip source to this layer
6887                         this._tooltip._source = layer;
6888
6889                         // update the tooltip (content, layout, ect...)
6890                         this._tooltip.update();
6891
6892                         // open the tooltip on the map
6893                         this._map.openTooltip(this._tooltip, latlng);
6894
6895                         // Tooltip container may not be defined if not permanent and never
6896                         // opened.
6897                         if (this._tooltip.options.interactive && this._tooltip._container) {
6898                                 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
6899                                 this.addInteractiveTarget(this._tooltip._container);
6900                         }
6901                 }
6902
6903                 return this;
6904         },
6905
6906         // @method closeTooltip(): this
6907         // Closes the tooltip bound to this layer if it is open.
6908         closeTooltip: function () {
6909                 if (this._tooltip) {
6910                         this._tooltip._close();
6911                         if (this._tooltip.options.interactive && this._tooltip._container) {
6912                                 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
6913                                 this.removeInteractiveTarget(this._tooltip._container);
6914                         }
6915                 }
6916                 return this;
6917         },
6918
6919         // @method toggleTooltip(): this
6920         // Opens or closes the tooltip bound to this layer depending on its current state.
6921         toggleTooltip: function (target) {
6922                 if (this._tooltip) {
6923                         if (this._tooltip._map) {
6924                                 this.closeTooltip();
6925                         } else {
6926                                 this.openTooltip(target);
6927                         }
6928                 }
6929                 return this;
6930         },
6931
6932         // @method isTooltipOpen(): boolean
6933         // Returns `true` if the tooltip bound to this layer is currently open.
6934         isTooltipOpen: function () {
6935                 return this._tooltip.isOpen();
6936         },
6937
6938         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
6939         // Sets the content of the tooltip bound to this layer.
6940         setTooltipContent: function (content) {
6941                 if (this._tooltip) {
6942                         this._tooltip.setContent(content);
6943                 }
6944                 return this;
6945         },
6946
6947         // @method getTooltip(): Tooltip
6948         // Returns the tooltip bound to this layer.
6949         getTooltip: function () {
6950                 return this._tooltip;
6951         },
6952
6953         _openTooltip: function (e) {
6954                 var layer = e.layer || e.target;
6955
6956                 if (!this._tooltip || !this._map) {
6957                         return;
6958                 }
6959                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
6960         },
6961
6962         _moveTooltip: function (e) {
6963                 var latlng = e.latlng, containerPoint, layerPoint;
6964                 if (this._tooltip.options.sticky && e.originalEvent) {
6965                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
6966                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
6967                         latlng = this._map.layerPointToLatLng(layerPoint);
6968                 }
6969                 this._tooltip.setLatLng(latlng);
6970         }
6971 });
6972
6973
6974
6975 /*
6976  * Tooltip extension to L.Marker, adding tooltip-related methods.
6977  */
6978
6979 L.Marker.include({
6980         _getTooltipAnchor: function () {
6981                 return this.options.icon.options.tooltipAnchor || [0, 0];
6982         }
6983 });
6984
6985
6986
6987 /*
6988  * @class LayerGroup
6989  * @aka L.LayerGroup
6990  * @inherits Layer
6991  *
6992  * Used to group several layers and handle them as one. If you add it to the map,
6993  * any layers added or removed from the group will be added/removed on the map as
6994  * well. Extends `Layer`.
6995  *
6996  * @example
6997  *
6998  * ```js
6999  * L.layerGroup([marker1, marker2])
7000  *      .addLayer(polyline)
7001  *      .addTo(map);
7002  * ```
7003  */
7004
7005 L.LayerGroup = L.Layer.extend({
7006
7007         initialize: function (layers) {
7008                 this._layers = {};
7009
7010                 var i, len;
7011
7012                 if (layers) {
7013                         for (i = 0, len = layers.length; i < len; i++) {
7014                                 this.addLayer(layers[i]);
7015                         }
7016                 }
7017         },
7018
7019         // @method addLayer(layer: Layer): this
7020         // Adds the given layer to the group.
7021         addLayer: function (layer) {
7022                 var id = this.getLayerId(layer);
7023
7024                 this._layers[id] = layer;
7025
7026                 if (this._map) {
7027                         this._map.addLayer(layer);
7028                 }
7029
7030                 return this;
7031         },
7032
7033         // @method removeLayer(layer: Layer): this
7034         // Removes the given layer from the group.
7035         // @alternative
7036         // @method removeLayer(id: Number): this
7037         // Removes the layer with the given internal ID from the group.
7038         removeLayer: function (layer) {
7039                 var id = layer in this._layers ? layer : this.getLayerId(layer);
7040
7041                 if (this._map && this._layers[id]) {
7042                         this._map.removeLayer(this._layers[id]);
7043                 }
7044
7045                 delete this._layers[id];
7046
7047                 return this;
7048         },
7049
7050         // @method hasLayer(layer: Layer): Boolean
7051         // Returns `true` if the given layer is currently added to the group.
7052         hasLayer: function (layer) {
7053                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7054         },
7055
7056         // @method clearLayers(): this
7057         // Removes all the layers from the group.
7058         clearLayers: function () {
7059                 for (var i in this._layers) {
7060                         this.removeLayer(this._layers[i]);
7061                 }
7062                 return this;
7063         },
7064
7065         // @method invoke(methodName: String, …): this
7066         // Calls `methodName` on every layer contained in this group, passing any
7067         // additional parameters. Has no effect if the layers contained do not
7068         // implement `methodName`.
7069         invoke: function (methodName) {
7070                 var args = Array.prototype.slice.call(arguments, 1),
7071                     i, layer;
7072
7073                 for (i in this._layers) {
7074                         layer = this._layers[i];
7075
7076                         if (layer[methodName]) {
7077                                 layer[methodName].apply(layer, args);
7078                         }
7079                 }
7080
7081                 return this;
7082         },
7083
7084         onAdd: function (map) {
7085                 for (var i in this._layers) {
7086                         map.addLayer(this._layers[i]);
7087                 }
7088         },
7089
7090         onRemove: function (map) {
7091                 for (var i in this._layers) {
7092                         map.removeLayer(this._layers[i]);
7093                 }
7094         },
7095
7096         // @method eachLayer(fn: Function, context?: Object): this
7097         // Iterates over the layers of the group, optionally specifying context of the iterator function.
7098         // ```js
7099         // group.eachLayer(function (layer) {
7100         //      layer.bindPopup('Hello');
7101         // });
7102         // ```
7103         eachLayer: function (method, context) {
7104                 for (var i in this._layers) {
7105                         method.call(context, this._layers[i]);
7106                 }
7107                 return this;
7108         },
7109
7110         // @method getLayer(id: Number): Layer
7111         // Returns the layer with the given internal ID.
7112         getLayer: function (id) {
7113                 return this._layers[id];
7114         },
7115
7116         // @method getLayers(): Layer[]
7117         // Returns an array of all the layers added to the group.
7118         getLayers: function () {
7119                 var layers = [];
7120
7121                 for (var i in this._layers) {
7122                         layers.push(this._layers[i]);
7123                 }
7124                 return layers;
7125         },
7126
7127         // @method setZIndex(zIndex: Number): this
7128         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
7129         setZIndex: function (zIndex) {
7130                 return this.invoke('setZIndex', zIndex);
7131         },
7132
7133         // @method getLayerId(layer: Layer): Number
7134         // Returns the internal ID for a layer
7135         getLayerId: function (layer) {
7136                 return L.stamp(layer);
7137         }
7138 });
7139
7140
7141 // @factory L.layerGroup(layers: Layer[])
7142 // Create a layer group, optionally given an initial set of layers.
7143 L.layerGroup = function (layers) {
7144         return new L.LayerGroup(layers);
7145 };
7146
7147
7148
7149 /*
7150  * @class FeatureGroup
7151  * @aka L.FeatureGroup
7152  * @inherits LayerGroup
7153  *
7154  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7155  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7156  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
7157  * handler, it will handle events from any of the layers. This includes mouse events
7158  * and custom events.
7159  *  * Has `layeradd` and `layerremove` events
7160  *
7161  * @example
7162  *
7163  * ```js
7164  * L.featureGroup([marker1, marker2, polyline])
7165  *      .bindPopup('Hello world!')
7166  *      .on('click', function() { alert('Clicked on a member of the group!'); })
7167  *      .addTo(map);
7168  * ```
7169  */
7170
7171 L.FeatureGroup = L.LayerGroup.extend({
7172
7173         addLayer: function (layer) {
7174                 if (this.hasLayer(layer)) {
7175                         return this;
7176                 }
7177
7178                 layer.addEventParent(this);
7179
7180                 L.LayerGroup.prototype.addLayer.call(this, layer);
7181
7182                 // @event layeradd: LayerEvent
7183                 // Fired when a layer is added to this `FeatureGroup`
7184                 return this.fire('layeradd', {layer: layer});
7185         },
7186
7187         removeLayer: function (layer) {
7188                 if (!this.hasLayer(layer)) {
7189                         return this;
7190                 }
7191                 if (layer in this._layers) {
7192                         layer = this._layers[layer];
7193                 }
7194
7195                 layer.removeEventParent(this);
7196
7197                 L.LayerGroup.prototype.removeLayer.call(this, layer);
7198
7199                 // @event layerremove: LayerEvent
7200                 // Fired when a layer is removed from this `FeatureGroup`
7201                 return this.fire('layerremove', {layer: layer});
7202         },
7203
7204         // @method setStyle(style: Path options): this
7205         // Sets the given path options to each layer of the group that has a `setStyle` method.
7206         setStyle: function (style) {
7207                 return this.invoke('setStyle', style);
7208         },
7209
7210         // @method bringToFront(): this
7211         // Brings the layer group to the top of all other layers
7212         bringToFront: function () {
7213                 return this.invoke('bringToFront');
7214         },
7215
7216         // @method bringToBack(): this
7217         // Brings the layer group to the top of all other layers
7218         bringToBack: function () {
7219                 return this.invoke('bringToBack');
7220         },
7221
7222         // @method getBounds(): LatLngBounds
7223         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7224         getBounds: function () {
7225                 var bounds = new L.LatLngBounds();
7226
7227                 for (var id in this._layers) {
7228                         var layer = this._layers[id];
7229                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7230                 }
7231                 return bounds;
7232         }
7233 });
7234
7235 // @factory L.featureGroup(layers: Layer[])
7236 // Create a feature group, optionally given an initial set of layers.
7237 L.featureGroup = function (layers) {
7238         return new L.FeatureGroup(layers);
7239 };
7240
7241
7242
7243 /*
7244  * @class Renderer
7245  * @inherits Layer
7246  * @aka L.Renderer
7247  *
7248  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
7249  * DOM container of the renderer, its bounds, and its zoom animation.
7250  *
7251  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
7252  * itself can be added or removed to the map. All paths use a renderer, which can
7253  * be implicit (the map will decide the type of renderer and use it automatically)
7254  * or explicit (using the [`renderer`](#path-renderer) option of the path).
7255  *
7256  * Do not use this class directly, use `SVG` and `Canvas` instead.
7257  *
7258  * @event update: Event
7259  * Fired when the renderer updates its bounds, center and zoom, for example when
7260  * its map has moved
7261  */
7262
7263 L.Renderer = L.Layer.extend({
7264
7265         // @section
7266         // @aka Renderer options
7267         options: {
7268                 // @option padding: Number = 0.1
7269                 // How much to extend the clip area around the map view (relative to its size)
7270                 // e.g. 0.1 would be 10% of map view in each direction
7271                 padding: 0.1
7272         },
7273
7274         initialize: function (options) {
7275                 L.setOptions(this, options);
7276                 L.stamp(this);
7277         },
7278
7279         onAdd: function () {
7280                 if (!this._container) {
7281                         this._initContainer(); // defined by renderer implementations
7282
7283                         if (this._zoomAnimated) {
7284                                 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
7285                         }
7286                 }
7287
7288                 this.getPane().appendChild(this._container);
7289                 this._update();
7290         },
7291
7292         onRemove: function () {
7293                 L.DomUtil.remove(this._container);
7294         },
7295
7296         getEvents: function () {
7297                 var events = {
7298                         viewreset: this._reset,
7299                         zoom: this._onZoom,
7300                         moveend: this._update
7301                 };
7302                 if (this._zoomAnimated) {
7303                         events.zoomanim = this._onAnimZoom;
7304                 }
7305                 return events;
7306         },
7307
7308         _onAnimZoom: function (ev) {
7309                 this._updateTransform(ev.center, ev.zoom);
7310         },
7311
7312         _onZoom: function () {
7313                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
7314         },
7315
7316         _updateTransform: function (center, zoom) {
7317                 var scale = this._map.getZoomScale(zoom, this._zoom),
7318                     position = L.DomUtil.getPosition(this._container),
7319                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
7320                     currentCenterPoint = this._map.project(this._center, zoom),
7321                     destCenterPoint = this._map.project(center, zoom),
7322                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
7323
7324                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
7325
7326                 if (L.Browser.any3d) {
7327                         L.DomUtil.setTransform(this._container, topLeftOffset, scale);
7328                 } else {
7329                         L.DomUtil.setPosition(this._container, topLeftOffset);
7330                 }
7331         },
7332
7333         _reset: function () {
7334                 this._update();
7335                 this._updateTransform(this._center, this._zoom);
7336         },
7337
7338         _update: function () {
7339                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
7340                 // Subclasses are responsible of firing the 'update' event.
7341                 var p = this.options.padding,
7342                     size = this._map.getSize(),
7343                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
7344
7345                 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
7346
7347                 this._center = this._map.getCenter();
7348                 this._zoom = this._map.getZoom();
7349         }
7350 });
7351
7352
7353 L.Map.include({
7354         // @namespace Map; @method getRenderer(layer: Path): Renderer
7355         // Returns the instance of `Renderer` that should be used to render the given
7356         // `Path`. It will ensure that the `renderer` options of the map and paths
7357         // are respected, and that the renderers do exist on the map.
7358         getRenderer: function (layer) {
7359                 // @namespace Path; @option renderer: Renderer
7360                 // Use this specific instance of `Renderer` for this path. Takes
7361                 // precedence over the map's [default renderer](#map-renderer).
7362                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
7363
7364                 if (!renderer) {
7365                         // @namespace Map; @option preferCanvas: Boolean = false
7366                         // Whether `Path`s should be rendered on a `Canvas` renderer.
7367                         // By default, all `Path`s are rendered in a `SVG` renderer.
7368                         renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
7369                 }
7370
7371                 if (!this.hasLayer(renderer)) {
7372                         this.addLayer(renderer);
7373                 }
7374                 return renderer;
7375         },
7376
7377         _getPaneRenderer: function (name) {
7378                 if (name === 'overlayPane' || name === undefined) {
7379                         return false;
7380                 }
7381
7382                 var renderer = this._paneRenderers[name];
7383                 if (renderer === undefined) {
7384                         renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
7385                         this._paneRenderers[name] = renderer;
7386                 }
7387                 return renderer;
7388         }
7389 });
7390
7391
7392
7393 /*
7394  * @class Path
7395  * @aka L.Path
7396  * @inherits Interactive layer
7397  *
7398  * An abstract class that contains options and constants shared between vector
7399  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7400  */
7401
7402 L.Path = L.Layer.extend({
7403
7404         // @section
7405         // @aka Path options
7406         options: {
7407                 // @option stroke: Boolean = true
7408                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7409                 stroke: true,
7410
7411                 // @option color: String = '#3388ff'
7412                 // Stroke color
7413                 color: '#3388ff',
7414
7415                 // @option weight: Number = 3
7416                 // Stroke width in pixels
7417                 weight: 3,
7418
7419                 // @option opacity: Number = 1.0
7420                 // Stroke opacity
7421                 opacity: 1,
7422
7423                 // @option lineCap: String= 'round'
7424                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7425                 lineCap: 'round',
7426
7427                 // @option lineJoin: String = 'round'
7428                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7429                 lineJoin: 'round',
7430
7431                 // @option dashArray: String = null
7432                 // 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).
7433                 dashArray: null,
7434
7435                 // @option dashOffset: String = null
7436                 // 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).
7437                 dashOffset: null,
7438
7439                 // @option fill: Boolean = depends
7440                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7441                 fill: false,
7442
7443                 // @option fillColor: String = *
7444                 // Fill color. Defaults to the value of the [`color`](#path-color) option
7445                 fillColor: null,
7446
7447                 // @option fillOpacity: Number = 0.2
7448                 // Fill opacity.
7449                 fillOpacity: 0.2,
7450
7451                 // @option fillRule: String = 'evenodd'
7452                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7453                 fillRule: 'evenodd',
7454
7455                 // className: '',
7456
7457                 // Option inherited from "Interactive layer" abstract class
7458                 interactive: true
7459         },
7460
7461         beforeAdd: function (map) {
7462                 // Renderer is set here because we need to call renderer.getEvents
7463                 // before this.getEvents.
7464                 this._renderer = map.getRenderer(this);
7465         },
7466
7467         onAdd: function () {
7468                 this._renderer._initPath(this);
7469                 this._reset();
7470                 this._renderer._addPath(this);
7471                 this._renderer.on('update', this._update, this);
7472         },
7473
7474         onRemove: function () {
7475                 this._renderer._removePath(this);
7476                 this._renderer.off('update', this._update, this);
7477         },
7478
7479         getEvents: function () {
7480                 return {
7481                         zoomend: this._project,
7482                         viewreset: this._reset
7483                 };
7484         },
7485
7486         // @method redraw(): this
7487         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7488         redraw: function () {
7489                 if (this._map) {
7490                         this._renderer._updatePath(this);
7491                 }
7492                 return this;
7493         },
7494
7495         // @method setStyle(style: Path options): this
7496         // Changes the appearance of a Path based on the options in the `Path options` object.
7497         setStyle: function (style) {
7498                 L.setOptions(this, style);
7499                 if (this._renderer) {
7500                         this._renderer._updateStyle(this);
7501                 }
7502                 return this;
7503         },
7504
7505         // @method bringToFront(): this
7506         // Brings the layer to the top of all path layers.
7507         bringToFront: function () {
7508                 if (this._renderer) {
7509                         this._renderer._bringToFront(this);
7510                 }
7511                 return this;
7512         },
7513
7514         // @method bringToBack(): this
7515         // Brings the layer to the bottom of all path layers.
7516         bringToBack: function () {
7517                 if (this._renderer) {
7518                         this._renderer._bringToBack(this);
7519                 }
7520                 return this;
7521         },
7522
7523         getElement: function () {
7524                 return this._path;
7525         },
7526
7527         _reset: function () {
7528                 // defined in children classes
7529                 this._project();
7530                 this._update();
7531         },
7532
7533         _clickTolerance: function () {
7534                 // used when doing hit detection for Canvas layers
7535                 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
7536         }
7537 });
7538
7539
7540
7541 /*
7542  * @namespace LineUtil
7543  *
7544  * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
7545  */
7546
7547 L.LineUtil = {
7548
7549         // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
7550         // Improves rendering performance dramatically by lessening the number of points to draw.
7551
7552         // @function simplify(points: Point[], tolerance: Number): Point[]
7553         // Dramatically reduces the number of points in a polyline while retaining
7554         // its shape and returns a new array of simplified points, using the
7555         // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
7556         // Used for a huge performance boost when processing/displaying Leaflet polylines for
7557         // each zoom level and also reducing visual noise. tolerance affects the amount of
7558         // simplification (lesser value means higher quality but slower and with more points).
7559         // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
7560         simplify: function (points, tolerance) {
7561                 if (!tolerance || !points.length) {
7562                         return points.slice();
7563                 }
7564
7565                 var sqTolerance = tolerance * tolerance;
7566
7567                 // stage 1: vertex reduction
7568                 points = this._reducePoints(points, sqTolerance);
7569
7570                 // stage 2: Douglas-Peucker simplification
7571                 points = this._simplifyDP(points, sqTolerance);
7572
7573                 return points;
7574         },
7575
7576         // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
7577         // Returns the distance between point `p` and segment `p1` to `p2`.
7578         pointToSegmentDistance:  function (p, p1, p2) {
7579                 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
7580         },
7581
7582         // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
7583         // Returns the closest point from a point `p` on a segment `p1` to `p2`.
7584         closestPointOnSegment: function (p, p1, p2) {
7585                 return this._sqClosestPointOnSegment(p, p1, p2);
7586         },
7587
7588         // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
7589         _simplifyDP: function (points, sqTolerance) {
7590
7591                 var len = points.length,
7592                     ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
7593                     markers = new ArrayConstructor(len);
7594
7595                 markers[0] = markers[len - 1] = 1;
7596
7597                 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
7598
7599                 var i,
7600                     newPoints = [];
7601
7602                 for (i = 0; i < len; i++) {
7603                         if (markers[i]) {
7604                                 newPoints.push(points[i]);
7605                         }
7606                 }
7607
7608                 return newPoints;
7609         },
7610
7611         _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
7612
7613                 var maxSqDist = 0,
7614                     index, i, sqDist;
7615
7616                 for (i = first + 1; i <= last - 1; i++) {
7617                         sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
7618
7619                         if (sqDist > maxSqDist) {
7620                                 index = i;
7621                                 maxSqDist = sqDist;
7622                         }
7623                 }
7624
7625                 if (maxSqDist > sqTolerance) {
7626                         markers[index] = 1;
7627
7628                         this._simplifyDPStep(points, markers, sqTolerance, first, index);
7629                         this._simplifyDPStep(points, markers, sqTolerance, index, last);
7630                 }
7631         },
7632
7633         // reduce points that are too close to each other to a single point
7634         _reducePoints: function (points, sqTolerance) {
7635                 var reducedPoints = [points[0]];
7636
7637                 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
7638                         if (this._sqDist(points[i], points[prev]) > sqTolerance) {
7639                                 reducedPoints.push(points[i]);
7640                                 prev = i;
7641                         }
7642                 }
7643                 if (prev < len - 1) {
7644                         reducedPoints.push(points[len - 1]);
7645                 }
7646                 return reducedPoints;
7647         },
7648
7649
7650         // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
7651         // Clips the segment a to b by rectangular bounds with the
7652         // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
7653         // (modifying the segment points directly!). Used by Leaflet to only show polyline
7654         // points that are on the screen or near, increasing performance.
7655         clipSegment: function (a, b, bounds, useLastCode, round) {
7656                 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
7657                     codeB = this._getBitCode(b, bounds),
7658
7659                     codeOut, p, newCode;
7660
7661                 // save 2nd code to avoid calculating it on the next segment
7662                 this._lastCode = codeB;
7663
7664                 while (true) {
7665                         // if a,b is inside the clip window (trivial accept)
7666                         if (!(codeA | codeB)) {
7667                                 return [a, b];
7668                         }
7669
7670                         // if a,b is outside the clip window (trivial reject)
7671                         if (codeA & codeB) {
7672                                 return false;
7673                         }
7674
7675                         // other cases
7676                         codeOut = codeA || codeB;
7677                         p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
7678                         newCode = this._getBitCode(p, bounds);
7679
7680                         if (codeOut === codeA) {
7681                                 a = p;
7682                                 codeA = newCode;
7683                         } else {
7684                                 b = p;
7685                                 codeB = newCode;
7686                         }
7687                 }
7688         },
7689
7690         _getEdgeIntersection: function (a, b, code, bounds, round) {
7691                 var dx = b.x - a.x,
7692                     dy = b.y - a.y,
7693                     min = bounds.min,
7694                     max = bounds.max,
7695                     x, y;
7696
7697                 if (code & 8) { // top
7698                         x = a.x + dx * (max.y - a.y) / dy;
7699                         y = max.y;
7700
7701                 } else if (code & 4) { // bottom
7702                         x = a.x + dx * (min.y - a.y) / dy;
7703                         y = min.y;
7704
7705                 } else if (code & 2) { // right
7706                         x = max.x;
7707                         y = a.y + dy * (max.x - a.x) / dx;
7708
7709                 } else if (code & 1) { // left
7710                         x = min.x;
7711                         y = a.y + dy * (min.x - a.x) / dx;
7712                 }
7713
7714                 return new L.Point(x, y, round);
7715         },
7716
7717         _getBitCode: function (p, bounds) {
7718                 var code = 0;
7719
7720                 if (p.x < bounds.min.x) { // left
7721                         code |= 1;
7722                 } else if (p.x > bounds.max.x) { // right
7723                         code |= 2;
7724                 }
7725
7726                 if (p.y < bounds.min.y) { // bottom
7727                         code |= 4;
7728                 } else if (p.y > bounds.max.y) { // top
7729                         code |= 8;
7730                 }
7731
7732                 return code;
7733         },
7734
7735         // square distance (to avoid unnecessary Math.sqrt calls)
7736         _sqDist: function (p1, p2) {
7737                 var dx = p2.x - p1.x,
7738                     dy = p2.y - p1.y;
7739                 return dx * dx + dy * dy;
7740         },
7741
7742         // return closest point on segment or distance to that point
7743         _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
7744                 var x = p1.x,
7745                     y = p1.y,
7746                     dx = p2.x - x,
7747                     dy = p2.y - y,
7748                     dot = dx * dx + dy * dy,
7749                     t;
7750
7751                 if (dot > 0) {
7752                         t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
7753
7754                         if (t > 1) {
7755                                 x = p2.x;
7756                                 y = p2.y;
7757                         } else if (t > 0) {
7758                                 x += dx * t;
7759                                 y += dy * t;
7760                         }
7761                 }
7762
7763                 dx = p.x - x;
7764                 dy = p.y - y;
7765
7766                 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
7767         }
7768 };
7769
7770
7771
7772 /*
7773  * @class Polyline
7774  * @aka L.Polyline
7775  * @inherits Path
7776  *
7777  * A class for drawing polyline overlays on a map. Extends `Path`.
7778  *
7779  * @example
7780  *
7781  * ```js
7782  * // create a red polyline from an array of LatLng points
7783  * var latlngs = [
7784  *      [-122.68, 45.51],
7785  *      [-122.43, 37.77],
7786  *      [-118.2, 34.04]
7787  * ];
7788  *
7789  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7790  *
7791  * // zoom the map to the polyline
7792  * map.fitBounds(polyline.getBounds());
7793  * ```
7794  *
7795  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7796  *
7797  * ```js
7798  * // create a red polyline from an array of arrays of LatLng points
7799  * var latlngs = [
7800  *      [[-122.68, 45.51],
7801  *       [-122.43, 37.77],
7802  *       [-118.2, 34.04]],
7803  *      [[-73.91, 40.78],
7804  *       [-87.62, 41.83],
7805  *       [-96.72, 32.76]]
7806  * ];
7807  * ```
7808  */
7809
7810 L.Polyline = L.Path.extend({
7811
7812         // @section
7813         // @aka Polyline options
7814         options: {
7815                 // @option smoothFactor: Number = 1.0
7816                 // How much to simplify the polyline on each zoom level. More means
7817                 // better performance and smoother look, and less means more accurate representation.
7818                 smoothFactor: 1.0,
7819
7820                 // @option noClip: Boolean = false
7821                 // Disable polyline clipping.
7822                 noClip: false
7823         },
7824
7825         initialize: function (latlngs, options) {
7826                 L.setOptions(this, options);
7827                 this._setLatLngs(latlngs);
7828         },
7829
7830         // @method getLatLngs(): LatLng[]
7831         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
7832         getLatLngs: function () {
7833                 return this._latlngs;
7834         },
7835
7836         // @method setLatLngs(latlngs: LatLng[]): this
7837         // Replaces all the points in the polyline with the given array of geographical points.
7838         setLatLngs: function (latlngs) {
7839                 this._setLatLngs(latlngs);
7840                 return this.redraw();
7841         },
7842
7843         // @method isEmpty(): Boolean
7844         // Returns `true` if the Polyline has no LatLngs.
7845         isEmpty: function () {
7846                 return !this._latlngs.length;
7847         },
7848
7849         closestLayerPoint: function (p) {
7850                 var minDistance = Infinity,
7851                     minPoint = null,
7852                     closest = L.LineUtil._sqClosestPointOnSegment,
7853                     p1, p2;
7854
7855                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
7856                         var points = this._parts[j];
7857
7858                         for (var i = 1, len = points.length; i < len; i++) {
7859                                 p1 = points[i - 1];
7860                                 p2 = points[i];
7861
7862                                 var sqDist = closest(p, p1, p2, true);
7863
7864                                 if (sqDist < minDistance) {
7865                                         minDistance = sqDist;
7866                                         minPoint = closest(p, p1, p2);
7867                                 }
7868                         }
7869                 }
7870                 if (minPoint) {
7871                         minPoint.distance = Math.sqrt(minDistance);
7872                 }
7873                 return minPoint;
7874         },
7875
7876         // @method getCenter(): LatLng
7877         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
7878         getCenter: function () {
7879                 // throws error when not yet added to map as this center calculation requires projected coordinates
7880                 if (!this._map) {
7881                         throw new Error('Must add layer to map before using getCenter()');
7882                 }
7883
7884                 var i, halfDist, segDist, dist, p1, p2, ratio,
7885                     points = this._rings[0],
7886                     len = points.length;
7887
7888                 if (!len) { return null; }
7889
7890                 // polyline centroid algorithm; only uses the first ring if there are multiple
7891
7892                 for (i = 0, halfDist = 0; i < len - 1; i++) {
7893                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
7894                 }
7895
7896                 // The line is so small in the current view that all points are on the same pixel.
7897                 if (halfDist === 0) {
7898                         return this._map.layerPointToLatLng(points[0]);
7899                 }
7900
7901                 for (i = 0, dist = 0; i < len - 1; i++) {
7902                         p1 = points[i];
7903                         p2 = points[i + 1];
7904                         segDist = p1.distanceTo(p2);
7905                         dist += segDist;
7906
7907                         if (dist > halfDist) {
7908                                 ratio = (dist - halfDist) / segDist;
7909                                 return this._map.layerPointToLatLng([
7910                                         p2.x - ratio * (p2.x - p1.x),
7911                                         p2.y - ratio * (p2.y - p1.y)
7912                                 ]);
7913                         }
7914                 }
7915         },
7916
7917         // @method getBounds(): LatLngBounds
7918         // Returns the `LatLngBounds` of the path.
7919         getBounds: function () {
7920                 return this._bounds;
7921         },
7922
7923         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
7924         // Adds a given point to the polyline. By default, adds to the first ring of
7925         // the polyline in case of a multi-polyline, but can be overridden by passing
7926         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
7927         addLatLng: function (latlng, latlngs) {
7928                 latlngs = latlngs || this._defaultShape();
7929                 latlng = L.latLng(latlng);
7930                 latlngs.push(latlng);
7931                 this._bounds.extend(latlng);
7932                 return this.redraw();
7933         },
7934
7935         _setLatLngs: function (latlngs) {
7936                 this._bounds = new L.LatLngBounds();
7937                 this._latlngs = this._convertLatLngs(latlngs);
7938         },
7939
7940         _defaultShape: function () {
7941                 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
7942         },
7943
7944         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
7945         _convertLatLngs: function (latlngs) {
7946                 var result = [],
7947                     flat = L.Polyline._flat(latlngs);
7948
7949                 for (var i = 0, len = latlngs.length; i < len; i++) {
7950                         if (flat) {
7951                                 result[i] = L.latLng(latlngs[i]);
7952                                 this._bounds.extend(result[i]);
7953                         } else {
7954                                 result[i] = this._convertLatLngs(latlngs[i]);
7955                         }
7956                 }
7957
7958                 return result;
7959         },
7960
7961         _project: function () {
7962                 var pxBounds = new L.Bounds();
7963                 this._rings = [];
7964                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
7965
7966                 var w = this._clickTolerance(),
7967                     p = new L.Point(w, w);
7968
7969                 if (this._bounds.isValid() && pxBounds.isValid()) {
7970                         pxBounds.min._subtract(p);
7971                         pxBounds.max._add(p);
7972                         this._pxBounds = pxBounds;
7973                 }
7974         },
7975
7976         // recursively turns latlngs into a set of rings with projected coordinates
7977         _projectLatlngs: function (latlngs, result, projectedBounds) {
7978                 var flat = latlngs[0] instanceof L.LatLng,
7979                     len = latlngs.length,
7980                     i, ring;
7981
7982                 if (flat) {
7983                         ring = [];
7984                         for (i = 0; i < len; i++) {
7985                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
7986                                 projectedBounds.extend(ring[i]);
7987                         }
7988                         result.push(ring);
7989                 } else {
7990                         for (i = 0; i < len; i++) {
7991                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
7992                         }
7993                 }
7994         },
7995
7996         // clip polyline by renderer bounds so that we have less to render for performance
7997         _clipPoints: function () {
7998                 var bounds = this._renderer._bounds;
7999
8000                 this._parts = [];
8001                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8002                         return;
8003                 }
8004
8005                 if (this.options.noClip) {
8006                         this._parts = this._rings;
8007                         return;
8008                 }
8009
8010                 var parts = this._parts,
8011                     i, j, k, len, len2, segment, points;
8012
8013                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8014                         points = this._rings[i];
8015
8016                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8017                                 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8018
8019                                 if (!segment) { continue; }
8020
8021                                 parts[k] = parts[k] || [];
8022                                 parts[k].push(segment[0]);
8023
8024                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8025                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8026                                         parts[k].push(segment[1]);
8027                                         k++;
8028                                 }
8029                         }
8030                 }
8031         },
8032
8033         // simplify each clipped part of the polyline for performance
8034         _simplifyPoints: function () {
8035                 var parts = this._parts,
8036                     tolerance = this.options.smoothFactor;
8037
8038                 for (var i = 0, len = parts.length; i < len; i++) {
8039                         parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8040                 }
8041         },
8042
8043         _update: function () {
8044                 if (!this._map) { return; }
8045
8046                 this._clipPoints();
8047                 this._simplifyPoints();
8048                 this._updatePath();
8049         },
8050
8051         _updatePath: function () {
8052                 this._renderer._updatePoly(this);
8053         }
8054 });
8055
8056 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8057 // Instantiates a polyline object given an array of geographical points and
8058 // optionally an options object. You can create a `Polyline` object with
8059 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8060 // of geographic points.
8061 L.polyline = function (latlngs, options) {
8062         return new L.Polyline(latlngs, options);
8063 };
8064
8065 L.Polyline._flat = function (latlngs) {
8066         // true if it's a flat array of latlngs; false if nested
8067         return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8068 };
8069
8070
8071
8072 /*
8073  * @namespace PolyUtil
8074  * Various utility functions for polygon geometries.
8075  */
8076
8077 L.PolyUtil = {};
8078
8079 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8080  * 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)).
8081  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8082  * performance. Note that polygon points needs different algorithm for clipping
8083  * than polyline, so there's a seperate method for it.
8084  */
8085 L.PolyUtil.clipPolygon = function (points, bounds, round) {
8086         var clippedPoints,
8087             edges = [1, 4, 2, 8],
8088             i, j, k,
8089             a, b,
8090             len, edge, p,
8091             lu = L.LineUtil;
8092
8093         for (i = 0, len = points.length; i < len; i++) {
8094                 points[i]._code = lu._getBitCode(points[i], bounds);
8095         }
8096
8097         // for each edge (left, bottom, right, top)
8098         for (k = 0; k < 4; k++) {
8099                 edge = edges[k];
8100                 clippedPoints = [];
8101
8102                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8103                         a = points[i];
8104                         b = points[j];
8105
8106                         // if a is inside the clip window
8107                         if (!(a._code & edge)) {
8108                                 // if b is outside the clip window (a->b goes out of screen)
8109                                 if (b._code & edge) {
8110                                         p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8111                                         p._code = lu._getBitCode(p, bounds);
8112                                         clippedPoints.push(p);
8113                                 }
8114                                 clippedPoints.push(a);
8115
8116                         // else if b is inside the clip window (a->b enters the screen)
8117                         } else if (!(b._code & edge)) {
8118                                 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8119                                 p._code = lu._getBitCode(p, bounds);
8120                                 clippedPoints.push(p);
8121                         }
8122                 }
8123                 points = clippedPoints;
8124         }
8125
8126         return points;
8127 };
8128
8129
8130
8131 /*
8132  * @class Polygon
8133  * @aka L.Polygon
8134  * @inherits Polyline
8135  *
8136  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8137  *
8138  * 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.
8139  *
8140  *
8141  * @example
8142  *
8143  * ```js
8144  * // create a red polygon from an array of LatLng points
8145  * var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]];
8146  *
8147  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8148  *
8149  * // zoom the map to the polygon
8150  * map.fitBounds(polygon.getBounds());
8151  * ```
8152  *
8153  * 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:
8154  *
8155  * ```js
8156  * var latlngs = [
8157  *   [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8158  *   [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8159  * ];
8160  * ```
8161  *
8162  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8163  *
8164  * ```js
8165  * var latlngs = [
8166  *   [ // first polygon
8167  *     [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8168  *     [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8169  *   ],
8170  *   [ // second polygon
8171  *     [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]]
8172  *   ]
8173  * ];
8174  * ```
8175  */
8176
8177 L.Polygon = L.Polyline.extend({
8178
8179         options: {
8180                 fill: true
8181         },
8182
8183         isEmpty: function () {
8184                 return !this._latlngs.length || !this._latlngs[0].length;
8185         },
8186
8187         getCenter: function () {
8188                 // throws error when not yet added to map as this center calculation requires projected coordinates
8189                 if (!this._map) {
8190                         throw new Error('Must add layer to map before using getCenter()');
8191                 }
8192
8193                 var i, j, p1, p2, f, area, x, y, center,
8194                     points = this._rings[0],
8195                     len = points.length;
8196
8197                 if (!len) { return null; }
8198
8199                 // polygon centroid algorithm; only uses the first ring if there are multiple
8200
8201                 area = x = y = 0;
8202
8203                 for (i = 0, j = len - 1; i < len; j = i++) {
8204                         p1 = points[i];
8205                         p2 = points[j];
8206
8207                         f = p1.y * p2.x - p2.y * p1.x;
8208                         x += (p1.x + p2.x) * f;
8209                         y += (p1.y + p2.y) * f;
8210                         area += f * 3;
8211                 }
8212
8213                 if (area === 0) {
8214                         // Polygon is so small that all points are on same pixel.
8215                         center = points[0];
8216                 } else {
8217                         center = [x / area, y / area];
8218                 }
8219                 return this._map.layerPointToLatLng(center);
8220         },
8221
8222         _convertLatLngs: function (latlngs) {
8223                 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
8224                     len = result.length;
8225
8226                 // remove last point if it equals first one
8227                 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
8228                         result.pop();
8229                 }
8230                 return result;
8231         },
8232
8233         _setLatLngs: function (latlngs) {
8234                 L.Polyline.prototype._setLatLngs.call(this, latlngs);
8235                 if (L.Polyline._flat(this._latlngs)) {
8236                         this._latlngs = [this._latlngs];
8237                 }
8238         },
8239
8240         _defaultShape: function () {
8241                 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8242         },
8243
8244         _clipPoints: function () {
8245                 // polygons need a different clipping algorithm so we redefine that
8246
8247                 var bounds = this._renderer._bounds,
8248                     w = this.options.weight,
8249                     p = new L.Point(w, w);
8250
8251                 // increase clip padding by stroke width to avoid stroke on clip edges
8252                 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
8253
8254                 this._parts = [];
8255                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8256                         return;
8257                 }
8258
8259                 if (this.options.noClip) {
8260                         this._parts = this._rings;
8261                         return;
8262                 }
8263
8264                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8265                         clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
8266                         if (clipped.length) {
8267                                 this._parts.push(clipped);
8268                         }
8269                 }
8270         },
8271
8272         _updatePath: function () {
8273                 this._renderer._updatePoly(this, true);
8274         }
8275 });
8276
8277
8278 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8279 L.polygon = function (latlngs, options) {
8280         return new L.Polygon(latlngs, options);
8281 };
8282
8283
8284
8285 /*
8286  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
8287  */
8288
8289 /*
8290  * @class Rectangle
8291  * @aka L.Retangle
8292  * @inherits Polygon
8293  *
8294  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
8295  *
8296  * @example
8297  *
8298  * ```js
8299  * // define rectangle geographical bounds
8300  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
8301  *
8302  * // create an orange rectangle
8303  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
8304  *
8305  * // zoom the map to the rectangle bounds
8306  * map.fitBounds(bounds);
8307  * ```
8308  *
8309  */
8310
8311
8312 L.Rectangle = L.Polygon.extend({
8313         initialize: function (latLngBounds, options) {
8314                 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
8315         },
8316
8317         // @method setBounds(latLngBounds: LatLngBounds): this
8318         // Redraws the rectangle with the passed bounds.
8319         setBounds: function (latLngBounds) {
8320                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
8321         },
8322
8323         _boundsToLatLngs: function (latLngBounds) {
8324                 latLngBounds = L.latLngBounds(latLngBounds);
8325                 return [
8326                         latLngBounds.getSouthWest(),
8327                         latLngBounds.getNorthWest(),
8328                         latLngBounds.getNorthEast(),
8329                         latLngBounds.getSouthEast()
8330                 ];
8331         }
8332 });
8333
8334
8335 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
8336 L.rectangle = function (latLngBounds, options) {
8337         return new L.Rectangle(latLngBounds, options);
8338 };
8339
8340
8341
8342 /*
8343  * @class CircleMarker
8344  * @aka L.CircleMarker
8345  * @inherits Path
8346  *
8347  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8348  */
8349
8350 L.CircleMarker = L.Path.extend({
8351
8352         // @section
8353         // @aka CircleMarker options
8354         options: {
8355                 fill: true,
8356
8357                 // @option radius: Number = 10
8358                 // Radius of the circle marker, in pixels
8359                 radius: 10
8360         },
8361
8362         initialize: function (latlng, options) {
8363                 L.setOptions(this, options);
8364                 this._latlng = L.latLng(latlng);
8365                 this._radius = this.options.radius;
8366         },
8367
8368         // @method setLatLng(latLng: LatLng): this
8369         // Sets the position of a circle marker to a new location.
8370         setLatLng: function (latlng) {
8371                 this._latlng = L.latLng(latlng);
8372                 this.redraw();
8373                 return this.fire('move', {latlng: this._latlng});
8374         },
8375
8376         // @method getLatLng(): LatLng
8377         // Returns the current geographical position of the circle marker
8378         getLatLng: function () {
8379                 return this._latlng;
8380         },
8381
8382         // @method setRadius(radius: Number): this
8383         // Sets the radius of a circle marker. Units are in pixels.
8384         setRadius: function (radius) {
8385                 this.options.radius = this._radius = radius;
8386                 return this.redraw();
8387         },
8388
8389         // @method getRadius(): Number
8390         // Returns the current radius of the circle
8391         getRadius: function () {
8392                 return this._radius;
8393         },
8394
8395         setStyle : function (options) {
8396                 var radius = options && options.radius || this._radius;
8397                 L.Path.prototype.setStyle.call(this, options);
8398                 this.setRadius(radius);
8399                 return this;
8400         },
8401
8402         _project: function () {
8403                 this._point = this._map.latLngToLayerPoint(this._latlng);
8404                 this._updateBounds();
8405         },
8406
8407         _updateBounds: function () {
8408                 var r = this._radius,
8409                     r2 = this._radiusY || r,
8410                     w = this._clickTolerance(),
8411                     p = [r + w, r2 + w];
8412                 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
8413         },
8414
8415         _update: function () {
8416                 if (this._map) {
8417                         this._updatePath();
8418                 }
8419         },
8420
8421         _updatePath: function () {
8422                 this._renderer._updateCircle(this);
8423         },
8424
8425         _empty: function () {
8426                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8427         }
8428 });
8429
8430
8431 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8432 // Instantiates a circle marker object given a geographical point, and an optional options object.
8433 L.circleMarker = function (latlng, options) {
8434         return new L.CircleMarker(latlng, options);
8435 };
8436
8437
8438
8439 /*
8440  * @class Circle
8441  * @aka L.Circle
8442  * @inherits CircleMarker
8443  *
8444  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8445  *
8446  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8447  *
8448  * @example
8449  *
8450  * ```js
8451  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8452  * ```
8453  */
8454
8455 L.Circle = L.CircleMarker.extend({
8456
8457         initialize: function (latlng, options, legacyOptions) {
8458                 if (typeof options === 'number') {
8459                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8460                         options = L.extend({}, legacyOptions, {radius: options});
8461                 }
8462                 L.setOptions(this, options);
8463                 this._latlng = L.latLng(latlng);
8464
8465                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8466
8467                 // @section
8468                 // @aka Circle options
8469                 // @option radius: Number; Radius of the circle, in meters.
8470                 this._mRadius = this.options.radius;
8471         },
8472
8473         // @method setRadius(radius: Number): this
8474         // Sets the radius of a circle. Units are in meters.
8475         setRadius: function (radius) {
8476                 this._mRadius = radius;
8477                 return this.redraw();
8478         },
8479
8480         // @method getRadius(): Number
8481         // Returns the current radius of a circle. Units are in meters.
8482         getRadius: function () {
8483                 return this._mRadius;
8484         },
8485
8486         // @method getBounds(): LatLngBounds
8487         // Returns the `LatLngBounds` of the path.
8488         getBounds: function () {
8489                 var half = [this._radius, this._radiusY || this._radius];
8490
8491                 return new L.LatLngBounds(
8492                         this._map.layerPointToLatLng(this._point.subtract(half)),
8493                         this._map.layerPointToLatLng(this._point.add(half)));
8494         },
8495
8496         setStyle: L.Path.prototype.setStyle,
8497
8498         _project: function () {
8499
8500                 var lng = this._latlng.lng,
8501                     lat = this._latlng.lat,
8502                     map = this._map,
8503                     crs = map.options.crs;
8504
8505                 if (crs.distance === L.CRS.Earth.distance) {
8506                         var d = Math.PI / 180,
8507                             latR = (this._mRadius / L.CRS.Earth.R) / d,
8508                             top = map.project([lat + latR, lng]),
8509                             bottom = map.project([lat - latR, lng]),
8510                             p = top.add(bottom).divideBy(2),
8511                             lat2 = map.unproject(p).lat,
8512                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8513                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8514
8515                         if (isNaN(lngR) || lngR === 0) {
8516                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8517                         }
8518
8519                         this._point = p.subtract(map.getPixelOrigin());
8520                         this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
8521                         this._radiusY = Math.max(Math.round(p.y - top.y), 1);
8522
8523                 } else {
8524                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8525
8526                         this._point = map.latLngToLayerPoint(this._latlng);
8527                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8528                 }
8529
8530                 this._updateBounds();
8531         }
8532 });
8533
8534 // @factory L.circle(latlng: LatLng, options?: Circle options)
8535 // Instantiates a circle object given a geographical point, and an options object
8536 // which contains the circle radius.
8537 // @alternative
8538 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8539 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8540 // Do not use in new applications or plugins.
8541 L.circle = function (latlng, options, legacyOptions) {
8542         return new L.Circle(latlng, options, legacyOptions);
8543 };
8544
8545
8546
8547 /*
8548  * @class SVG
8549  * @inherits Renderer
8550  * @aka L.SVG
8551  *
8552  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
8553  * Inherits `Renderer`.
8554  *
8555  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
8556  * available in all web browsers, notably Android 2.x and 3.x.
8557  *
8558  * Although SVG is not available on IE7 and IE8, these browsers support
8559  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
8560  * (a now deprecated technology), and the SVG renderer will fall back to VML in
8561  * this case.
8562  *
8563  * @example
8564  *
8565  * Use SVG by default for all paths in the map:
8566  *
8567  * ```js
8568  * var map = L.map('map', {
8569  *      renderer: L.svg()
8570  * });
8571  * ```
8572  *
8573  * Use a SVG renderer with extra padding for specific vector geometries:
8574  *
8575  * ```js
8576  * var map = L.map('map');
8577  * var myRenderer = L.svg({ padding: 0.5 });
8578  * var line = L.polyline( coordinates, { renderer: myRenderer } );
8579  * var circle = L.circle( center, { renderer: myRenderer } );
8580  * ```
8581  */
8582
8583 L.SVG = L.Renderer.extend({
8584
8585         getEvents: function () {
8586                 var events = L.Renderer.prototype.getEvents.call(this);
8587                 events.zoomstart = this._onZoomStart;
8588                 return events;
8589         },
8590
8591         _initContainer: function () {
8592                 this._container = L.SVG.create('svg');
8593
8594                 // makes it possible to click through svg root; we'll reset it back in individual paths
8595                 this._container.setAttribute('pointer-events', 'none');
8596
8597                 this._rootGroup = L.SVG.create('g');
8598                 this._container.appendChild(this._rootGroup);
8599         },
8600
8601         _onZoomStart: function () {
8602                 // Drag-then-pinch interactions might mess up the center and zoom.
8603                 // In this case, the easiest way to prevent this is re-do the renderer
8604                 //   bounds and padding when the zooming starts.
8605                 this._update();
8606         },
8607
8608         _update: function () {
8609                 if (this._map._animatingZoom && this._bounds) { return; }
8610
8611                 L.Renderer.prototype._update.call(this);
8612
8613                 var b = this._bounds,
8614                     size = b.getSize(),
8615                     container = this._container;
8616
8617                 // set size of svg-container if changed
8618                 if (!this._svgSize || !this._svgSize.equals(size)) {
8619                         this._svgSize = size;
8620                         container.setAttribute('width', size.x);
8621                         container.setAttribute('height', size.y);
8622                 }
8623
8624                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
8625                 L.DomUtil.setPosition(container, b.min);
8626                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
8627
8628                 this.fire('update');
8629         },
8630
8631         // methods below are called by vector layers implementations
8632
8633         _initPath: function (layer) {
8634                 var path = layer._path = L.SVG.create('path');
8635
8636                 // @namespace Path
8637                 // @option className: String = null
8638                 // Custom class name set on an element. Only for SVG renderer.
8639                 if (layer.options.className) {
8640                         L.DomUtil.addClass(path, layer.options.className);
8641                 }
8642
8643                 if (layer.options.interactive) {
8644                         L.DomUtil.addClass(path, 'leaflet-interactive');
8645                 }
8646
8647                 this._updateStyle(layer);
8648         },
8649
8650         _addPath: function (layer) {
8651                 this._rootGroup.appendChild(layer._path);
8652                 layer.addInteractiveTarget(layer._path);
8653         },
8654
8655         _removePath: function (layer) {
8656                 L.DomUtil.remove(layer._path);
8657                 layer.removeInteractiveTarget(layer._path);
8658         },
8659
8660         _updatePath: function (layer) {
8661                 layer._project();
8662                 layer._update();
8663         },
8664
8665         _updateStyle: function (layer) {
8666                 var path = layer._path,
8667                     options = layer.options;
8668
8669                 if (!path) { return; }
8670
8671                 if (options.stroke) {
8672                         path.setAttribute('stroke', options.color);
8673                         path.setAttribute('stroke-opacity', options.opacity);
8674                         path.setAttribute('stroke-width', options.weight);
8675                         path.setAttribute('stroke-linecap', options.lineCap);
8676                         path.setAttribute('stroke-linejoin', options.lineJoin);
8677
8678                         if (options.dashArray) {
8679                                 path.setAttribute('stroke-dasharray', options.dashArray);
8680                         } else {
8681                                 path.removeAttribute('stroke-dasharray');
8682                         }
8683
8684                         if (options.dashOffset) {
8685                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
8686                         } else {
8687                                 path.removeAttribute('stroke-dashoffset');
8688                         }
8689                 } else {
8690                         path.setAttribute('stroke', 'none');
8691                 }
8692
8693                 if (options.fill) {
8694                         path.setAttribute('fill', options.fillColor || options.color);
8695                         path.setAttribute('fill-opacity', options.fillOpacity);
8696                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
8697                 } else {
8698                         path.setAttribute('fill', 'none');
8699                 }
8700         },
8701
8702         _updatePoly: function (layer, closed) {
8703                 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
8704         },
8705
8706         _updateCircle: function (layer) {
8707                 var p = layer._point,
8708                     r = layer._radius,
8709                     r2 = layer._radiusY || r,
8710                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
8711
8712                 // drawing a circle with two half-arcs
8713                 var d = layer._empty() ? 'M0 0' :
8714                                 'M' + (p.x - r) + ',' + p.y +
8715                                 arc + (r * 2) + ',0 ' +
8716                                 arc + (-r * 2) + ',0 ';
8717
8718                 this._setPath(layer, d);
8719         },
8720
8721         _setPath: function (layer, path) {
8722                 layer._path.setAttribute('d', path);
8723         },
8724
8725         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
8726         _bringToFront: function (layer) {
8727                 L.DomUtil.toFront(layer._path);
8728         },
8729
8730         _bringToBack: function (layer) {
8731                 L.DomUtil.toBack(layer._path);
8732         }
8733 });
8734
8735
8736 // @namespace SVG; @section
8737 // There are several static functions which can be called without instantiating L.SVG:
8738 L.extend(L.SVG, {
8739         // @function create(name: String): SVGElement
8740         // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
8741         // corresponding to the class name passed. For example, using 'line' will return
8742         // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
8743         create: function (name) {
8744                 return document.createElementNS('http://www.w3.org/2000/svg', name);
8745         },
8746
8747         // @function pointsToPath(rings: Point[], closed: Boolean): String
8748         // Generates a SVG path string for multiple rings, with each ring turning
8749         // into "M..L..L.." instructions
8750         pointsToPath: function (rings, closed) {
8751                 var str = '',
8752                     i, j, len, len2, points, p;
8753
8754                 for (i = 0, len = rings.length; i < len; i++) {
8755                         points = rings[i];
8756
8757                         for (j = 0, len2 = points.length; j < len2; j++) {
8758                                 p = points[j];
8759                                 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
8760                         }
8761
8762                         // closes the ring for polygons; "x" is VML syntax
8763                         str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
8764                 }
8765
8766                 // SVG complains about empty path strings
8767                 return str || 'M0 0';
8768         }
8769 });
8770
8771 // @namespace Browser; @property svg: Boolean
8772 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
8773 L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
8774
8775
8776 // @namespace SVG
8777 // @factory L.svg(options?: Renderer options)
8778 // Creates a SVG renderer with the given options.
8779 L.svg = function (options) {
8780         return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
8781 };
8782
8783
8784
8785 /*
8786  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
8787  */
8788
8789 /*
8790  * @class SVG
8791  *
8792  * 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.
8793  *
8794  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
8795  * with old versions of Internet Explorer.
8796  */
8797
8798 // @namespace Browser; @property vml: Boolean
8799 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
8800 L.Browser.vml = !L.Browser.svg && (function () {
8801         try {
8802                 var div = document.createElement('div');
8803                 div.innerHTML = '<v:shape adj="1"/>';
8804
8805                 var shape = div.firstChild;
8806                 shape.style.behavior = 'url(#default#VML)';
8807
8808                 return shape && (typeof shape.adj === 'object');
8809
8810         } catch (e) {
8811                 return false;
8812         }
8813 }());
8814
8815 // redefine some SVG methods to handle VML syntax which is similar but with some differences
8816 L.SVG.include(!L.Browser.vml ? {} : {
8817
8818         _initContainer: function () {
8819                 this._container = L.DomUtil.create('div', 'leaflet-vml-container');
8820         },
8821
8822         _update: function () {
8823                 if (this._map._animatingZoom) { return; }
8824                 L.Renderer.prototype._update.call(this);
8825                 this.fire('update');
8826         },
8827
8828         _initPath: function (layer) {
8829                 var container = layer._container = L.SVG.create('shape');
8830
8831                 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
8832
8833                 container.coordsize = '1 1';
8834
8835                 layer._path = L.SVG.create('path');
8836                 container.appendChild(layer._path);
8837
8838                 this._updateStyle(layer);
8839         },
8840
8841         _addPath: function (layer) {
8842                 var container = layer._container;
8843                 this._container.appendChild(container);
8844
8845                 if (layer.options.interactive) {
8846                         layer.addInteractiveTarget(container);
8847                 }
8848         },
8849
8850         _removePath: function (layer) {
8851                 var container = layer._container;
8852                 L.DomUtil.remove(container);
8853                 layer.removeInteractiveTarget(container);
8854         },
8855
8856         _updateStyle: function (layer) {
8857                 var stroke = layer._stroke,
8858                     fill = layer._fill,
8859                     options = layer.options,
8860                     container = layer._container;
8861
8862                 container.stroked = !!options.stroke;
8863                 container.filled = !!options.fill;
8864
8865                 if (options.stroke) {
8866                         if (!stroke) {
8867                                 stroke = layer._stroke = L.SVG.create('stroke');
8868                         }
8869                         container.appendChild(stroke);
8870                         stroke.weight = options.weight + 'px';
8871                         stroke.color = options.color;
8872                         stroke.opacity = options.opacity;
8873
8874                         if (options.dashArray) {
8875                                 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
8876                                     options.dashArray.join(' ') :
8877                                     options.dashArray.replace(/( *, *)/g, ' ');
8878                         } else {
8879                                 stroke.dashStyle = '';
8880                         }
8881                         stroke.endcap = options.lineCap.replace('butt', 'flat');
8882                         stroke.joinstyle = options.lineJoin;
8883
8884                 } else if (stroke) {
8885                         container.removeChild(stroke);
8886                         layer._stroke = null;
8887                 }
8888
8889                 if (options.fill) {
8890                         if (!fill) {
8891                                 fill = layer._fill = L.SVG.create('fill');
8892                         }
8893                         container.appendChild(fill);
8894                         fill.color = options.fillColor || options.color;
8895                         fill.opacity = options.fillOpacity;
8896
8897                 } else if (fill) {
8898                         container.removeChild(fill);
8899                         layer._fill = null;
8900                 }
8901         },
8902
8903         _updateCircle: function (layer) {
8904                 var p = layer._point.round(),
8905                     r = Math.round(layer._radius),
8906                     r2 = Math.round(layer._radiusY || r);
8907
8908                 this._setPath(layer, layer._empty() ? 'M0 0' :
8909                                 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
8910         },
8911
8912         _setPath: function (layer, path) {
8913                 layer._path.v = path;
8914         },
8915
8916         _bringToFront: function (layer) {
8917                 L.DomUtil.toFront(layer._container);
8918         },
8919
8920         _bringToBack: function (layer) {
8921                 L.DomUtil.toBack(layer._container);
8922         }
8923 });
8924
8925 if (L.Browser.vml) {
8926         L.SVG.create = (function () {
8927                 try {
8928                         document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
8929                         return function (name) {
8930                                 return document.createElement('<lvml:' + name + ' class="lvml">');
8931                         };
8932                 } catch (e) {
8933                         return function (name) {
8934                                 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
8935                         };
8936                 }
8937         })();
8938 }
8939
8940
8941
8942 /*
8943  * @class Canvas
8944  * @inherits Renderer
8945  * @aka L.Canvas
8946  *
8947  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
8948  * Inherits `Renderer`.
8949  *
8950  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
8951  * available in all web browsers, notably IE8, and overlapping geometries might
8952  * not display properly in some edge cases.
8953  *
8954  * @example
8955  *
8956  * Use Canvas by default for all paths in the map:
8957  *
8958  * ```js
8959  * var map = L.map('map', {
8960  *      renderer: L.canvas()
8961  * });
8962  * ```
8963  *
8964  * Use a Canvas renderer with extra padding for specific vector geometries:
8965  *
8966  * ```js
8967  * var map = L.map('map');
8968  * var myRenderer = L.canvas({ padding: 0.5 });
8969  * var line = L.polyline( coordinates, { renderer: myRenderer } );
8970  * var circle = L.circle( center, { renderer: myRenderer } );
8971  * ```
8972  */
8973
8974 L.Canvas = L.Renderer.extend({
8975
8976         onAdd: function () {
8977                 L.Renderer.prototype.onAdd.call(this);
8978
8979                 this._layers = this._layers || {};
8980
8981                 // Redraw vectors since canvas is cleared upon removal,
8982                 // in case of removing the renderer itself from the map.
8983                 this._draw();
8984         },
8985
8986         _initContainer: function () {
8987                 var container = this._container = document.createElement('canvas');
8988
8989                 L.DomEvent
8990                         .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
8991                         .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
8992                         .on(container, 'mouseout', this._handleMouseOut, this);
8993
8994                 this._ctx = container.getContext('2d');
8995         },
8996
8997         _update: function () {
8998                 if (this._map._animatingZoom && this._bounds) { return; }
8999
9000                 this._drawnLayers = {};
9001
9002                 L.Renderer.prototype._update.call(this);
9003
9004                 var b = this._bounds,
9005                     container = this._container,
9006                     size = b.getSize(),
9007                     m = L.Browser.retina ? 2 : 1;
9008
9009                 L.DomUtil.setPosition(container, b.min);
9010
9011                 // set canvas size (also clearing it); use double size on retina
9012                 container.width = m * size.x;
9013                 container.height = m * size.y;
9014                 container.style.width = size.x + 'px';
9015                 container.style.height = size.y + 'px';
9016
9017                 if (L.Browser.retina) {
9018                         this._ctx.scale(2, 2);
9019                 }
9020
9021                 // translate so we use the same path coordinates after canvas element moves
9022                 this._ctx.translate(-b.min.x, -b.min.y);
9023
9024                 // Tell paths to redraw themselves
9025                 this.fire('update');
9026         },
9027
9028         _initPath: function (layer) {
9029                 this._updateDashArray(layer);
9030                 this._layers[L.stamp(layer)] = layer;
9031         },
9032
9033         _addPath: L.Util.falseFn,
9034
9035         _removePath: function (layer) {
9036                 layer._removed = true;
9037                 this._requestRedraw(layer);
9038         },
9039
9040         _updatePath: function (layer) {
9041                 this._redrawBounds = layer._pxBounds;
9042                 this._draw(true);
9043                 layer._project();
9044                 layer._update();
9045                 this._draw();
9046                 this._redrawBounds = null;
9047         },
9048
9049         _updateStyle: function (layer) {
9050                 this._updateDashArray(layer);
9051                 this._requestRedraw(layer);
9052         },
9053
9054         _updateDashArray: function (layer) {
9055                 if (layer.options.dashArray) {
9056                         var parts = layer.options.dashArray.split(','),
9057                             dashArray = [],
9058                             i;
9059                         for (i = 0; i < parts.length; i++) {
9060                                 dashArray.push(Number(parts[i]));
9061                         }
9062                         layer.options._dashArray = dashArray;
9063                 }
9064         },
9065
9066         _requestRedraw: function (layer) {
9067                 if (!this._map) { return; }
9068
9069                 var padding = (layer.options.weight || 0) + 1;
9070                 this._redrawBounds = this._redrawBounds || new L.Bounds();
9071                 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
9072                 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
9073
9074                 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
9075         },
9076
9077         _redraw: function () {
9078                 this._redrawRequest = null;
9079
9080                 this._draw(true); // clear layers in redraw bounds
9081                 this._draw(); // draw layers
9082
9083                 this._redrawBounds = null;
9084         },
9085
9086         _draw: function (clear) {
9087                 this._clear = clear;
9088                 var layer, bounds = this._redrawBounds;
9089                 this._ctx.save();
9090                 if (bounds) {
9091                         this._ctx.beginPath();
9092                         this._ctx.rect(bounds.min.x, bounds.min.y, bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y);
9093                         this._ctx.clip();
9094                 }
9095
9096                 for (var id in this._layers) {
9097                         layer = this._layers[id];
9098                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
9099                                 layer._updatePath();
9100                         }
9101                         if (clear && layer._removed) {
9102                                 delete layer._removed;
9103                                 delete this._layers[id];
9104                         }
9105                 }
9106                 this._ctx.restore();  // Restore state before clipping.
9107         },
9108
9109         _updatePoly: function (layer, closed) {
9110
9111                 var i, j, len2, p,
9112                     parts = layer._parts,
9113                     len = parts.length,
9114                     ctx = this._ctx;
9115
9116                 if (!len) { return; }
9117
9118                 this._drawnLayers[layer._leaflet_id] = layer;
9119
9120                 ctx.beginPath();
9121
9122                 if (ctx.setLineDash) {
9123                         ctx.setLineDash(layer.options && layer.options._dashArray || []);
9124                 }
9125
9126                 for (i = 0; i < len; i++) {
9127                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
9128                                 p = parts[i][j];
9129                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
9130                         }
9131                         if (closed) {
9132                                 ctx.closePath();
9133                         }
9134                 }
9135
9136                 this._fillStroke(ctx, layer);
9137
9138                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
9139         },
9140
9141         _updateCircle: function (layer) {
9142
9143                 if (layer._empty()) { return; }
9144
9145                 var p = layer._point,
9146                     ctx = this._ctx,
9147                     r = layer._radius,
9148                     s = (layer._radiusY || r) / r;
9149
9150                 this._drawnLayers[layer._leaflet_id] = layer;
9151
9152                 if (s !== 1) {
9153                         ctx.save();
9154                         ctx.scale(1, s);
9155                 }
9156
9157                 ctx.beginPath();
9158                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
9159
9160                 if (s !== 1) {
9161                         ctx.restore();
9162                 }
9163
9164                 this._fillStroke(ctx, layer);
9165         },
9166
9167         _fillStroke: function (ctx, layer) {
9168                 var clear = this._clear,
9169                     options = layer.options;
9170
9171                 ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over';
9172
9173                 if (options.fill) {
9174                         ctx.globalAlpha = clear ? 1 : options.fillOpacity;
9175                         ctx.fillStyle = options.fillColor || options.color;
9176                         ctx.fill(options.fillRule || 'evenodd');
9177                 }
9178
9179                 if (options.stroke && options.weight !== 0) {
9180                         ctx.globalAlpha = clear ? 1 : options.opacity;
9181
9182                         // if clearing shape, do it with the previously drawn line width
9183                         layer._prevWeight = ctx.lineWidth = clear ? layer._prevWeight + 1 : options.weight;
9184
9185                         ctx.strokeStyle = options.color;
9186                         ctx.lineCap = options.lineCap;
9187                         ctx.lineJoin = options.lineJoin;
9188                         ctx.stroke();
9189                 }
9190         },
9191
9192         // Canvas obviously doesn't have mouse events for individual drawn objects,
9193         // so we emulate that by calculating what's under the mouse on mousemove/click manually
9194
9195         _onClick: function (e) {
9196                 var point = this._map.mouseEventToLayerPoint(e), layers = [], layer;
9197
9198                 for (var id in this._layers) {
9199                         layer = this._layers[id];
9200                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
9201                                 L.DomEvent._fakeStop(e);
9202                                 layers.push(layer);
9203                         }
9204                 }
9205                 if (layers.length)  {
9206                         this._fireEvent(layers, e);
9207                 }
9208         },
9209
9210         _onMouseMove: function (e) {
9211                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
9212
9213                 var point = this._map.mouseEventToLayerPoint(e);
9214                 this._handleMouseOut(e, point);
9215                 this._handleMouseHover(e, point);
9216         },
9217
9218
9219         _handleMouseOut: function (e, point) {
9220                 var layer = this._hoveredLayer;
9221                 if (layer && (e.type === 'mouseout' || !layer._containsPoint(point))) {
9222                         // if we're leaving the layer, fire mouseout
9223                         L.DomUtil.removeClass(this._container, 'leaflet-interactive');
9224                         this._fireEvent([layer], e, 'mouseout');
9225                         this._hoveredLayer = null;
9226                 }
9227         },
9228
9229         _handleMouseHover: function (e, point) {
9230                 var id, layer;
9231
9232                 for (id in this._drawnLayers) {
9233                         layer = this._drawnLayers[id];
9234                         if (layer.options.interactive && layer._containsPoint(point)) {
9235                                 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
9236                                 this._fireEvent([layer], e, 'mouseover');
9237                                 this._hoveredLayer = layer;
9238                         }
9239                 }
9240
9241                 if (this._hoveredLayer) {
9242                         this._fireEvent([this._hoveredLayer], e);
9243                 }
9244         },
9245
9246         _fireEvent: function (layers, e, type) {
9247                 this._map._fireDOMEvent(e, type || e.type, layers);
9248         },
9249
9250         // TODO _bringToFront & _bringToBack, pretty tricky
9251
9252         _bringToFront: L.Util.falseFn,
9253         _bringToBack: L.Util.falseFn
9254 });
9255
9256 // @namespace Browser; @property canvas: Boolean
9257 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9258 L.Browser.canvas = (function () {
9259         return !!document.createElement('canvas').getContext;
9260 }());
9261
9262 // @namespace Canvas
9263 // @factory L.canvas(options?: Renderer options)
9264 // Creates a Canvas renderer with the given options.
9265 L.canvas = function (options) {
9266         return L.Browser.canvas ? new L.Canvas(options) : null;
9267 };
9268
9269 L.Polyline.prototype._containsPoint = function (p, closed) {
9270         var i, j, k, len, len2, part,
9271             w = this._clickTolerance();
9272
9273         if (!this._pxBounds.contains(p)) { return false; }
9274
9275         // hit detection for polylines
9276         for (i = 0, len = this._parts.length; i < len; i++) {
9277                 part = this._parts[i];
9278
9279                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9280                         if (!closed && (j === 0)) { continue; }
9281
9282                         if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
9283                                 return true;
9284                         }
9285                 }
9286         }
9287         return false;
9288 };
9289
9290 L.Polygon.prototype._containsPoint = function (p) {
9291         var inside = false,
9292             part, p1, p2, i, j, k, len, len2;
9293
9294         if (!this._pxBounds.contains(p)) { return false; }
9295
9296         // ray casting algorithm for detecting if point is in polygon
9297         for (i = 0, len = this._parts.length; i < len; i++) {
9298                 part = this._parts[i];
9299
9300                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9301                         p1 = part[j];
9302                         p2 = part[k];
9303
9304                         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)) {
9305                                 inside = !inside;
9306                         }
9307                 }
9308         }
9309
9310         // also check if it's on polygon stroke
9311         return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
9312 };
9313
9314 L.CircleMarker.prototype._containsPoint = function (p) {
9315         return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
9316 };
9317
9318
9319
9320 /*
9321  * @class GeoJSON
9322  * @aka L.GeoJSON
9323  * @inherits FeatureGroup
9324  *
9325  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
9326  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
9327  *
9328  * @example
9329  *
9330  * ```js
9331  * L.geoJSON(data, {
9332  *      style: function (feature) {
9333  *              return {color: feature.properties.color};
9334  *      }
9335  * }).bindPopup(function (layer) {
9336  *      return layer.feature.properties.description;
9337  * }).addTo(map);
9338  * ```
9339  */
9340
9341 L.GeoJSON = L.FeatureGroup.extend({
9342
9343         /* @section
9344          * @aka GeoJSON options
9345          *
9346          * @option pointToLayer: Function = *
9347          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
9348          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
9349          * The default is to spawn a default `Marker`:
9350          * ```js
9351          * function(geoJsonPoint, latlng) {
9352          *      return L.marker(latlng);
9353          * }
9354          * ```
9355          *
9356          * @option style: Function = *
9357          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
9358          * called internally when data is added.
9359          * The default value is to not override any defaults:
9360          * ```js
9361          * function (geoJsonFeature) {
9362          *      return {}
9363          * }
9364          * ```
9365          *
9366          * @option onEachFeature: Function = *
9367          * A `Function` that will be called once for each created `Feature`, after it has
9368          * been created and styled. Useful for attaching events and popups to features.
9369          * The default is to do nothing with the newly created layers:
9370          * ```js
9371          * function (feature, layer) {}
9372          * ```
9373          *
9374          * @option filter: Function = *
9375          * A `Function` that will be used to decide whether to include a feature or not.
9376          * The default is to include all features:
9377          * ```js
9378          * function (geoJsonFeature) {
9379          *      return true;
9380          * }
9381          * ```
9382          * Note: dynamically changing the `filter` option will have effect only on newly
9383          * added data. It will _not_ re-evaluate already included features.
9384          *
9385          * @option coordsToLatLng: Function = *
9386          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
9387          * The default is the `coordsToLatLng` static method.
9388          */
9389
9390         initialize: function (geojson, options) {
9391                 L.setOptions(this, options);
9392
9393                 this._layers = {};
9394
9395                 if (geojson) {
9396                         this.addData(geojson);
9397                 }
9398         },
9399
9400         // @method addData( <GeoJSON> data ): Layer
9401         // Adds a GeoJSON object to the layer.
9402         addData: function (geojson) {
9403                 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
9404                     i, len, feature;
9405
9406                 if (features) {
9407                         for (i = 0, len = features.length; i < len; i++) {
9408                                 // only add this if geometry or geometries are set and not null
9409                                 feature = features[i];
9410                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
9411                                         this.addData(feature);
9412                                 }
9413                         }
9414                         return this;
9415                 }
9416
9417                 var options = this.options;
9418
9419                 if (options.filter && !options.filter(geojson)) { return this; }
9420
9421                 var layer = L.GeoJSON.geometryToLayer(geojson, options);
9422                 if (!layer) {
9423                         return this;
9424                 }
9425                 layer.feature = L.GeoJSON.asFeature(geojson);
9426
9427                 layer.defaultOptions = layer.options;
9428                 this.resetStyle(layer);
9429
9430                 if (options.onEachFeature) {
9431                         options.onEachFeature(geojson, layer);
9432                 }
9433
9434                 return this.addLayer(layer);
9435         },
9436
9437         // @method resetStyle( <Path> layer ): Layer
9438         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
9439         resetStyle: function (layer) {
9440                 // reset any custom styles
9441                 layer.options = L.Util.extend({}, layer.defaultOptions);
9442                 this._setLayerStyle(layer, this.options.style);
9443                 return this;
9444         },
9445
9446         // @method setStyle( <Function> style ): Layer
9447         // Changes styles of GeoJSON vector layers with the given style function.
9448         setStyle: function (style) {
9449                 return this.eachLayer(function (layer) {
9450                         this._setLayerStyle(layer, style);
9451                 }, this);
9452         },
9453
9454         _setLayerStyle: function (layer, style) {
9455                 if (typeof style === 'function') {
9456                         style = style(layer.feature);
9457                 }
9458                 if (layer.setStyle) {
9459                         layer.setStyle(style);
9460                 }
9461         }
9462 });
9463
9464 // @section
9465 // There are several static functions which can be called without instantiating L.GeoJSON:
9466 L.extend(L.GeoJSON, {
9467         // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
9468         // Creates a `Layer` from a given GeoJSON feature. Can use a custom
9469         // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
9470         // functions if provided as options.
9471         geometryToLayer: function (geojson, options) {
9472
9473                 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
9474                     coords = geometry ? geometry.coordinates : null,
9475                     layers = [],
9476                     pointToLayer = options && options.pointToLayer,
9477                     coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
9478                     latlng, latlngs, i, len;
9479
9480                 if (!coords && !geometry) {
9481                         return null;
9482                 }
9483
9484                 switch (geometry.type) {
9485                 case 'Point':
9486                         latlng = coordsToLatLng(coords);
9487                         return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
9488
9489                 case 'MultiPoint':
9490                         for (i = 0, len = coords.length; i < len; i++) {
9491                                 latlng = coordsToLatLng(coords[i]);
9492                                 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
9493                         }
9494                         return new L.FeatureGroup(layers);
9495
9496                 case 'LineString':
9497                 case 'MultiLineString':
9498                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
9499                         return new L.Polyline(latlngs, options);
9500
9501                 case 'Polygon':
9502                 case 'MultiPolygon':
9503                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
9504                         return new L.Polygon(latlngs, options);
9505
9506                 case 'GeometryCollection':
9507                         for (i = 0, len = geometry.geometries.length; i < len; i++) {
9508                                 var layer = this.geometryToLayer({
9509                                         geometry: geometry.geometries[i],
9510                                         type: 'Feature',
9511                                         properties: geojson.properties
9512                                 }, options);
9513
9514                                 if (layer) {
9515                                         layers.push(layer);
9516                                 }
9517                         }
9518                         return new L.FeatureGroup(layers);
9519
9520                 default:
9521                         throw new Error('Invalid GeoJSON object.');
9522                 }
9523         },
9524
9525         // @function coordsToLatLng(coords: Array): LatLng
9526         // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
9527         // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
9528         coordsToLatLng: function (coords) {
9529                 return new L.LatLng(coords[1], coords[0], coords[2]);
9530         },
9531
9532         // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
9533         // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
9534         // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
9535         // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
9536         coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
9537                 var latlngs = [];
9538
9539                 for (var i = 0, len = coords.length, latlng; i < len; i++) {
9540                         latlng = levelsDeep ?
9541                                 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
9542                                 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
9543
9544                         latlngs.push(latlng);
9545                 }
9546
9547                 return latlngs;
9548         },
9549
9550         // @function latLngToCoords(latlng: LatLng): Array
9551         // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
9552         latLngToCoords: function (latlng) {
9553                 return latlng.alt !== undefined ?
9554                                 [latlng.lng, latlng.lat, latlng.alt] :
9555                                 [latlng.lng, latlng.lat];
9556         },
9557
9558         // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
9559         // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
9560         // `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.
9561         latLngsToCoords: function (latlngs, levelsDeep, closed) {
9562                 var coords = [];
9563
9564                 for (var i = 0, len = latlngs.length; i < len; i++) {
9565                         coords.push(levelsDeep ?
9566                                 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
9567                                 L.GeoJSON.latLngToCoords(latlngs[i]));
9568                 }
9569
9570                 if (!levelsDeep && closed) {
9571                         coords.push(coords[0]);
9572                 }
9573
9574                 return coords;
9575         },
9576
9577         getFeature: function (layer, newGeometry) {
9578                 return layer.feature ?
9579                                 L.extend({}, layer.feature, {geometry: newGeometry}) :
9580                                 L.GeoJSON.asFeature(newGeometry);
9581         },
9582
9583         // @function asFeature(geojson: Object): Object
9584         // Normalize GeoJSON geometries/features into GeoJSON features.
9585         asFeature: function (geojson) {
9586                 if (geojson.type === 'Feature') {
9587                         return geojson;
9588                 }
9589
9590                 return {
9591                         type: 'Feature',
9592                         properties: {},
9593                         geometry: geojson
9594                 };
9595         }
9596 });
9597
9598 var PointToGeoJSON = {
9599         toGeoJSON: function () {
9600                 return L.GeoJSON.getFeature(this, {
9601                         type: 'Point',
9602                         coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
9603                 });
9604         }
9605 };
9606
9607 L.Marker.include(PointToGeoJSON);
9608
9609 // @namespace CircleMarker
9610 // @method toGeoJSON(): Object
9611 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
9612 L.Circle.include(PointToGeoJSON);
9613 L.CircleMarker.include(PointToGeoJSON);
9614
9615
9616 // @namespace Polyline
9617 // @method toGeoJSON(): Object
9618 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
9619 L.Polyline.prototype.toGeoJSON = function () {
9620         var multi = !L.Polyline._flat(this._latlngs);
9621
9622         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
9623
9624         return L.GeoJSON.getFeature(this, {
9625                 type: (multi ? 'Multi' : '') + 'LineString',
9626                 coordinates: coords
9627         });
9628 };
9629
9630 // @namespace Polygon
9631 // @method toGeoJSON(): Object
9632 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
9633 L.Polygon.prototype.toGeoJSON = function () {
9634         var holes = !L.Polyline._flat(this._latlngs),
9635             multi = holes && !L.Polyline._flat(this._latlngs[0]);
9636
9637         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
9638
9639         if (!holes) {
9640                 coords = [coords];
9641         }
9642
9643         return L.GeoJSON.getFeature(this, {
9644                 type: (multi ? 'Multi' : '') + 'Polygon',
9645                 coordinates: coords
9646         });
9647 };
9648
9649
9650 // @namespace LayerGroup
9651 L.LayerGroup.include({
9652         toMultiPoint: function () {
9653                 var coords = [];
9654
9655                 this.eachLayer(function (layer) {
9656                         coords.push(layer.toGeoJSON().geometry.coordinates);
9657                 });
9658
9659                 return L.GeoJSON.getFeature(this, {
9660                         type: 'MultiPoint',
9661                         coordinates: coords
9662                 });
9663         },
9664
9665         // @method toGeoJSON(): Object
9666         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
9667         toGeoJSON: function () {
9668
9669                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9670
9671                 if (type === 'MultiPoint') {
9672                         return this.toMultiPoint();
9673                 }
9674
9675                 var isGeometryCollection = type === 'GeometryCollection',
9676                     jsons = [];
9677
9678                 this.eachLayer(function (layer) {
9679                         if (layer.toGeoJSON) {
9680                                 var json = layer.toGeoJSON();
9681                                 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
9682                         }
9683                 });
9684
9685                 if (isGeometryCollection) {
9686                         return L.GeoJSON.getFeature(this, {
9687                                 geometries: jsons,
9688                                 type: 'GeometryCollection'
9689                         });
9690                 }
9691
9692                 return {
9693                         type: 'FeatureCollection',
9694                         features: jsons
9695                 };
9696         }
9697 });
9698
9699 // @namespace GeoJSON
9700 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9701 // Creates a GeoJSON layer. Optionally accepts an object in
9702 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
9703 // (you can alternatively add it later with `addData` method) and an `options` object.
9704 L.geoJSON = function (geojson, options) {
9705         return new L.GeoJSON(geojson, options);
9706 };
9707 // Backward compatibility.
9708 L.geoJson = L.geoJSON;
9709
9710
9711
9712 /*
9713  * @namespace DomEvent
9714  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
9715  */
9716
9717 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
9718
9719
9720
9721 var eventsKey = '_leaflet_events';
9722
9723 L.DomEvent = {
9724
9725         // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
9726         // Adds a listener function (`fn`) to a particular DOM event type of the
9727         // element `el`. You can optionally specify the context of the listener
9728         // (object the `this` keyword will point to). You can also pass several
9729         // space-separated types (e.g. `'click dblclick'`).
9730
9731         // @alternative
9732         // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
9733         // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9734         on: function (obj, types, fn, context) {
9735
9736                 if (typeof types === 'object') {
9737                         for (var type in types) {
9738                                 this._on(obj, type, types[type], fn);
9739                         }
9740                 } else {
9741                         types = L.Util.splitWords(types);
9742
9743                         for (var i = 0, len = types.length; i < len; i++) {
9744                                 this._on(obj, types[i], fn, context);
9745                         }
9746                 }
9747
9748                 return this;
9749         },
9750
9751         // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
9752         // Removes a previously added listener function. If no function is specified,
9753         // it will remove all the listeners of that particular DOM event from the element.
9754         // Note that if you passed a custom context to on, you must pass the same
9755         // context to `off` in order to remove the listener.
9756
9757         // @alternative
9758         // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
9759         // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9760         off: function (obj, types, fn, context) {
9761
9762                 if (typeof types === 'object') {
9763                         for (var type in types) {
9764                                 this._off(obj, type, types[type], fn);
9765                         }
9766                 } else {
9767                         types = L.Util.splitWords(types);
9768
9769                         for (var i = 0, len = types.length; i < len; i++) {
9770                                 this._off(obj, types[i], fn, context);
9771                         }
9772                 }
9773
9774                 return this;
9775         },
9776
9777         _on: function (obj, type, fn, context) {
9778                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
9779
9780                 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
9781
9782                 var handler = function (e) {
9783                         return fn.call(context || obj, e || window.event);
9784                 };
9785
9786                 var originalHandler = handler;
9787
9788                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
9789                         this.addPointerListener(obj, type, handler, id);
9790
9791                 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
9792                         this.addDoubleTapListener(obj, handler, id);
9793
9794                 } else if ('addEventListener' in obj) {
9795
9796                         if (type === 'mousewheel') {
9797                                 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9798
9799                         } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
9800                                 handler = function (e) {
9801                                         e = e || window.event;
9802                                         if (L.DomEvent._isExternalTarget(obj, e)) {
9803                                                 originalHandler(e);
9804                                         }
9805                                 };
9806                                 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
9807
9808                         } else {
9809                                 if (type === 'click' && L.Browser.android) {
9810                                         handler = function (e) {
9811                                                 return L.DomEvent._filterClick(e, originalHandler);
9812                                         };
9813                                 }
9814                                 obj.addEventListener(type, handler, false);
9815                         }
9816
9817                 } else if ('attachEvent' in obj) {
9818                         obj.attachEvent('on' + type, handler);
9819                 }
9820
9821                 obj[eventsKey] = obj[eventsKey] || {};
9822                 obj[eventsKey][id] = handler;
9823
9824                 return this;
9825         },
9826
9827         _off: function (obj, type, fn, context) {
9828
9829                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
9830                     handler = obj[eventsKey] && obj[eventsKey][id];
9831
9832                 if (!handler) { return this; }
9833
9834                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
9835                         this.removePointerListener(obj, type, id);
9836
9837                 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
9838                         this.removeDoubleTapListener(obj, id);
9839
9840                 } else if ('removeEventListener' in obj) {
9841
9842                         if (type === 'mousewheel') {
9843                                 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9844
9845                         } else {
9846                                 obj.removeEventListener(
9847                                         type === 'mouseenter' ? 'mouseover' :
9848                                         type === 'mouseleave' ? 'mouseout' : type, handler, false);
9849                         }
9850
9851                 } else if ('detachEvent' in obj) {
9852                         obj.detachEvent('on' + type, handler);
9853                 }
9854
9855                 obj[eventsKey][id] = null;
9856
9857                 return this;
9858         },
9859
9860         // @function stopPropagation(ev: DOMEvent): this
9861         // Stop the given event from propagation to parent elements. Used inside the listener functions:
9862         // ```js
9863         // L.DomEvent.on(div, 'click', function (ev) {
9864         //      L.DomEvent.stopPropagation(ev);
9865         // });
9866         // ```
9867         stopPropagation: function (e) {
9868
9869                 if (e.stopPropagation) {
9870                         e.stopPropagation();
9871                 } else if (e.originalEvent) {  // In case of Leaflet event.
9872                         e.originalEvent._stopped = true;
9873                 } else {
9874                         e.cancelBubble = true;
9875                 }
9876                 L.DomEvent._skipped(e);
9877
9878                 return this;
9879         },
9880
9881         // @function disableScrollPropagation(el: HTMLElement): this
9882         // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
9883         disableScrollPropagation: function (el) {
9884                 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
9885         },
9886
9887         // @function disableClickPropagation(el: HTMLElement): this
9888         // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
9889         // `'mousedown'` and `'touchstart'` events (plus browser variants).
9890         disableClickPropagation: function (el) {
9891                 var stop = L.DomEvent.stopPropagation;
9892
9893                 L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
9894
9895                 return L.DomEvent.on(el, {
9896                         click: L.DomEvent._fakeStop,
9897                         dblclick: stop
9898                 });
9899         },
9900
9901         // @function preventDefault(ev: DOMEvent): this
9902         // Prevents the default action of the DOM Event `ev` from happening (such as
9903         // following a link in the href of the a element, or doing a POST request
9904         // with page reload when a `<form>` is submitted).
9905         // Use it inside listener functions.
9906         preventDefault: function (e) {
9907
9908                 if (e.preventDefault) {
9909                         e.preventDefault();
9910                 } else {
9911                         e.returnValue = false;
9912                 }
9913                 return this;
9914         },
9915
9916         // @function stop(ev): this
9917         // Does `stopPropagation` and `preventDefault` at the same time.
9918         stop: function (e) {
9919                 return L.DomEvent
9920                         .preventDefault(e)
9921                         .stopPropagation(e);
9922         },
9923
9924         // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
9925         // Gets normalized mouse position from a DOM event relative to the
9926         // `container` or to the whole page if not specified.
9927         getMousePosition: function (e, container) {
9928                 if (!container) {
9929                         return new L.Point(e.clientX, e.clientY);
9930                 }
9931
9932                 var rect = container.getBoundingClientRect();
9933
9934                 return new L.Point(
9935                         e.clientX - rect.left - container.clientLeft,
9936                         e.clientY - rect.top - container.clientTop);
9937         },
9938
9939         // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
9940         // and Firefox scrolls device pixels, not CSS pixels
9941         _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
9942                         L.Browser.gecko ? window.devicePixelRatio :
9943                         1,
9944
9945         // @function getWheelDelta(ev: DOMEvent): Number
9946         // Gets normalized wheel delta from a mousewheel DOM event, in vertical
9947         // pixels scrolled (negative if scrolling down).
9948         // Events from pointing devices without precise scrolling are mapped to
9949         // a best guess of 60 pixels.
9950         getWheelDelta: function (e) {
9951                 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
9952                        (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
9953                        (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
9954                        (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
9955                        (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
9956                        e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
9957                        (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
9958                        e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
9959                        0;
9960         },
9961
9962         _skipEvents: {},
9963
9964         _fakeStop: function (e) {
9965                 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
9966                 L.DomEvent._skipEvents[e.type] = true;
9967         },
9968
9969         _skipped: function (e) {
9970                 var skipped = this._skipEvents[e.type];
9971                 // reset when checking, as it's only used in map container and propagates outside of the map
9972                 this._skipEvents[e.type] = false;
9973                 return skipped;
9974         },
9975
9976         // check if element really left/entered the event target (for mouseenter/mouseleave)
9977         _isExternalTarget: function (el, e) {
9978
9979                 var related = e.relatedTarget;
9980
9981                 if (!related) { return true; }
9982
9983                 try {
9984                         while (related && (related !== el)) {
9985                                 related = related.parentNode;
9986                         }
9987                 } catch (err) {
9988                         return false;
9989                 }
9990                 return (related !== el);
9991         },
9992
9993         // this is a horrible workaround for a bug in Android where a single touch triggers two click events
9994         _filterClick: function (e, handler) {
9995                 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
9996                     elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
9997
9998                 // are they closer together than 500ms yet more than 100ms?
9999                 // Android typically triggers them ~300ms apart while multiple listeners
10000                 // on the same event should be triggered far faster;
10001                 // or check if click is simulated on the element, and if it is, reject any non-simulated events
10002
10003                 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
10004                         L.DomEvent.stop(e);
10005                         return;
10006                 }
10007                 L.DomEvent._lastClick = timeStamp;
10008
10009                 handler(e);
10010         }
10011 };
10012
10013 // @function addListener(…): this
10014 // Alias to [`L.DomEvent.on`](#domevent-on)
10015 L.DomEvent.addListener = L.DomEvent.on;
10016
10017 // @function removeListener(…): this
10018 // Alias to [`L.DomEvent.off`](#domevent-off)
10019 L.DomEvent.removeListener = L.DomEvent.off;
10020
10021
10022
10023 /*
10024  * @class Draggable
10025  * @aka L.Draggable
10026  * @inherits Evented
10027  *
10028  * A class for making DOM elements draggable (including touch support).
10029  * Used internally for map and marker dragging. Only works for elements
10030  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10031  *
10032  * @example
10033  * ```js
10034  * var draggable = new L.Draggable(elementToDrag);
10035  * draggable.enable();
10036  * ```
10037  */
10038
10039 L.Draggable = L.Evented.extend({
10040
10041         options: {
10042                 // @option clickTolerance: Number = 3
10043                 // The max number of pixels a user can shift the mouse pointer during a click
10044                 // for it to be considered a valid click (as opposed to a mouse drag).
10045                 clickTolerance: 3
10046         },
10047
10048         statics: {
10049                 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10050                 END: {
10051                         mousedown: 'mouseup',
10052                         touchstart: 'touchend',
10053                         pointerdown: 'touchend',
10054                         MSPointerDown: 'touchend'
10055                 },
10056                 MOVE: {
10057                         mousedown: 'mousemove',
10058                         touchstart: 'touchmove',
10059                         pointerdown: 'touchmove',
10060                         MSPointerDown: 'touchmove'
10061                 }
10062         },
10063
10064         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10065         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10066         initialize: function (element, dragStartTarget, preventOutline) {
10067                 this._element = element;
10068                 this._dragStartTarget = dragStartTarget || element;
10069                 this._preventOutline = preventOutline;
10070         },
10071
10072         // @method enable()
10073         // Enables the dragging ability
10074         enable: function () {
10075                 if (this._enabled) { return; }
10076
10077                 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10078
10079                 this._enabled = true;
10080         },
10081
10082         // @method disable()
10083         // Disables the dragging ability
10084         disable: function () {
10085                 if (!this._enabled) { return; }
10086
10087                 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10088
10089                 this._enabled = false;
10090                 this._moved = false;
10091         },
10092
10093         _onDown: function (e) {
10094                 // Ignore simulated events, since we handle both touch and
10095                 // mouse explicitly; otherwise we risk getting duplicates of
10096                 // touch events, see #4315.
10097                 // Also ignore the event if disabled; this happens in IE11
10098                 // under some circumstances, see #3666.
10099                 if (e._simulated || !this._enabled) { return; }
10100
10101                 this._moved = false;
10102
10103                 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10104
10105                 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches) || !this._enabled) { return; }
10106                 L.Draggable._dragging = true;  // Prevent dragging multiple objects at once.
10107
10108                 if (this._preventOutline) {
10109                         L.DomUtil.preventOutline(this._element);
10110                 }
10111
10112                 L.DomUtil.disableImageDrag();
10113                 L.DomUtil.disableTextSelection();
10114
10115                 if (this._moving) { return; }
10116
10117                 // @event down: Event
10118                 // Fired when a drag is about to start.
10119                 this.fire('down');
10120
10121                 var first = e.touches ? e.touches[0] : e;
10122
10123                 this._startPoint = new L.Point(first.clientX, first.clientY);
10124
10125                 L.DomEvent
10126                         .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10127                         .on(document, L.Draggable.END[e.type], this._onUp, this);
10128         },
10129
10130         _onMove: function (e) {
10131                 // Ignore simulated events, since we handle both touch and
10132                 // mouse explicitly; otherwise we risk getting duplicates of
10133                 // touch events, see #4315.
10134                 // Also ignore the event if disabled; this happens in IE11
10135                 // under some circumstances, see #3666.
10136                 if (e._simulated || !this._enabled) { return; }
10137
10138                 if (e.touches && e.touches.length > 1) {
10139                         this._moved = true;
10140                         return;
10141                 }
10142
10143                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10144                     newPoint = new L.Point(first.clientX, first.clientY),
10145                     offset = newPoint.subtract(this._startPoint);
10146
10147                 if (!offset.x && !offset.y) { return; }
10148                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10149
10150                 L.DomEvent.preventDefault(e);
10151
10152                 if (!this._moved) {
10153                         // @event dragstart: Event
10154                         // Fired when a drag starts
10155                         this.fire('dragstart');
10156
10157                         this._moved = true;
10158                         this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10159
10160                         L.DomUtil.addClass(document.body, 'leaflet-dragging');
10161
10162                         this._lastTarget = e.target || e.srcElement;
10163                         // IE and Edge do not give the <use> element, so fetch it
10164                         // if necessary
10165                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10166                                 this._lastTarget = this._lastTarget.correspondingUseElement;
10167                         }
10168                         L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10169                 }
10170
10171                 this._newPos = this._startPos.add(offset);
10172                 this._moving = true;
10173
10174                 L.Util.cancelAnimFrame(this._animRequest);
10175                 this._lastEvent = e;
10176                 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10177         },
10178
10179         _updatePosition: function () {
10180                 var e = {originalEvent: this._lastEvent};
10181
10182                 // @event predrag: Event
10183                 // Fired continuously during dragging *before* each corresponding
10184                 // update of the element's position.
10185                 this.fire('predrag', e);
10186                 L.DomUtil.setPosition(this._element, this._newPos);
10187
10188                 // @event drag: Event
10189                 // Fired continuously during dragging.
10190                 this.fire('drag', e);
10191         },
10192
10193         _onUp: function (e) {
10194                 // Ignore simulated events, since we handle both touch and
10195                 // mouse explicitly; otherwise we risk getting duplicates of
10196                 // touch events, see #4315.
10197                 // Also ignore the event if disabled; this happens in IE11
10198                 // under some circumstances, see #3666.
10199                 if (e._simulated || !this._enabled) { return; }
10200
10201                 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10202
10203                 if (this._lastTarget) {
10204                         L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10205                         this._lastTarget = null;
10206                 }
10207
10208                 for (var i in L.Draggable.MOVE) {
10209                         L.DomEvent
10210                                 .off(document, L.Draggable.MOVE[i], this._onMove, this)
10211                                 .off(document, L.Draggable.END[i], this._onUp, this);
10212                 }
10213
10214                 L.DomUtil.enableImageDrag();
10215                 L.DomUtil.enableTextSelection();
10216
10217                 if (this._moved && this._moving) {
10218                         // ensure drag is not fired after dragend
10219                         L.Util.cancelAnimFrame(this._animRequest);
10220
10221                         // @event dragend: DragEndEvent
10222                         // Fired when the drag ends.
10223                         this.fire('dragend', {
10224                                 distance: this._newPos.distanceTo(this._startPos)
10225                         });
10226                 }
10227
10228                 this._moving = false;
10229                 L.Draggable._dragging = false;
10230         }
10231 });
10232
10233
10234
10235 /*
10236         L.Handler is a base class for handler classes that are used internally to inject
10237         interaction features like dragging to classes like Map and Marker.
10238 */
10239
10240 // @class Handler
10241 // @aka L.Handler
10242 // Abstract class for map interaction handlers
10243
10244 L.Handler = L.Class.extend({
10245         initialize: function (map) {
10246                 this._map = map;
10247         },
10248
10249         // @method enable(): this
10250         // Enables the handler
10251         enable: function () {
10252                 if (this._enabled) { return this; }
10253
10254                 this._enabled = true;
10255                 this.addHooks();
10256                 return this;
10257         },
10258
10259         // @method disable(): this
10260         // Disables the handler
10261         disable: function () {
10262                 if (!this._enabled) { return this; }
10263
10264                 this._enabled = false;
10265                 this.removeHooks();
10266                 return this;
10267         },
10268
10269         // @method enabled(): Boolean
10270         // Returns `true` if the handler is enabled
10271         enabled: function () {
10272                 return !!this._enabled;
10273         }
10274
10275         // @section Extension methods
10276         // Classes inheriting from `Handler` must implement the two following methods:
10277         // @method addHooks()
10278         // Called when the handler is enabled, should add event hooks.
10279         // @method removeHooks()
10280         // Called when the handler is disabled, should remove the event hooks added previously.
10281 });
10282
10283
10284
10285 /*
10286  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
10287  */
10288
10289 // @namespace Map
10290 // @section Interaction Options
10291 L.Map.mergeOptions({
10292         // @option dragging: Boolean = true
10293         // Whether the map be draggable with mouse/touch or not.
10294         dragging: true,
10295
10296         // @section Panning Inertia Options
10297         // @option inertia: Boolean = *
10298         // If enabled, panning of the map will have an inertia effect where
10299         // the map builds momentum while dragging and continues moving in
10300         // the same direction for some time. Feels especially nice on touch
10301         // devices. Enabled by default unless running on old Android devices.
10302         inertia: !L.Browser.android23,
10303
10304         // @option inertiaDeceleration: Number = 3000
10305         // The rate with which the inertial movement slows down, in pixels/second².
10306         inertiaDeceleration: 3400, // px/s^2
10307
10308         // @option inertiaMaxSpeed: Number = Infinity
10309         // Max speed of the inertial movement, in pixels/second.
10310         inertiaMaxSpeed: Infinity, // px/s
10311
10312         // @option easeLinearity: Number = 0.2
10313         easeLinearity: 0.2,
10314
10315         // TODO refactor, move to CRS
10316         // @option worldCopyJump: Boolean = false
10317         // With this option enabled, the map tracks when you pan to another "copy"
10318         // of the world and seamlessly jumps to the original one so that all overlays
10319         // like markers and vector layers are still visible.
10320         worldCopyJump: false,
10321
10322         // @option maxBoundsViscosity: Number = 0.0
10323         // If `maxBounds` is set, this option will control how solid the bounds
10324         // are when dragging the map around. The default value of `0.0` allows the
10325         // user to drag outside the bounds at normal speed, higher values will
10326         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
10327         // solid, preventing the user from dragging outside the bounds.
10328         maxBoundsViscosity: 0.0
10329 });
10330
10331 L.Map.Drag = L.Handler.extend({
10332         addHooks: function () {
10333                 if (!this._draggable) {
10334                         var map = this._map;
10335
10336                         this._draggable = new L.Draggable(map._mapPane, map._container);
10337
10338                         this._draggable.on({
10339                                 down: this._onDown,
10340                                 dragstart: this._onDragStart,
10341                                 drag: this._onDrag,
10342                                 dragend: this._onDragEnd
10343                         }, this);
10344
10345                         this._draggable.on('predrag', this._onPreDragLimit, this);
10346                         if (map.options.worldCopyJump) {
10347                                 this._draggable.on('predrag', this._onPreDragWrap, this);
10348                                 map.on('zoomend', this._onZoomEnd, this);
10349
10350                                 map.whenReady(this._onZoomEnd, this);
10351                         }
10352                 }
10353                 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
10354                 this._draggable.enable();
10355                 this._positions = [];
10356                 this._times = [];
10357         },
10358
10359         removeHooks: function () {
10360                 L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
10361                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
10362                 this._draggable.disable();
10363         },
10364
10365         moved: function () {
10366                 return this._draggable && this._draggable._moved;
10367         },
10368
10369         moving: function () {
10370                 return this._draggable && this._draggable._moving;
10371         },
10372
10373         _onDown: function () {
10374                 this._map._stop();
10375         },
10376
10377         _onDragStart: function () {
10378                 var map = this._map;
10379
10380                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
10381                         var bounds = L.latLngBounds(this._map.options.maxBounds);
10382
10383                         this._offsetLimit = L.bounds(
10384                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
10385                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
10386                                         .add(this._map.getSize()));
10387
10388                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
10389                 } else {
10390                         this._offsetLimit = null;
10391                 }
10392
10393                 map
10394                     .fire('movestart')
10395                     .fire('dragstart');
10396
10397                 if (map.options.inertia) {
10398                         this._positions = [];
10399                         this._times = [];
10400                 }
10401         },
10402
10403         _onDrag: function (e) {
10404                 if (this._map.options.inertia) {
10405                         var time = this._lastTime = +new Date(),
10406                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
10407
10408                         this._positions.push(pos);
10409                         this._times.push(time);
10410
10411                         if (time - this._times[0] > 50) {
10412                                 this._positions.shift();
10413                                 this._times.shift();
10414                         }
10415                 }
10416
10417                 this._map
10418                     .fire('move', e)
10419                     .fire('drag', e);
10420         },
10421
10422         _onZoomEnd: function () {
10423                 var pxCenter = this._map.getSize().divideBy(2),
10424                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
10425
10426                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
10427                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
10428         },
10429
10430         _viscousLimit: function (value, threshold) {
10431                 return value - (value - threshold) * this._viscosity;
10432         },
10433
10434         _onPreDragLimit: function () {
10435                 if (!this._viscosity || !this._offsetLimit) { return; }
10436
10437                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
10438
10439                 var limit = this._offsetLimit;
10440                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
10441                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
10442                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
10443                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
10444
10445                 this._draggable._newPos = this._draggable._startPos.add(offset);
10446         },
10447
10448         _onPreDragWrap: function () {
10449                 // TODO refactor to be able to adjust map pane position after zoom
10450                 var worldWidth = this._worldWidth,
10451                     halfWidth = Math.round(worldWidth / 2),
10452                     dx = this._initialWorldOffset,
10453                     x = this._draggable._newPos.x,
10454                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
10455                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
10456                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
10457
10458                 this._draggable._absPos = this._draggable._newPos.clone();
10459                 this._draggable._newPos.x = newX;
10460         },
10461
10462         _onDragEnd: function (e) {
10463                 var map = this._map,
10464                     options = map.options,
10465
10466                     noInertia = !options.inertia || this._times.length < 2;
10467
10468                 map.fire('dragend', e);
10469
10470                 if (noInertia) {
10471                         map.fire('moveend');
10472
10473                 } else {
10474
10475                         var direction = this._lastPos.subtract(this._positions[0]),
10476                             duration = (this._lastTime - this._times[0]) / 1000,
10477                             ease = options.easeLinearity,
10478
10479                             speedVector = direction.multiplyBy(ease / duration),
10480                             speed = speedVector.distanceTo([0, 0]),
10481
10482                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
10483                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
10484
10485                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
10486                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
10487
10488                         if (!offset.x && !offset.y) {
10489                                 map.fire('moveend');
10490
10491                         } else {
10492                                 offset = map._limitOffset(offset, map.options.maxBounds);
10493
10494                                 L.Util.requestAnimFrame(function () {
10495                                         map.panBy(offset, {
10496                                                 duration: decelerationDuration,
10497                                                 easeLinearity: ease,
10498                                                 noMoveStart: true,
10499                                                 animate: true
10500                                         });
10501                                 });
10502                         }
10503                 }
10504         }
10505 });
10506
10507 // @section Handlers
10508 // @property dragging: Handler
10509 // Map dragging handler (by both mouse and touch).
10510 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
10511
10512
10513
10514 /*
10515  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
10516  */
10517
10518 // @namespace Map
10519 // @section Interaction Options
10520
10521 L.Map.mergeOptions({
10522         // @option doubleClickZoom: Boolean|String = true
10523         // Whether the map can be zoomed in by double clicking on it and
10524         // zoomed out by double clicking while holding shift. If passed
10525         // `'center'`, double-click zoom will zoom to the center of the
10526         //  view regardless of where the mouse was.
10527         doubleClickZoom: true
10528 });
10529
10530 L.Map.DoubleClickZoom = L.Handler.extend({
10531         addHooks: function () {
10532                 this._map.on('dblclick', this._onDoubleClick, this);
10533         },
10534
10535         removeHooks: function () {
10536                 this._map.off('dblclick', this._onDoubleClick, this);
10537         },
10538
10539         _onDoubleClick: function (e) {
10540                 var map = this._map,
10541                     oldZoom = map.getZoom(),
10542                     delta = map.options.zoomDelta,
10543                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
10544
10545                 if (map.options.doubleClickZoom === 'center') {
10546                         map.setZoom(zoom);
10547                 } else {
10548                         map.setZoomAround(e.containerPoint, zoom);
10549                 }
10550         }
10551 });
10552
10553 // @section Handlers
10554 //
10555 // Map properties include interaction handlers that allow you to control
10556 // interaction behavior in runtime, enabling or disabling certain features such
10557 // as dragging or touch zoom (see `Handler` methods). For example:
10558 //
10559 // ```js
10560 // map.doubleClickZoom.disable();
10561 // ```
10562 //
10563 // @property doubleClickZoom: Handler
10564 // Double click zoom handler.
10565 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
10566
10567
10568
10569 /*
10570  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
10571  */
10572
10573 // @namespace Map
10574 // @section Interaction Options
10575 L.Map.mergeOptions({
10576         // @section Mousewheel options
10577         // @option scrollWheelZoom: Boolean|String = true
10578         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
10579         // it will zoom to the center of the view regardless of where the mouse was.
10580         scrollWheelZoom: true,
10581
10582         // @option wheelDebounceTime: Number = 40
10583         // Limits the rate at which a wheel can fire (in milliseconds). By default
10584         // user can't zoom via wheel more often than once per 40 ms.
10585         wheelDebounceTime: 40,
10586
10587         // @option wheelPxPerZoomLevel: Number = 60
10588         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
10589         // mean a change of one full zoom level. Smaller values will make wheel-zooming
10590         // faster (and vice versa).
10591         wheelPxPerZoomLevel: 60
10592 });
10593
10594 L.Map.ScrollWheelZoom = L.Handler.extend({
10595         addHooks: function () {
10596                 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
10597
10598                 this._delta = 0;
10599         },
10600
10601         removeHooks: function () {
10602                 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
10603         },
10604
10605         _onWheelScroll: function (e) {
10606                 var delta = L.DomEvent.getWheelDelta(e);
10607
10608                 var debounce = this._map.options.wheelDebounceTime;
10609
10610                 this._delta += delta;
10611                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
10612
10613                 if (!this._startTime) {
10614                         this._startTime = +new Date();
10615                 }
10616
10617                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
10618
10619                 clearTimeout(this._timer);
10620                 this._timer = setTimeout(L.bind(this._performZoom, this), left);
10621
10622                 L.DomEvent.stop(e);
10623         },
10624
10625         _performZoom: function () {
10626                 var map = this._map,
10627                     zoom = map.getZoom(),
10628                     snap = this._map.options.zoomSnap || 0;
10629
10630                 map._stop(); // stop panning and fly animations if any
10631
10632                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
10633                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
10634                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
10635                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
10636                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
10637
10638                 this._delta = 0;
10639                 this._startTime = null;
10640
10641                 if (!delta) { return; }
10642
10643                 if (map.options.scrollWheelZoom === 'center') {
10644                         map.setZoom(zoom + delta);
10645                 } else {
10646                         map.setZoomAround(this._lastMousePos, zoom + delta);
10647                 }
10648         }
10649 });
10650
10651 // @section Handlers
10652 // @property scrollWheelZoom: Handler
10653 // Scroll wheel zoom handler.
10654 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
10655
10656
10657
10658 /*
10659  * Extends the event handling code with double tap support for mobile browsers.
10660  */
10661
10662 L.extend(L.DomEvent, {
10663
10664         _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
10665         _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
10666
10667         // inspired by Zepto touch code by Thomas Fuchs
10668         addDoubleTapListener: function (obj, handler, id) {
10669                 var last, touch,
10670                     doubleTap = false,
10671                     delay = 250;
10672
10673                 function onTouchStart(e) {
10674                         var count;
10675
10676                         if (L.Browser.pointer) {
10677                                 count = L.DomEvent._pointersCount;
10678                         } else {
10679                                 count = e.touches.length;
10680                         }
10681
10682                         if (count > 1) { return; }
10683
10684                         var now = Date.now(),
10685                             delta = now - (last || now);
10686
10687                         touch = e.touches ? e.touches[0] : e;
10688                         doubleTap = (delta > 0 && delta <= delay);
10689                         last = now;
10690                 }
10691
10692                 function onTouchEnd() {
10693                         if (doubleTap && !touch.cancelBubble) {
10694                                 if (L.Browser.pointer) {
10695                                         // work around .type being readonly with MSPointer* events
10696                                         var newTouch = {},
10697                                             prop, i;
10698
10699                                         for (i in touch) {
10700                                                 prop = touch[i];
10701                                                 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
10702                                         }
10703                                         touch = newTouch;
10704                                 }
10705                                 touch.type = 'dblclick';
10706                                 handler(touch);
10707                                 last = null;
10708                         }
10709                 }
10710
10711                 var pre = '_leaflet_',
10712                     touchstart = this._touchstart,
10713                     touchend = this._touchend;
10714
10715                 obj[pre + touchstart + id] = onTouchStart;
10716                 obj[pre + touchend + id] = onTouchEnd;
10717                 obj[pre + 'dblclick' + id] = handler;
10718
10719                 obj.addEventListener(touchstart, onTouchStart, false);
10720                 obj.addEventListener(touchend, onTouchEnd, false);
10721
10722                 // On some platforms (notably, chrome on win10 + touchscreen + mouse),
10723                 // the browser doesn't fire touchend/pointerup events but does fire
10724                 // native dblclicks. See #4127.
10725                 if (!L.Browser.edge) {
10726                         obj.addEventListener('dblclick', handler, false);
10727                 }
10728
10729                 return this;
10730         },
10731
10732         removeDoubleTapListener: function (obj, id) {
10733                 var pre = '_leaflet_',
10734                     touchstart = obj[pre + this._touchstart + id],
10735                     touchend = obj[pre + this._touchend + id],
10736                     dblclick = obj[pre + 'dblclick' + id];
10737
10738                 obj.removeEventListener(this._touchstart, touchstart, false);
10739                 obj.removeEventListener(this._touchend, touchend, false);
10740                 if (!L.Browser.edge) {
10741                         obj.removeEventListener('dblclick', dblclick, false);
10742                 }
10743
10744                 return this;
10745         }
10746 });
10747
10748
10749
10750 /*
10751  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
10752  */
10753
10754 L.extend(L.DomEvent, {
10755
10756         POINTER_DOWN:   L.Browser.msPointer ? 'MSPointerDown'   : 'pointerdown',
10757         POINTER_MOVE:   L.Browser.msPointer ? 'MSPointerMove'   : 'pointermove',
10758         POINTER_UP:     L.Browser.msPointer ? 'MSPointerUp'     : 'pointerup',
10759         POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
10760         TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
10761
10762         _pointers: {},
10763         _pointersCount: 0,
10764
10765         // Provides a touch events wrapper for (ms)pointer events.
10766         // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
10767
10768         addPointerListener: function (obj, type, handler, id) {
10769
10770                 if (type === 'touchstart') {
10771                         this._addPointerStart(obj, handler, id);
10772
10773                 } else if (type === 'touchmove') {
10774                         this._addPointerMove(obj, handler, id);
10775
10776                 } else if (type === 'touchend') {
10777                         this._addPointerEnd(obj, handler, id);
10778                 }
10779
10780                 return this;
10781         },
10782
10783         removePointerListener: function (obj, type, id) {
10784                 var handler = obj['_leaflet_' + type + id];
10785
10786                 if (type === 'touchstart') {
10787                         obj.removeEventListener(this.POINTER_DOWN, handler, false);
10788
10789                 } else if (type === 'touchmove') {
10790                         obj.removeEventListener(this.POINTER_MOVE, handler, false);
10791
10792                 } else if (type === 'touchend') {
10793                         obj.removeEventListener(this.POINTER_UP, handler, false);
10794                         obj.removeEventListener(this.POINTER_CANCEL, handler, false);
10795                 }
10796
10797                 return this;
10798         },
10799
10800         _addPointerStart: function (obj, handler, id) {
10801                 var onDown = L.bind(function (e) {
10802                         if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
10803                                 // In IE11, some touch events needs to fire for form controls, or
10804                                 // the controls will stop working. We keep a whitelist of tag names that
10805                                 // need these events. For other target tags, we prevent default on the event.
10806                                 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
10807                                         L.DomEvent.preventDefault(e);
10808                                 } else {
10809                                         return;
10810                                 }
10811                         }
10812
10813                         this._handlePointer(e, handler);
10814                 }, this);
10815
10816                 obj['_leaflet_touchstart' + id] = onDown;
10817                 obj.addEventListener(this.POINTER_DOWN, onDown, false);
10818
10819                 // need to keep track of what pointers and how many are active to provide e.touches emulation
10820                 if (!this._pointerDocListener) {
10821                         var pointerUp = L.bind(this._globalPointerUp, this);
10822
10823                         // we listen documentElement as any drags that end by moving the touch off the screen get fired there
10824                         document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
10825                         document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
10826                         document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
10827                         document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
10828
10829                         this._pointerDocListener = true;
10830                 }
10831         },
10832
10833         _globalPointerDown: function (e) {
10834                 this._pointers[e.pointerId] = e;
10835                 this._pointersCount++;
10836         },
10837
10838         _globalPointerMove: function (e) {
10839                 if (this._pointers[e.pointerId]) {
10840                         this._pointers[e.pointerId] = e;
10841                 }
10842         },
10843
10844         _globalPointerUp: function (e) {
10845                 delete this._pointers[e.pointerId];
10846                 this._pointersCount--;
10847         },
10848
10849         _handlePointer: function (e, handler) {
10850                 e.touches = [];
10851                 for (var i in this._pointers) {
10852                         e.touches.push(this._pointers[i]);
10853                 }
10854                 e.changedTouches = [e];
10855
10856                 handler(e);
10857         },
10858
10859         _addPointerMove: function (obj, handler, id) {
10860                 var onMove = L.bind(function (e) {
10861                         // don't fire touch moves when mouse isn't down
10862                         if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
10863
10864                         this._handlePointer(e, handler);
10865                 }, this);
10866
10867                 obj['_leaflet_touchmove' + id] = onMove;
10868                 obj.addEventListener(this.POINTER_MOVE, onMove, false);
10869         },
10870
10871         _addPointerEnd: function (obj, handler, id) {
10872                 var onUp = L.bind(function (e) {
10873                         this._handlePointer(e, handler);
10874                 }, this);
10875
10876                 obj['_leaflet_touchend' + id] = onUp;
10877                 obj.addEventListener(this.POINTER_UP, onUp, false);
10878                 obj.addEventListener(this.POINTER_CANCEL, onUp, false);
10879         }
10880 });
10881
10882
10883
10884 /*
10885  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
10886  */
10887
10888 // @namespace Map
10889 // @section Interaction Options
10890 L.Map.mergeOptions({
10891         // @section Touch interaction options
10892         // @option touchZoom: Boolean|String = *
10893         // Whether the map can be zoomed by touch-dragging with two fingers. If
10894         // passed `'center'`, it will zoom to the center of the view regardless of
10895         // where the touch events (fingers) were. Enabled for touch-capable web
10896         // browsers except for old Androids.
10897         touchZoom: L.Browser.touch && !L.Browser.android23,
10898
10899         // @option bounceAtZoomLimits: Boolean = true
10900         // Set it to false if you don't want the map to zoom beyond min/max zoom
10901         // and then bounce back when pinch-zooming.
10902         bounceAtZoomLimits: true
10903 });
10904
10905 L.Map.TouchZoom = L.Handler.extend({
10906         addHooks: function () {
10907                 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
10908                 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
10909         },
10910
10911         removeHooks: function () {
10912                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
10913                 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
10914         },
10915
10916         _onTouchStart: function (e) {
10917                 var map = this._map;
10918                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
10919
10920                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
10921                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
10922
10923                 this._centerPoint = map.getSize()._divideBy(2);
10924                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
10925                 if (map.options.touchZoom !== 'center') {
10926                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
10927                 }
10928
10929                 this._startDist = p1.distanceTo(p2);
10930                 this._startZoom = map.getZoom();
10931
10932                 this._moved = false;
10933                 this._zooming = true;
10934
10935                 map._stop();
10936
10937                 L.DomEvent
10938                     .on(document, 'touchmove', this._onTouchMove, this)
10939                     .on(document, 'touchend', this._onTouchEnd, this);
10940
10941                 L.DomEvent.preventDefault(e);
10942         },
10943
10944         _onTouchMove: function (e) {
10945                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
10946
10947                 var map = this._map,
10948                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
10949                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
10950                     scale = p1.distanceTo(p2) / this._startDist;
10951
10952
10953                 this._zoom = map.getScaleZoom(scale, this._startZoom);
10954
10955                 if (!map.options.bounceAtZoomLimits && (
10956                         (this._zoom < map.getMinZoom() && scale < 1) ||
10957                         (this._zoom > map.getMaxZoom() && scale > 1))) {
10958                         this._zoom = map._limitZoom(this._zoom);
10959                 }
10960
10961                 if (map.options.touchZoom === 'center') {
10962                         this._center = this._startLatLng;
10963                         if (scale === 1) { return; }
10964                 } else {
10965                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
10966                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
10967                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
10968                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
10969                 }
10970
10971                 if (!this._moved) {
10972                         map._moveStart(true);
10973                         this._moved = true;
10974                 }
10975
10976                 L.Util.cancelAnimFrame(this._animRequest);
10977
10978                 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
10979                 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
10980
10981                 L.DomEvent.preventDefault(e);
10982         },
10983
10984         _onTouchEnd: function () {
10985                 if (!this._moved || !this._zooming) {
10986                         this._zooming = false;
10987                         return;
10988                 }
10989
10990                 this._zooming = false;
10991                 L.Util.cancelAnimFrame(this._animRequest);
10992
10993                 L.DomEvent
10994                     .off(document, 'touchmove', this._onTouchMove)
10995                     .off(document, 'touchend', this._onTouchEnd);
10996
10997                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
10998                 if (this._map.options.zoomAnimation) {
10999                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
11000                 } else {
11001                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
11002                 }
11003         }
11004 });
11005
11006 // @section Handlers
11007 // @property touchZoom: Handler
11008 // Touch zoom handler.
11009 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11010
11011
11012
11013 /*
11014  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11015  */
11016
11017 // @namespace Map
11018 // @section Interaction Options
11019 L.Map.mergeOptions({
11020         // @section Touch interaction options
11021         // @option tap: Boolean = true
11022         // Enables mobile hacks for supporting instant taps (fixing 200ms click
11023         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11024         tap: true,
11025
11026         // @option tapTolerance: Number = 15
11027         // The max number of pixels a user can shift his finger during touch
11028         // for it to be considered a valid tap.
11029         tapTolerance: 15
11030 });
11031
11032 L.Map.Tap = L.Handler.extend({
11033         addHooks: function () {
11034                 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11035         },
11036
11037         removeHooks: function () {
11038                 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11039         },
11040
11041         _onDown: function (e) {
11042                 if (!e.touches) { return; }
11043
11044                 L.DomEvent.preventDefault(e);
11045
11046                 this._fireClick = true;
11047
11048                 // don't simulate click or track longpress if more than 1 touch
11049                 if (e.touches.length > 1) {
11050                         this._fireClick = false;
11051                         clearTimeout(this._holdTimeout);
11052                         return;
11053                 }
11054
11055                 var first = e.touches[0],
11056                     el = first.target;
11057
11058                 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11059
11060                 // if touching a link, highlight it
11061                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
11062                         L.DomUtil.addClass(el, 'leaflet-active');
11063                 }
11064
11065                 // simulate long hold but setting a timeout
11066                 this._holdTimeout = setTimeout(L.bind(function () {
11067                         if (this._isTapValid()) {
11068                                 this._fireClick = false;
11069                                 this._onUp();
11070                                 this._simulateEvent('contextmenu', first);
11071                         }
11072                 }, this), 1000);
11073
11074                 this._simulateEvent('mousedown', first);
11075
11076                 L.DomEvent.on(document, {
11077                         touchmove: this._onMove,
11078                         touchend: this._onUp
11079                 }, this);
11080         },
11081
11082         _onUp: function (e) {
11083                 clearTimeout(this._holdTimeout);
11084
11085                 L.DomEvent.off(document, {
11086                         touchmove: this._onMove,
11087                         touchend: this._onUp
11088                 }, this);
11089
11090                 if (this._fireClick && e && e.changedTouches) {
11091
11092                         var first = e.changedTouches[0],
11093                             el = first.target;
11094
11095                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11096                                 L.DomUtil.removeClass(el, 'leaflet-active');
11097                         }
11098
11099                         this._simulateEvent('mouseup', first);
11100
11101                         // simulate click if the touch didn't move too much
11102                         if (this._isTapValid()) {
11103                                 this._simulateEvent('click', first);
11104                         }
11105                 }
11106         },
11107
11108         _isTapValid: function () {
11109                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11110         },
11111
11112         _onMove: function (e) {
11113                 var first = e.touches[0];
11114                 this._newPos = new L.Point(first.clientX, first.clientY);
11115                 this._simulateEvent('mousemove', first);
11116         },
11117
11118         _simulateEvent: function (type, e) {
11119                 var simulatedEvent = document.createEvent('MouseEvents');
11120
11121                 simulatedEvent._simulated = true;
11122                 e.target._simulatedClick = true;
11123
11124                 simulatedEvent.initMouseEvent(
11125                         type, true, true, window, 1,
11126                         e.screenX, e.screenY,
11127                         e.clientX, e.clientY,
11128                         false, false, false, false, 0, null);
11129
11130                 e.target.dispatchEvent(simulatedEvent);
11131         }
11132 });
11133
11134 // @section Handlers
11135 // @property tap: Handler
11136 // Mobile touch hacks (quick tap and touch hold) handler.
11137 if (L.Browser.touch && !L.Browser.pointer) {
11138         L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11139 }
11140
11141
11142
11143 /*
11144  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11145  * (zoom to a selected bounding box), enabled by default.
11146  */
11147
11148 // @namespace Map
11149 // @section Interaction Options
11150 L.Map.mergeOptions({
11151         // @option boxZoom: Boolean = true
11152         // Whether the map can be zoomed to a rectangular area specified by
11153         // dragging the mouse while pressing the shift key.
11154         boxZoom: true
11155 });
11156
11157 L.Map.BoxZoom = L.Handler.extend({
11158         initialize: function (map) {
11159                 this._map = map;
11160                 this._container = map._container;
11161                 this._pane = map._panes.overlayPane;
11162         },
11163
11164         addHooks: function () {
11165                 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11166         },
11167
11168         removeHooks: function () {
11169                 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11170         },
11171
11172         moved: function () {
11173                 return this._moved;
11174         },
11175
11176         _resetState: function () {
11177                 this._moved = false;
11178         },
11179
11180         _onMouseDown: function (e) {
11181                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11182
11183                 this._resetState();
11184
11185                 L.DomUtil.disableTextSelection();
11186                 L.DomUtil.disableImageDrag();
11187
11188                 this._startPoint = this._map.mouseEventToContainerPoint(e);
11189
11190                 L.DomEvent.on(document, {
11191                         contextmenu: L.DomEvent.stop,
11192                         mousemove: this._onMouseMove,
11193                         mouseup: this._onMouseUp,
11194                         keydown: this._onKeyDown
11195                 }, this);
11196         },
11197
11198         _onMouseMove: function (e) {
11199                 if (!this._moved) {
11200                         this._moved = true;
11201
11202                         this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11203                         L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11204
11205                         this._map.fire('boxzoomstart');
11206                 }
11207
11208                 this._point = this._map.mouseEventToContainerPoint(e);
11209
11210                 var bounds = new L.Bounds(this._point, this._startPoint),
11211                     size = bounds.getSize();
11212
11213                 L.DomUtil.setPosition(this._box, bounds.min);
11214
11215                 this._box.style.width  = size.x + 'px';
11216                 this._box.style.height = size.y + 'px';
11217         },
11218
11219         _finish: function () {
11220                 if (this._moved) {
11221                         L.DomUtil.remove(this._box);
11222                         L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11223                 }
11224
11225                 L.DomUtil.enableTextSelection();
11226                 L.DomUtil.enableImageDrag();
11227
11228                 L.DomEvent.off(document, {
11229                         contextmenu: L.DomEvent.stop,
11230                         mousemove: this._onMouseMove,
11231                         mouseup: this._onMouseUp,
11232                         keydown: this._onKeyDown
11233                 }, this);
11234         },
11235
11236         _onMouseUp: function (e) {
11237                 if ((e.which !== 1) && (e.button !== 1)) { return; }
11238
11239                 this._finish();
11240
11241                 if (!this._moved) { return; }
11242                 // Postpone to next JS tick so internal click event handling
11243                 // still see it as "moved".
11244                 setTimeout(L.bind(this._resetState, this), 0);
11245
11246                 var bounds = new L.LatLngBounds(
11247                         this._map.containerPointToLatLng(this._startPoint),
11248                         this._map.containerPointToLatLng(this._point));
11249
11250                 this._map
11251                         .fitBounds(bounds)
11252                         .fire('boxzoomend', {boxZoomBounds: bounds});
11253         },
11254
11255         _onKeyDown: function (e) {
11256                 if (e.keyCode === 27) {
11257                         this._finish();
11258                 }
11259         }
11260 });
11261
11262 // @section Handlers
11263 // @property boxZoom: Handler
11264 // Box (shift-drag with mouse) zoom handler.
11265 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11266
11267
11268
11269 /*
11270  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
11271  */
11272
11273 // @namespace Map
11274 // @section Keyboard Navigation Options
11275 L.Map.mergeOptions({
11276         // @option keyboard: Boolean = true
11277         // Makes the map focusable and allows users to navigate the map with keyboard
11278         // arrows and `+`/`-` keys.
11279         keyboard: true,
11280
11281         // @option keyboardPanDelta: Number = 80
11282         // Amount of pixels to pan when pressing an arrow key.
11283         keyboardPanDelta: 80
11284 });
11285
11286 L.Map.Keyboard = L.Handler.extend({
11287
11288         keyCodes: {
11289                 left:    [37],
11290                 right:   [39],
11291                 down:    [40],
11292                 up:      [38],
11293                 zoomIn:  [187, 107, 61, 171],
11294                 zoomOut: [189, 109, 54, 173]
11295         },
11296
11297         initialize: function (map) {
11298                 this._map = map;
11299
11300                 this._setPanDelta(map.options.keyboardPanDelta);
11301                 this._setZoomDelta(map.options.zoomDelta);
11302         },
11303
11304         addHooks: function () {
11305                 var container = this._map._container;
11306
11307                 // make the container focusable by tabbing
11308                 if (container.tabIndex <= 0) {
11309                         container.tabIndex = '0';
11310                 }
11311
11312                 L.DomEvent.on(container, {
11313                         focus: this._onFocus,
11314                         blur: this._onBlur,
11315                         mousedown: this._onMouseDown
11316                 }, this);
11317
11318                 this._map.on({
11319                         focus: this._addHooks,
11320                         blur: this._removeHooks
11321                 }, this);
11322         },
11323
11324         removeHooks: function () {
11325                 this._removeHooks();
11326
11327                 L.DomEvent.off(this._map._container, {
11328                         focus: this._onFocus,
11329                         blur: this._onBlur,
11330                         mousedown: this._onMouseDown
11331                 }, this);
11332
11333                 this._map.off({
11334                         focus: this._addHooks,
11335                         blur: this._removeHooks
11336                 }, this);
11337         },
11338
11339         _onMouseDown: function () {
11340                 if (this._focused) { return; }
11341
11342                 var body = document.body,
11343                     docEl = document.documentElement,
11344                     top = body.scrollTop || docEl.scrollTop,
11345                     left = body.scrollLeft || docEl.scrollLeft;
11346
11347                 this._map._container.focus();
11348
11349                 window.scrollTo(left, top);
11350         },
11351
11352         _onFocus: function () {
11353                 this._focused = true;
11354                 this._map.fire('focus');
11355         },
11356
11357         _onBlur: function () {
11358                 this._focused = false;
11359                 this._map.fire('blur');
11360         },
11361
11362         _setPanDelta: function (panDelta) {
11363                 var keys = this._panKeys = {},
11364                     codes = this.keyCodes,
11365                     i, len;
11366
11367                 for (i = 0, len = codes.left.length; i < len; i++) {
11368                         keys[codes.left[i]] = [-1 * panDelta, 0];
11369                 }
11370                 for (i = 0, len = codes.right.length; i < len; i++) {
11371                         keys[codes.right[i]] = [panDelta, 0];
11372                 }
11373                 for (i = 0, len = codes.down.length; i < len; i++) {
11374                         keys[codes.down[i]] = [0, panDelta];
11375                 }
11376                 for (i = 0, len = codes.up.length; i < len; i++) {
11377                         keys[codes.up[i]] = [0, -1 * panDelta];
11378                 }
11379         },
11380
11381         _setZoomDelta: function (zoomDelta) {
11382                 var keys = this._zoomKeys = {},
11383                     codes = this.keyCodes,
11384                     i, len;
11385
11386                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
11387                         keys[codes.zoomIn[i]] = zoomDelta;
11388                 }
11389                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
11390                         keys[codes.zoomOut[i]] = -zoomDelta;
11391                 }
11392         },
11393
11394         _addHooks: function () {
11395                 L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
11396         },
11397
11398         _removeHooks: function () {
11399                 L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
11400         },
11401
11402         _onKeyDown: function (e) {
11403                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
11404
11405                 var key = e.keyCode,
11406                     map = this._map,
11407                     offset;
11408
11409                 if (key in this._panKeys) {
11410
11411                         if (map._panAnim && map._panAnim._inProgress) { return; }
11412
11413                         offset = this._panKeys[key];
11414                         if (e.shiftKey) {
11415                                 offset = L.point(offset).multiplyBy(3);
11416                         }
11417
11418                         map.panBy(offset);
11419
11420                         if (map.options.maxBounds) {
11421                                 map.panInsideBounds(map.options.maxBounds);
11422                         }
11423
11424                 } else if (key in this._zoomKeys) {
11425                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
11426
11427                 } else if (key === 27) {
11428                         map.closePopup();
11429
11430                 } else {
11431                         return;
11432                 }
11433
11434                 L.DomEvent.stop(e);
11435         }
11436 });
11437
11438 // @section Handlers
11439 // @section Handlers
11440 // @property keyboard: Handler
11441 // Keyboard navigation handler.
11442 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
11443
11444
11445
11446 /*
11447  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
11448  */
11449
11450
11451 /* @namespace Marker
11452  * @section Interaction handlers
11453  *
11454  * 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:
11455  *
11456  * ```js
11457  * marker.dragging.disable();
11458  * ```
11459  *
11460  * @property dragging: Handler
11461  * Marker dragging handler (by both mouse and touch).
11462  */
11463
11464 L.Handler.MarkerDrag = L.Handler.extend({
11465         initialize: function (marker) {
11466                 this._marker = marker;
11467         },
11468
11469         addHooks: function () {
11470                 var icon = this._marker._icon;
11471
11472                 if (!this._draggable) {
11473                         this._draggable = new L.Draggable(icon, icon, true);
11474                 }
11475
11476                 this._draggable.on({
11477                         dragstart: this._onDragStart,
11478                         drag: this._onDrag,
11479                         dragend: this._onDragEnd
11480                 }, this).enable();
11481
11482                 L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
11483         },
11484
11485         removeHooks: function () {
11486                 this._draggable.off({
11487                         dragstart: this._onDragStart,
11488                         drag: this._onDrag,
11489                         dragend: this._onDragEnd
11490                 }, this).disable();
11491
11492                 if (this._marker._icon) {
11493                         L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
11494                 }
11495         },
11496
11497         moved: function () {
11498                 return this._draggable && this._draggable._moved;
11499         },
11500
11501         _onDragStart: function () {
11502                 // @section Dragging events
11503                 // @event dragstart: Event
11504                 // Fired when the user starts dragging the marker.
11505
11506                 // @event movestart: Event
11507                 // Fired when the marker starts moving (because of dragging).
11508
11509                 this._oldLatLng = this._marker.getLatLng();
11510                 this._marker
11511                     .closePopup()
11512                     .fire('movestart')
11513                     .fire('dragstart');
11514         },
11515
11516         _onDrag: function (e) {
11517                 var marker = this._marker,
11518                     shadow = marker._shadow,
11519                     iconPos = L.DomUtil.getPosition(marker._icon),
11520                     latlng = marker._map.layerPointToLatLng(iconPos);
11521
11522                 // update shadow position
11523                 if (shadow) {
11524                         L.DomUtil.setPosition(shadow, iconPos);
11525                 }
11526
11527                 marker._latlng = latlng;
11528                 e.latlng = latlng;
11529                 e.oldLatLng = this._oldLatLng;
11530
11531                 // @event drag: Event
11532                 // Fired repeatedly while the user drags the marker.
11533                 marker
11534                     .fire('move', e)
11535                     .fire('drag', e);
11536         },
11537
11538         _onDragEnd: function (e) {
11539                 // @event dragend: DragEndEvent
11540                 // Fired when the user stops dragging the marker.
11541
11542                 // @event moveend: Event
11543                 // Fired when the marker stops moving (because of dragging).
11544                 delete this._oldLatLng;
11545                 this._marker
11546                     .fire('moveend')
11547                     .fire('dragend', e);
11548         }
11549 });
11550
11551
11552
11553 /*
11554  * @class Control
11555  * @aka L.Control
11556  *
11557  * L.Control is a base class for implementing map controls. Handles positioning.
11558  * All other controls extend from this class.
11559  */
11560
11561 L.Control = L.Class.extend({
11562         // @section
11563         // @aka Control options
11564         options: {
11565                 // @option position: String = 'topright'
11566                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
11567                 // `'topright'`, `'bottomleft'` or `'bottomright'`
11568                 position: 'topright'
11569         },
11570
11571         initialize: function (options) {
11572                 L.setOptions(this, options);
11573         },
11574
11575         /* @section
11576          * Classes extending L.Control will inherit the following methods:
11577          *
11578          * @method getPosition: string
11579          * Returns the position of the control.
11580          */
11581         getPosition: function () {
11582                 return this.options.position;
11583         },
11584
11585         // @method setPosition(position: string): this
11586         // Sets the position of the control.
11587         setPosition: function (position) {
11588                 var map = this._map;
11589
11590                 if (map) {
11591                         map.removeControl(this);
11592                 }
11593
11594                 this.options.position = position;
11595
11596                 if (map) {
11597                         map.addControl(this);
11598                 }
11599
11600                 return this;
11601         },
11602
11603         // @method getContainer: HTMLElement
11604         // Returns the HTMLElement that contains the control.
11605         getContainer: function () {
11606                 return this._container;
11607         },
11608
11609         // @method addTo(map: Map): this
11610         // Adds the control to the given map.
11611         addTo: function (map) {
11612                 this.remove();
11613                 this._map = map;
11614
11615                 var container = this._container = this.onAdd(map),
11616                     pos = this.getPosition(),
11617                     corner = map._controlCorners[pos];
11618
11619                 L.DomUtil.addClass(container, 'leaflet-control');
11620
11621                 if (pos.indexOf('bottom') !== -1) {
11622                         corner.insertBefore(container, corner.firstChild);
11623                 } else {
11624                         corner.appendChild(container);
11625                 }
11626
11627                 return this;
11628         },
11629
11630         // @method remove: this
11631         // Removes the control from the map it is currently active on.
11632         remove: function () {
11633                 if (!this._map) {
11634                         return this;
11635                 }
11636
11637                 L.DomUtil.remove(this._container);
11638
11639                 if (this.onRemove) {
11640                         this.onRemove(this._map);
11641                 }
11642
11643                 this._map = null;
11644
11645                 return this;
11646         },
11647
11648         _refocusOnMap: function (e) {
11649                 // if map exists and event is not a keyboard event
11650                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
11651                         this._map.getContainer().focus();
11652                 }
11653         }
11654 });
11655
11656 L.control = function (options) {
11657         return new L.Control(options);
11658 };
11659
11660 /* @section Extension methods
11661  * @uninheritable
11662  *
11663  * Every control should extend from `L.Control` and (re-)implement the following methods.
11664  *
11665  * @method onAdd(map: Map): HTMLElement
11666  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
11667  *
11668  * @method onRemove(map: Map)
11669  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
11670  */
11671
11672 /* @namespace Map
11673  * @section Methods for Layers and Controls
11674  */
11675 L.Map.include({
11676         // @method addControl(control: Control): this
11677         // Adds the given control to the map
11678         addControl: function (control) {
11679                 control.addTo(this);
11680                 return this;
11681         },
11682
11683         // @method removeControl(control: Control): this
11684         // Removes the given control from the map
11685         removeControl: function (control) {
11686                 control.remove();
11687                 return this;
11688         },
11689
11690         _initControlPos: function () {
11691                 var corners = this._controlCorners = {},
11692                     l = 'leaflet-',
11693                     container = this._controlContainer =
11694                             L.DomUtil.create('div', l + 'control-container', this._container);
11695
11696                 function createCorner(vSide, hSide) {
11697                         var className = l + vSide + ' ' + l + hSide;
11698
11699                         corners[vSide + hSide] = L.DomUtil.create('div', className, container);
11700                 }
11701
11702                 createCorner('top', 'left');
11703                 createCorner('top', 'right');
11704                 createCorner('bottom', 'left');
11705                 createCorner('bottom', 'right');
11706         },
11707
11708         _clearControlPos: function () {
11709                 L.DomUtil.remove(this._controlContainer);
11710         }
11711 });
11712
11713
11714
11715 /*
11716  * @class Control.Zoom
11717  * @aka L.Control.Zoom
11718  * @inherits Control
11719  *
11720  * 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`.
11721  */
11722
11723 L.Control.Zoom = L.Control.extend({
11724         // @section
11725         // @aka Control.Zoom options
11726         options: {
11727                 position: 'topleft',
11728
11729                 // @option zoomInText: String = '+'
11730                 // The text set on the 'zoom in' button.
11731                 zoomInText: '+',
11732
11733                 // @option zoomInTitle: String = 'Zoom in'
11734                 // The title set on the 'zoom in' button.
11735                 zoomInTitle: 'Zoom in',
11736
11737                 // @option zoomOutText: String = '-'
11738                 // The text set on the 'zoom out' button.
11739                 zoomOutText: '-',
11740
11741                 // @option zoomOutTitle: String = 'Zoom out'
11742                 // The title set on the 'zoom out' button.
11743                 zoomOutTitle: 'Zoom out'
11744         },
11745
11746         onAdd: function (map) {
11747                 var zoomName = 'leaflet-control-zoom',
11748                     container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
11749                     options = this.options;
11750
11751                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
11752                         zoomName + '-in',  container, this._zoomIn);
11753                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
11754                         zoomName + '-out', container, this._zoomOut);
11755
11756                 this._updateDisabled();
11757                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
11758
11759                 return container;
11760         },
11761
11762         onRemove: function (map) {
11763                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
11764         },
11765
11766         disable: function () {
11767                 this._disabled = true;
11768                 this._updateDisabled();
11769                 return this;
11770         },
11771
11772         enable: function () {
11773                 this._disabled = false;
11774                 this._updateDisabled();
11775                 return this;
11776         },
11777
11778         _zoomIn: function (e) {
11779                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
11780                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11781                 }
11782         },
11783
11784         _zoomOut: function (e) {
11785                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
11786                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11787                 }
11788         },
11789
11790         _createButton: function (html, title, className, container, fn) {
11791                 var link = L.DomUtil.create('a', className, container);
11792                 link.innerHTML = html;
11793                 link.href = '#';
11794                 link.title = title;
11795
11796                 L.DomEvent
11797                     .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
11798                     .on(link, 'click', L.DomEvent.stop)
11799                     .on(link, 'click', fn, this)
11800                     .on(link, 'click', this._refocusOnMap, this);
11801
11802                 return link;
11803         },
11804
11805         _updateDisabled: function () {
11806                 var map = this._map,
11807                     className = 'leaflet-disabled';
11808
11809                 L.DomUtil.removeClass(this._zoomInButton, className);
11810                 L.DomUtil.removeClass(this._zoomOutButton, className);
11811
11812                 if (this._disabled || map._zoom === map.getMinZoom()) {
11813                         L.DomUtil.addClass(this._zoomOutButton, className);
11814                 }
11815                 if (this._disabled || map._zoom === map.getMaxZoom()) {
11816                         L.DomUtil.addClass(this._zoomInButton, className);
11817                 }
11818         }
11819 });
11820
11821 // @namespace Map
11822 // @section Control options
11823 // @option zoomControl: Boolean = true
11824 // Whether a [zoom control](#control-zoom) is added to the map by default.
11825 L.Map.mergeOptions({
11826         zoomControl: true
11827 });
11828
11829 L.Map.addInitHook(function () {
11830         if (this.options.zoomControl) {
11831                 this.zoomControl = new L.Control.Zoom();
11832                 this.addControl(this.zoomControl);
11833         }
11834 });
11835
11836 // @namespace Control.Zoom
11837 // @factory L.control.zoom(options: Control.Zoom options)
11838 // Creates a zoom control
11839 L.control.zoom = function (options) {
11840         return new L.Control.Zoom(options);
11841 };
11842
11843
11844
11845 /*
11846  * @class Control.Attribution
11847  * @aka L.Control.Attribution
11848  * @inherits Control
11849  *
11850  * 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.
11851  */
11852
11853 L.Control.Attribution = L.Control.extend({
11854         // @section
11855         // @aka Control.Attribution options
11856         options: {
11857                 position: 'bottomright',
11858
11859                 // @option prefix: String = 'Leaflet'
11860                 // The HTML text shown before the attributions. Pass `false` to disable.
11861                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
11862         },
11863
11864         initialize: function (options) {
11865                 L.setOptions(this, options);
11866
11867                 this._attributions = {};
11868         },
11869
11870         onAdd: function (map) {
11871                 map.attributionControl = this;
11872                 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
11873                 if (L.DomEvent) {
11874                         L.DomEvent.disableClickPropagation(this._container);
11875                 }
11876
11877                 // TODO ugly, refactor
11878                 for (var i in map._layers) {
11879                         if (map._layers[i].getAttribution) {
11880                                 this.addAttribution(map._layers[i].getAttribution());
11881                         }
11882                 }
11883
11884                 this._update();
11885
11886                 return this._container;
11887         },
11888
11889         // @method setPrefix(prefix: String): this
11890         // Sets the text before the attributions.
11891         setPrefix: function (prefix) {
11892                 this.options.prefix = prefix;
11893                 this._update();
11894                 return this;
11895         },
11896
11897         // @method addAttribution(text: String): this
11898         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
11899         addAttribution: function (text) {
11900                 if (!text) { return this; }
11901
11902                 if (!this._attributions[text]) {
11903                         this._attributions[text] = 0;
11904                 }
11905                 this._attributions[text]++;
11906
11907                 this._update();
11908
11909                 return this;
11910         },
11911
11912         // @method removeAttribution(text: String): this
11913         // Removes an attribution text.
11914         removeAttribution: function (text) {
11915                 if (!text) { return this; }
11916
11917                 if (this._attributions[text]) {
11918                         this._attributions[text]--;
11919                         this._update();
11920                 }
11921
11922                 return this;
11923         },
11924
11925         _update: function () {
11926                 if (!this._map) { return; }
11927
11928                 var attribs = [];
11929
11930                 for (var i in this._attributions) {
11931                         if (this._attributions[i]) {
11932                                 attribs.push(i);
11933                         }
11934                 }
11935
11936                 var prefixAndAttribs = [];
11937
11938                 if (this.options.prefix) {
11939                         prefixAndAttribs.push(this.options.prefix);
11940                 }
11941                 if (attribs.length) {
11942                         prefixAndAttribs.push(attribs.join(', '));
11943                 }
11944
11945                 this._container.innerHTML = prefixAndAttribs.join(' | ');
11946         }
11947 });
11948
11949 // @namespace Map
11950 // @section Control options
11951 // @option attributionControl: Boolean = true
11952 // Whether a [attribution control](#control-attribution) is added to the map by default.
11953 L.Map.mergeOptions({
11954         attributionControl: true
11955 });
11956
11957 L.Map.addInitHook(function () {
11958         if (this.options.attributionControl) {
11959                 new L.Control.Attribution().addTo(this);
11960         }
11961 });
11962
11963 // @namespace Control.Attribution
11964 // @factory L.control.attribution(options: Control.Attribution options)
11965 // Creates an attribution control.
11966 L.control.attribution = function (options) {
11967         return new L.Control.Attribution(options);
11968 };
11969
11970
11971
11972 /*
11973  * @class Control.Scale
11974  * @aka L.Control.Scale
11975  * @inherits Control
11976  *
11977  * 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`.
11978  *
11979  * @example
11980  *
11981  * ```js
11982  * L.control.scale().addTo(map);
11983  * ```
11984  */
11985
11986 L.Control.Scale = L.Control.extend({
11987         // @section
11988         // @aka Control.Scale options
11989         options: {
11990                 position: 'bottomleft',
11991
11992                 // @option maxWidth: Number = 100
11993                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
11994                 maxWidth: 100,
11995
11996                 // @option metric: Boolean = True
11997                 // Whether to show the metric scale line (m/km).
11998                 metric: true,
11999
12000                 // @option imperial: Boolean = True
12001                 // Whether to show the imperial scale line (mi/ft).
12002                 imperial: true
12003
12004                 // @option updateWhenIdle: Boolean = false
12005                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12006         },
12007
12008         onAdd: function (map) {
12009                 var className = 'leaflet-control-scale',
12010                     container = L.DomUtil.create('div', className),
12011                     options = this.options;
12012
12013                 this._addScales(options, className + '-line', container);
12014
12015                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12016                 map.whenReady(this._update, this);
12017
12018                 return container;
12019         },
12020
12021         onRemove: function (map) {
12022                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12023         },
12024
12025         _addScales: function (options, className, container) {
12026                 if (options.metric) {
12027                         this._mScale = L.DomUtil.create('div', className, container);
12028                 }
12029                 if (options.imperial) {
12030                         this._iScale = L.DomUtil.create('div', className, container);
12031                 }
12032         },
12033
12034         _update: function () {
12035                 var map = this._map,
12036                     y = map.getSize().y / 2;
12037
12038                 var maxMeters = map.distance(
12039                                 map.containerPointToLatLng([0, y]),
12040                                 map.containerPointToLatLng([this.options.maxWidth, y]));
12041
12042                 this._updateScales(maxMeters);
12043         },
12044
12045         _updateScales: function (maxMeters) {
12046                 if (this.options.metric && maxMeters) {
12047                         this._updateMetric(maxMeters);
12048                 }
12049                 if (this.options.imperial && maxMeters) {
12050                         this._updateImperial(maxMeters);
12051                 }
12052         },
12053
12054         _updateMetric: function (maxMeters) {
12055                 var meters = this._getRoundNum(maxMeters),
12056                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12057
12058                 this._updateScale(this._mScale, label, meters / maxMeters);
12059         },
12060
12061         _updateImperial: function (maxMeters) {
12062                 var maxFeet = maxMeters * 3.2808399,
12063                     maxMiles, miles, feet;
12064
12065                 if (maxFeet > 5280) {
12066                         maxMiles = maxFeet / 5280;
12067                         miles = this._getRoundNum(maxMiles);
12068                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12069
12070                 } else {
12071                         feet = this._getRoundNum(maxFeet);
12072                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12073                 }
12074         },
12075
12076         _updateScale: function (scale, text, ratio) {
12077                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12078                 scale.innerHTML = text;
12079         },
12080
12081         _getRoundNum: function (num) {
12082                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12083                     d = num / pow10;
12084
12085                 d = d >= 10 ? 10 :
12086                     d >= 5 ? 5 :
12087                     d >= 3 ? 3 :
12088                     d >= 2 ? 2 : 1;
12089
12090                 return pow10 * d;
12091         }
12092 });
12093
12094
12095 // @factory L.control.scale(options?: Control.Scale options)
12096 // Creates an scale control with the given options.
12097 L.control.scale = function (options) {
12098         return new L.Control.Scale(options);
12099 };
12100
12101
12102
12103 /*
12104  * @class Control.Layers
12105  * @aka L.Control.Layers
12106  * @inherits Control
12107  *
12108  * 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`.
12109  *
12110  * @example
12111  *
12112  * ```js
12113  * var baseLayers = {
12114  *      "Mapbox": mapbox,
12115  *      "OpenStreetMap": osm
12116  * };
12117  *
12118  * var overlays = {
12119  *      "Marker": marker,
12120  *      "Roads": roadsLayer
12121  * };
12122  *
12123  * L.control.layers(baseLayers, overlays).addTo(map);
12124  * ```
12125  *
12126  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12127  *
12128  * ```js
12129  * {
12130  *     "<someName1>": layer1,
12131  *     "<someName2>": layer2
12132  * }
12133  * ```
12134  *
12135  * The layer names can contain HTML, which allows you to add additional styling to the items:
12136  *
12137  * ```js
12138  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12139  * ```
12140  */
12141
12142
12143 L.Control.Layers = L.Control.extend({
12144         // @section
12145         // @aka Control.Layers options
12146         options: {
12147                 // @option collapsed: Boolean = true
12148                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12149                 collapsed: true,
12150                 position: 'topright',
12151
12152                 // @option autoZIndex: Boolean = true
12153                 // 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.
12154                 autoZIndex: true,
12155
12156                 // @option hideSingleBase: Boolean = false
12157                 // If `true`, the base layers in the control will be hidden when there is only one.
12158                 hideSingleBase: false
12159         },
12160
12161         initialize: function (baseLayers, overlays, options) {
12162                 L.setOptions(this, options);
12163
12164                 this._layers = [];
12165                 this._lastZIndex = 0;
12166                 this._handlingClick = false;
12167
12168                 for (var i in baseLayers) {
12169                         this._addLayer(baseLayers[i], i);
12170                 }
12171
12172                 for (i in overlays) {
12173                         this._addLayer(overlays[i], i, true);
12174                 }
12175         },
12176
12177         onAdd: function (map) {
12178                 this._initLayout();
12179                 this._update();
12180
12181                 this._map = map;
12182                 map.on('zoomend', this._checkDisabledLayers, this);
12183
12184                 return this._container;
12185         },
12186
12187         onRemove: function () {
12188                 this._map.off('zoomend', this._checkDisabledLayers, this);
12189
12190                 for (var i = 0; i < this._layers.length; i++) {
12191                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
12192                 }
12193         },
12194
12195         // @method addBaseLayer(layer: Layer, name: String): this
12196         // Adds a base layer (radio button entry) with the given name to the control.
12197         addBaseLayer: function (layer, name) {
12198                 this._addLayer(layer, name);
12199                 return (this._map) ? this._update() : this;
12200         },
12201
12202         // @method addOverlay(layer: Layer, name: String): this
12203         // Adds an overlay (checkbox entry) with the given name to the control.
12204         addOverlay: function (layer, name) {
12205                 this._addLayer(layer, name, true);
12206                 return (this._map) ? this._update() : this;
12207         },
12208
12209         // @method removeLayer(layer: Layer): this
12210         // Remove the given layer from the control.
12211         removeLayer: function (layer) {
12212                 layer.off('add remove', this._onLayerChange, this);
12213
12214                 var obj = this._getLayer(L.stamp(layer));
12215                 if (obj) {
12216                         this._layers.splice(this._layers.indexOf(obj), 1);
12217                 }
12218                 return (this._map) ? this._update() : this;
12219         },
12220
12221         // @method expand(): this
12222         // Expand the control container if collapsed.
12223         expand: function () {
12224                 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12225                 this._form.style.height = null;
12226                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12227                 if (acceptableHeight < this._form.clientHeight) {
12228                         L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12229                         this._form.style.height = acceptableHeight + 'px';
12230                 } else {
12231                         L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12232                 }
12233                 this._checkDisabledLayers();
12234                 return this;
12235         },
12236
12237         // @method collapse(): this
12238         // Collapse the control container if expanded.
12239         collapse: function () {
12240                 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12241                 return this;
12242         },
12243
12244         _initLayout: function () {
12245                 var className = 'leaflet-control-layers',
12246                     container = this._container = L.DomUtil.create('div', className);
12247
12248                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
12249                 container.setAttribute('aria-haspopup', true);
12250
12251                 L.DomEvent.disableClickPropagation(container);
12252                 if (!L.Browser.touch) {
12253                         L.DomEvent.disableScrollPropagation(container);
12254                 }
12255
12256                 var form = this._form = L.DomUtil.create('form', className + '-list');
12257
12258                 if (this.options.collapsed) {
12259                         if (!L.Browser.android) {
12260                                 L.DomEvent.on(container, {
12261                                         mouseenter: this.expand,
12262                                         mouseleave: this.collapse
12263                                 }, this);
12264                         }
12265
12266                         var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
12267                         link.href = '#';
12268                         link.title = 'Layers';
12269
12270                         if (L.Browser.touch) {
12271                                 L.DomEvent
12272                                     .on(link, 'click', L.DomEvent.stop)
12273                                     .on(link, 'click', this.expand, this);
12274                         } else {
12275                                 L.DomEvent.on(link, 'focus', this.expand, this);
12276                         }
12277
12278                         // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
12279                         L.DomEvent.on(form, 'click', function () {
12280                                 setTimeout(L.bind(this._onInputClick, this), 0);
12281                         }, this);
12282
12283                         this._map.on('click', this.collapse, this);
12284                         // TODO keyboard accessibility
12285                 } else {
12286                         this.expand();
12287                 }
12288
12289                 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
12290                 this._separator = L.DomUtil.create('div', className + '-separator', form);
12291                 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
12292
12293                 container.appendChild(form);
12294         },
12295
12296         _getLayer: function (id) {
12297                 for (var i = 0; i < this._layers.length; i++) {
12298
12299                         if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
12300                                 return this._layers[i];
12301                         }
12302                 }
12303         },
12304
12305         _addLayer: function (layer, name, overlay) {
12306                 layer.on('add remove', this._onLayerChange, this);
12307
12308                 this._layers.push({
12309                         layer: layer,
12310                         name: name,
12311                         overlay: overlay
12312                 });
12313
12314                 if (this.options.autoZIndex && layer.setZIndex) {
12315                         this._lastZIndex++;
12316                         layer.setZIndex(this._lastZIndex);
12317                 }
12318         },
12319
12320         _update: function () {
12321                 if (!this._container) { return this; }
12322
12323                 L.DomUtil.empty(this._baseLayersList);
12324                 L.DomUtil.empty(this._overlaysList);
12325
12326                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
12327
12328                 for (i = 0; i < this._layers.length; i++) {
12329                         obj = this._layers[i];
12330                         this._addItem(obj);
12331                         overlaysPresent = overlaysPresent || obj.overlay;
12332                         baseLayersPresent = baseLayersPresent || !obj.overlay;
12333                         baseLayersCount += !obj.overlay ? 1 : 0;
12334                 }
12335
12336                 // Hide base layers section if there's only one layer.
12337                 if (this.options.hideSingleBase) {
12338                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
12339                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
12340                 }
12341
12342                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
12343
12344                 return this;
12345         },
12346
12347         _onLayerChange: function (e) {
12348                 if (!this._handlingClick) {
12349                         this._update();
12350                 }
12351
12352                 var obj = this._getLayer(L.stamp(e.target));
12353
12354                 // @namespace Map
12355                 // @section Layer events
12356                 // @event baselayerchange: LayersControlEvent
12357                 // Fired when the base layer is changed through the [layer control](#control-layers).
12358                 // @event overlayadd: LayersControlEvent
12359                 // Fired when an overlay is selected through the [layer control](#control-layers).
12360                 // @event overlayremove: LayersControlEvent
12361                 // Fired when an overlay is deselected through the [layer control](#control-layers).
12362                 // @namespace Control.Layers
12363                 var type = obj.overlay ?
12364                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
12365                         (e.type === 'add' ? 'baselayerchange' : null);
12366
12367                 if (type) {
12368                         this._map.fire(type, obj);
12369                 }
12370         },
12371
12372         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
12373         _createRadioElement: function (name, checked) {
12374
12375                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
12376                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
12377
12378                 var radioFragment = document.createElement('div');
12379                 radioFragment.innerHTML = radioHtml;
12380
12381                 return radioFragment.firstChild;
12382         },
12383
12384         _addItem: function (obj) {
12385                 var label = document.createElement('label'),
12386                     checked = this._map.hasLayer(obj.layer),
12387                     input;
12388
12389                 if (obj.overlay) {
12390                         input = document.createElement('input');
12391                         input.type = 'checkbox';
12392                         input.className = 'leaflet-control-layers-selector';
12393                         input.defaultChecked = checked;
12394                 } else {
12395                         input = this._createRadioElement('leaflet-base-layers', checked);
12396                 }
12397
12398                 input.layerId = L.stamp(obj.layer);
12399
12400                 L.DomEvent.on(input, 'click', this._onInputClick, this);
12401
12402                 var name = document.createElement('span');
12403                 name.innerHTML = ' ' + obj.name;
12404
12405                 // Helps from preventing layer control flicker when checkboxes are disabled
12406                 // https://github.com/Leaflet/Leaflet/issues/2771
12407                 var holder = document.createElement('div');
12408
12409                 label.appendChild(holder);
12410                 holder.appendChild(input);
12411                 holder.appendChild(name);
12412
12413                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
12414                 container.appendChild(label);
12415
12416                 this._checkDisabledLayers();
12417                 return label;
12418         },
12419
12420         _onInputClick: function () {
12421                 var inputs = this._form.getElementsByTagName('input'),
12422                     input, layer, hasLayer;
12423                 var addedLayers = [],
12424                     removedLayers = [];
12425
12426                 this._handlingClick = true;
12427
12428                 for (var i = inputs.length - 1; i >= 0; i--) {
12429                         input = inputs[i];
12430                         layer = this._getLayer(input.layerId).layer;
12431                         hasLayer = this._map.hasLayer(layer);
12432
12433                         if (input.checked && !hasLayer) {
12434                                 addedLayers.push(layer);
12435
12436                         } else if (!input.checked && hasLayer) {
12437                                 removedLayers.push(layer);
12438                         }
12439                 }
12440
12441                 // Bugfix issue 2318: Should remove all old layers before readding new ones
12442                 for (i = 0; i < removedLayers.length; i++) {
12443                         this._map.removeLayer(removedLayers[i]);
12444                 }
12445                 for (i = 0; i < addedLayers.length; i++) {
12446                         this._map.addLayer(addedLayers[i]);
12447                 }
12448
12449                 this._handlingClick = false;
12450
12451                 this._refocusOnMap();
12452         },
12453
12454         _checkDisabledLayers: function () {
12455                 var inputs = this._form.getElementsByTagName('input'),
12456                     input,
12457                     layer,
12458                     zoom = this._map.getZoom();
12459
12460                 for (var i = inputs.length - 1; i >= 0; i--) {
12461                         input = inputs[i];
12462                         layer = this._getLayer(input.layerId).layer;
12463                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
12464                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
12465
12466                 }
12467         },
12468
12469         _expand: function () {
12470                 // Backward compatibility, remove me in 1.1.
12471                 return this.expand();
12472         },
12473
12474         _collapse: function () {
12475                 // Backward compatibility, remove me in 1.1.
12476                 return this.collapse();
12477         }
12478
12479 });
12480
12481
12482 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
12483 // 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.
12484 L.control.layers = function (baseLayers, overlays, options) {
12485         return new L.Control.Layers(baseLayers, overlays, options);
12486 };
12487
12488
12489
12490 /*
12491  * @class PosAnimation
12492  * @aka L.PosAnimation
12493  * @inherits Evented
12494  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
12495  *
12496  * @example
12497  * ```js
12498  * var fx = new L.PosAnimation();
12499  * fx.run(el, [300, 500], 0.5);
12500  * ```
12501  *
12502  * @constructor L.PosAnimation()
12503  * Creates a `PosAnimation` object.
12504  *
12505  */
12506
12507 L.PosAnimation = L.Evented.extend({
12508
12509         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
12510         // Run an animation of a given element to a new position, optionally setting
12511         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
12512         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
12513         // `0.5` by default).
12514         run: function (el, newPos, duration, easeLinearity) {
12515                 this.stop();
12516
12517                 this._el = el;
12518                 this._inProgress = true;
12519                 this._duration = duration || 0.25;
12520                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
12521
12522                 this._startPos = L.DomUtil.getPosition(el);
12523                 this._offset = newPos.subtract(this._startPos);
12524                 this._startTime = +new Date();
12525
12526                 // @event start: Event
12527                 // Fired when the animation starts
12528                 this.fire('start');
12529
12530                 this._animate();
12531         },
12532
12533         // @method stop()
12534         // Stops the animation (if currently running).
12535         stop: function () {
12536                 if (!this._inProgress) { return; }
12537
12538                 this._step(true);
12539                 this._complete();
12540         },
12541
12542         _animate: function () {
12543                 // animation loop
12544                 this._animId = L.Util.requestAnimFrame(this._animate, this);
12545                 this._step();
12546         },
12547
12548         _step: function (round) {
12549                 var elapsed = (+new Date()) - this._startTime,
12550                     duration = this._duration * 1000;
12551
12552                 if (elapsed < duration) {
12553                         this._runFrame(this._easeOut(elapsed / duration), round);
12554                 } else {
12555                         this._runFrame(1);
12556                         this._complete();
12557                 }
12558         },
12559
12560         _runFrame: function (progress, round) {
12561                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
12562                 if (round) {
12563                         pos._round();
12564                 }
12565                 L.DomUtil.setPosition(this._el, pos);
12566
12567                 // @event step: Event
12568                 // Fired continuously during the animation.
12569                 this.fire('step');
12570         },
12571
12572         _complete: function () {
12573                 L.Util.cancelAnimFrame(this._animId);
12574
12575                 this._inProgress = false;
12576                 // @event end: Event
12577                 // Fired when the animation ends.
12578                 this.fire('end');
12579         },
12580
12581         _easeOut: function (t) {
12582                 return 1 - Math.pow(1 - t, this._easeOutPower);
12583         }
12584 });
12585
12586
12587
12588 /*
12589  * Extends L.Map to handle panning animations.
12590  */
12591
12592 L.Map.include({
12593
12594         setView: function (center, zoom, options) {
12595
12596                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
12597                 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
12598                 options = options || {};
12599
12600                 this._stop();
12601
12602                 if (this._loaded && !options.reset && options !== true) {
12603
12604                         if (options.animate !== undefined) {
12605                                 options.zoom = L.extend({animate: options.animate}, options.zoom);
12606                                 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
12607                         }
12608
12609                         // try animating pan or zoom
12610                         var moved = (this._zoom !== zoom) ?
12611                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
12612                                 this._tryAnimatedPan(center, options.pan);
12613
12614                         if (moved) {
12615                                 // prevent resize handler call, the view will refresh after animation anyway
12616                                 clearTimeout(this._sizeTimer);
12617                                 return this;
12618                         }
12619                 }
12620
12621                 // animation didn't start, just reset the map view
12622                 this._resetView(center, zoom);
12623
12624                 return this;
12625         },
12626
12627         panBy: function (offset, options) {
12628                 offset = L.point(offset).round();
12629                 options = options || {};
12630
12631                 if (!offset.x && !offset.y) {
12632                         return this.fire('moveend');
12633                 }
12634                 // If we pan too far, Chrome gets issues with tiles
12635                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
12636                 if (options.animate !== true && !this.getSize().contains(offset)) {
12637                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
12638                         return this;
12639                 }
12640
12641                 if (!this._panAnim) {
12642                         this._panAnim = new L.PosAnimation();
12643
12644                         this._panAnim.on({
12645                                 'step': this._onPanTransitionStep,
12646                                 'end': this._onPanTransitionEnd
12647                         }, this);
12648                 }
12649
12650                 // don't fire movestart if animating inertia
12651                 if (!options.noMoveStart) {
12652                         this.fire('movestart');
12653                 }
12654
12655                 // animate pan unless animate: false specified
12656                 if (options.animate !== false) {
12657                         L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
12658
12659                         var newPos = this._getMapPanePos().subtract(offset).round();
12660                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
12661                 } else {
12662                         this._rawPanBy(offset);
12663                         this.fire('move').fire('moveend');
12664                 }
12665
12666                 return this;
12667         },
12668
12669         _onPanTransitionStep: function () {
12670                 this.fire('move');
12671         },
12672
12673         _onPanTransitionEnd: function () {
12674                 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
12675                 this.fire('moveend');
12676         },
12677
12678         _tryAnimatedPan: function (center, options) {
12679                 // difference between the new and current centers in pixels
12680                 var offset = this._getCenterOffset(center)._floor();
12681
12682                 // don't animate too far unless animate: true specified in options
12683                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
12684
12685                 this.panBy(offset, options);
12686
12687                 return true;
12688         }
12689 });
12690
12691
12692
12693 /*
12694  * Extends L.Map to handle zoom animations.
12695  */
12696
12697 // @namespace Map
12698 // @section Animation Options
12699 L.Map.mergeOptions({
12700         // @option zoomAnimation: Boolean = true
12701         // Whether the map zoom animation is enabled. By default it's enabled
12702         // in all browsers that support CSS3 Transitions except Android.
12703         zoomAnimation: true,
12704
12705         // @option zoomAnimationThreshold: Number = 4
12706         // Won't animate zoom if the zoom difference exceeds this value.
12707         zoomAnimationThreshold: 4
12708 });
12709
12710 var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera;
12711
12712 if (zoomAnimated) {
12713
12714         L.Map.addInitHook(function () {
12715                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
12716                 this._zoomAnimated = this.options.zoomAnimation;
12717
12718                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
12719                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
12720                 if (this._zoomAnimated) {
12721
12722                         this._createAnimProxy();
12723
12724                         L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
12725                 }
12726         });
12727 }
12728
12729 L.Map.include(!zoomAnimated ? {} : {
12730
12731         _createAnimProxy: function () {
12732
12733                 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
12734                 this._panes.mapPane.appendChild(proxy);
12735
12736                 this.on('zoomanim', function (e) {
12737                         var prop = L.DomUtil.TRANSFORM,
12738                             transform = proxy.style[prop];
12739
12740                         L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
12741
12742                         // workaround for case when transform is the same and so transitionend event is not fired
12743                         if (transform === proxy.style[prop] && this._animatingZoom) {
12744                                 this._onZoomTransitionEnd();
12745                         }
12746                 }, this);
12747
12748                 this.on('load moveend', function () {
12749                         var c = this.getCenter(),
12750                             z = this.getZoom();
12751                         L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
12752                 }, this);
12753         },
12754
12755         _catchTransitionEnd: function (e) {
12756                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
12757                         this._onZoomTransitionEnd();
12758                 }
12759         },
12760
12761         _nothingToAnimate: function () {
12762                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
12763         },
12764
12765         _tryAnimatedZoom: function (center, zoom, options) {
12766
12767                 if (this._animatingZoom) { return true; }
12768
12769                 options = options || {};
12770
12771                 // don't animate if disabled, not supported or zoom difference is too large
12772                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
12773                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
12774
12775                 // offset is the pixel coords of the zoom origin relative to the current center
12776                 var scale = this.getZoomScale(zoom),
12777                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
12778
12779                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
12780                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
12781
12782                 L.Util.requestAnimFrame(function () {
12783                         this
12784                             ._moveStart(true)
12785                             ._animateZoom(center, zoom, true);
12786                 }, this);
12787
12788                 return true;
12789         },
12790
12791         _animateZoom: function (center, zoom, startAnim, noUpdate) {
12792                 if (startAnim) {
12793                         this._animatingZoom = true;
12794
12795                         // remember what center/zoom to set after animation
12796                         this._animateToCenter = center;
12797                         this._animateToZoom = zoom;
12798
12799                         L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
12800                 }
12801
12802                 // @event zoomanim: ZoomAnimEvent
12803                 // Fired on every frame of a zoom animation
12804                 this.fire('zoomanim', {
12805                         center: center,
12806                         zoom: zoom,
12807                         noUpdate: noUpdate
12808                 });
12809
12810                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
12811                 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
12812         },
12813
12814         _onZoomTransitionEnd: function () {
12815                 if (!this._animatingZoom) { return; }
12816
12817                 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
12818
12819                 this._animatingZoom = false;
12820
12821                 this._move(this._animateToCenter, this._animateToZoom);
12822
12823                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
12824                 L.Util.requestAnimFrame(function () {
12825                         this._moveEnd(true);
12826                 }, this);
12827         }
12828 });
12829
12830
12831
12832 // @namespace Map
12833 // @section Methods for modifying map state
12834 L.Map.include({
12835
12836         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
12837         // Sets the view of the map (geographical center and zoom) performing a smooth
12838         // pan-zoom animation.
12839         flyTo: function (targetCenter, targetZoom, options) {
12840
12841                 options = options || {};
12842                 if (options.animate === false || !L.Browser.any3d) {
12843                         return this.setView(targetCenter, targetZoom, options);
12844                 }
12845
12846                 this._stop();
12847
12848                 var from = this.project(this.getCenter()),
12849                     to = this.project(targetCenter),
12850                     size = this.getSize(),
12851                     startZoom = this._zoom;
12852
12853                 targetCenter = L.latLng(targetCenter);
12854                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
12855
12856                 var w0 = Math.max(size.x, size.y),
12857                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
12858                     u1 = (to.distanceTo(from)) || 1,
12859                     rho = 1.42,
12860                     rho2 = rho * rho;
12861
12862                 function r(i) {
12863                         var s1 = i ? -1 : 1,
12864                             s2 = i ? w1 : w0,
12865                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
12866                             b1 = 2 * s2 * rho2 * u1,
12867                             b = t1 / b1,
12868                             sq = Math.sqrt(b * b + 1) - b;
12869
12870                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
12871                             // thus triggering an infinite loop in flyTo
12872                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
12873
12874                         return log;
12875                 }
12876
12877                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
12878                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
12879                 function tanh(n) { return sinh(n) / cosh(n); }
12880
12881                 var r0 = r(0);
12882
12883                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
12884                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
12885
12886                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
12887
12888                 var start = Date.now(),
12889                     S = (r(1) - r0) / rho,
12890                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
12891
12892                 function frame() {
12893                         var t = (Date.now() - start) / duration,
12894                             s = easeOut(t) * S;
12895
12896                         if (t <= 1) {
12897                                 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
12898
12899                                 this._move(
12900                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
12901                                         this.getScaleZoom(w0 / w(s), startZoom),
12902                                         {flyTo: true});
12903
12904                         } else {
12905                                 this
12906                                         ._move(targetCenter, targetZoom)
12907                                         ._moveEnd(true);
12908                         }
12909                 }
12910
12911                 this._moveStart(true);
12912
12913                 frame.call(this);
12914                 return this;
12915         },
12916
12917         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
12918         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
12919         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
12920         flyToBounds: function (bounds, options) {
12921                 var target = this._getBoundsCenterZoom(bounds, options);
12922                 return this.flyTo(target.center, target.zoom, options);
12923         }
12924 });
12925
12926
12927
12928 /*
12929  * Provides L.Map with convenient shortcuts for using browser geolocation features.
12930  */
12931
12932 // @namespace Map
12933
12934 L.Map.include({
12935         // @section Geolocation methods
12936         _defaultLocateOptions: {
12937                 timeout: 10000,
12938                 watch: false
12939                 // setView: false
12940                 // maxZoom: <Number>
12941                 // maximumAge: 0
12942                 // enableHighAccuracy: false
12943         },
12944
12945         // @method locate(options?: Locate options): this
12946         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
12947         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
12948         // and optionally sets the map view to the user's location with respect to
12949         // detection accuracy (or to the world view if geolocation failed).
12950         // Note that, if your page doesn't use HTTPS, this method will fail in
12951         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
12952         // See `Locate options` for more details.
12953         locate: function (options) {
12954
12955                 options = this._locateOptions = L.extend({}, this._defaultLocateOptions, options);
12956
12957                 if (!('geolocation' in navigator)) {
12958                         this._handleGeolocationError({
12959                                 code: 0,
12960                                 message: 'Geolocation not supported.'
12961                         });
12962                         return this;
12963                 }
12964
12965                 var onResponse = L.bind(this._handleGeolocationResponse, this),
12966                     onError = L.bind(this._handleGeolocationError, this);
12967
12968                 if (options.watch) {
12969                         this._locationWatchId =
12970                                 navigator.geolocation.watchPosition(onResponse, onError, options);
12971                 } else {
12972                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
12973                 }
12974                 return this;
12975         },
12976
12977         // @method stopLocate(): this
12978         // Stops watching location previously initiated by `map.locate({watch: true})`
12979         // and aborts resetting the map view if map.locate was called with
12980         // `{setView: true}`.
12981         stopLocate: function () {
12982                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
12983                         navigator.geolocation.clearWatch(this._locationWatchId);
12984                 }
12985                 if (this._locateOptions) {
12986                         this._locateOptions.setView = false;
12987                 }
12988                 return this;
12989         },
12990
12991         _handleGeolocationError: function (error) {
12992                 var c = error.code,
12993                     message = error.message ||
12994                             (c === 1 ? 'permission denied' :
12995                             (c === 2 ? 'position unavailable' : 'timeout'));
12996
12997                 if (this._locateOptions.setView && !this._loaded) {
12998                         this.fitWorld();
12999                 }
13000
13001                 // @section Location events
13002                 // @event locationerror: ErrorEvent
13003                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
13004                 this.fire('locationerror', {
13005                         code: c,
13006                         message: 'Geolocation error: ' + message + '.'
13007                 });
13008         },
13009
13010         _handleGeolocationResponse: function (pos) {
13011                 var lat = pos.coords.latitude,
13012                     lng = pos.coords.longitude,
13013                     latlng = new L.LatLng(lat, lng),
13014                     bounds = latlng.toBounds(pos.coords.accuracy),
13015                     options = this._locateOptions;
13016
13017                 if (options.setView) {
13018                         var zoom = this.getBoundsZoom(bounds);
13019                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
13020                 }
13021
13022                 var data = {
13023                         latlng: latlng,
13024                         bounds: bounds,
13025                         timestamp: pos.timestamp
13026                 };
13027
13028                 for (var i in pos.coords) {
13029                         if (typeof pos.coords[i] === 'number') {
13030                                 data[i] = pos.coords[i];
13031                         }
13032                 }
13033
13034                 // @event locationfound: LocationEvent
13035                 // Fired when geolocation (using the [`locate`](#map-locate) method)
13036                 // went successfully.
13037                 this.fire('locationfound', data);
13038         }
13039 });
13040
13041
13042
13043 }(window, document));
13044 //# sourceMappingURL=leaflet-src.map