]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
Merge remote-tracking branch 'upstream/pull/1693'
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /* @preserve
2  * Leaflet 1.3.1, a JS library for interactive maps. http://leafletjs.com
3  * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4  */
5
6 (function (global, factory) {
7         typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8         typeof define === 'function' && define.amd ? define(['exports'], factory) :
9         (factory((global.L = {})));
10 }(this, (function (exports) { 'use strict';
11
12 var version = "1.3.1";
13
14 /*
15  * @namespace Util
16  *
17  * Various utility functions, used by Leaflet internally.
18  */
19
20 var freeze = Object.freeze;
21 Object.freeze = function (obj) { return obj; };
22
23 // @function extend(dest: Object, src?: Object): Object
24 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
25 function extend(dest) {
26         var i, j, len, src;
27
28         for (j = 1, len = arguments.length; j < len; j++) {
29                 src = arguments[j];
30                 for (i in src) {
31                         dest[i] = src[i];
32                 }
33         }
34         return dest;
35 }
36
37 // @function create(proto: Object, properties?: Object): Object
38 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
39 var create = Object.create || (function () {
40         function F() {}
41         return function (proto) {
42                 F.prototype = proto;
43                 return new F();
44         };
45 })();
46
47 // @function bind(fn: Function, …): Function
48 // 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).
49 // Has a `L.bind()` shortcut.
50 function bind(fn, obj) {
51         var slice = Array.prototype.slice;
52
53         if (fn.bind) {
54                 return fn.bind.apply(fn, slice.call(arguments, 1));
55         }
56
57         var args = slice.call(arguments, 2);
58
59         return function () {
60                 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
61         };
62 }
63
64 // @property lastId: Number
65 // Last unique ID used by [`stamp()`](#util-stamp)
66 var lastId = 0;
67
68 // @function stamp(obj: Object): Number
69 // Returns the unique ID of an object, assigning it one if it doesn't have it.
70 function stamp(obj) {
71         /*eslint-disable */
72         obj._leaflet_id = obj._leaflet_id || ++lastId;
73         return obj._leaflet_id;
74         /* eslint-enable */
75 }
76
77 // @function throttle(fn: Function, time: Number, context: Object): Function
78 // Returns a function which executes function `fn` with the given scope `context`
79 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
80 // `fn` will be called no more than one time per given amount of `time`. The arguments
81 // received by the bound function will be any arguments passed when binding the
82 // function, followed by any arguments passed when invoking the bound function.
83 // Has an `L.throttle` shortcut.
84 function throttle(fn, time, context) {
85         var lock, args, wrapperFn, later;
86
87         later = function () {
88                 // reset lock and call if queued
89                 lock = false;
90                 if (args) {
91                         wrapperFn.apply(context, args);
92                         args = false;
93                 }
94         };
95
96         wrapperFn = function () {
97                 if (lock) {
98                         // called too soon, queue to call later
99                         args = arguments;
100
101                 } else {
102                         // call and lock until later
103                         fn.apply(context, arguments);
104                         setTimeout(later, time);
105                         lock = true;
106                 }
107         };
108
109         return wrapperFn;
110 }
111
112 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
113 // Returns the number `num` modulo `range` in such a way so it lies within
114 // `range[0]` and `range[1]`. The returned value will be always smaller than
115 // `range[1]` unless `includeMax` is set to `true`.
116 function wrapNum(x, range, includeMax) {
117         var max = range[1],
118             min = range[0],
119             d = max - min;
120         return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
121 }
122
123 // @function falseFn(): Function
124 // Returns a function which always returns `false`.
125 function falseFn() { return false; }
126
127 // @function formatNum(num: Number, digits?: Number): Number
128 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129 function formatNum(num, digits) {
130         var pow = Math.pow(10, (digits === undefined ? 6 : digits));
131         return Math.round(num * pow) / pow;
132 }
133
134 // @function trim(str: String): String
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
136 function trim(str) {
137         return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
138 }
139
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143         return trim(str).split(/\s+/);
144 }
145
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149         if (!obj.hasOwnProperty('options')) {
150                 obj.options = obj.options ? create(obj.options) : {};
151         }
152         for (var i in options) {
153                 obj.options[i] = options[i];
154         }
155         return obj.options;
156 }
157
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
164         var params = [];
165         for (var i in obj) {
166                 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
167         }
168         return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
169 }
170
171 var templateRe = /\{ *([\w_-]+) *\}/g;
172
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179         return str.replace(templateRe, function (str, key) {
180                 var value = data[key];
181
182                 if (value === undefined) {
183                         throw new Error('No value provided for variable ' + str);
184
185                 } else if (typeof value === 'function') {
186                         value = value(data);
187                 }
188                 return value;
189         });
190 }
191
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195         return (Object.prototype.toString.call(obj) === '[object Array]');
196 };
197
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201         for (var i = 0; i < array.length; i++) {
202                 if (array[i] === el) { return i; }
203         }
204         return -1;
205 }
206
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
212
213 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
214
215 function getPrefixed(name) {
216         return window['webkit' + name] || window['moz' + name] || window['ms' + name];
217 }
218
219 var lastTime = 0;
220
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223         var time = +new Date(),
224             timeToCall = Math.max(0, 16 - (time - lastTime));
225
226         lastTime = time + timeToCall;
227         return window.setTimeout(fn, timeToCall);
228 }
229
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232                 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
233
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241         if (immediate && requestFn === timeoutDefer) {
242                 fn.call(context);
243         } else {
244                 return requestFn.call(window, bind(fn, context));
245         }
246 }
247
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
251         if (id) {
252                 cancelFn.call(window, id);
253         }
254 }
255
256
257 var Util = (Object.freeze || Object)({
258         freeze: freeze,
259         extend: extend,
260         create: create,
261         bind: bind,
262         lastId: lastId,
263         stamp: stamp,
264         throttle: throttle,
265         wrapNum: wrapNum,
266         falseFn: falseFn,
267         formatNum: formatNum,
268         trim: trim,
269         splitWords: splitWords,
270         setOptions: setOptions,
271         getParamString: getParamString,
272         template: template,
273         isArray: isArray,
274         indexOf: indexOf,
275         emptyImageUrl: emptyImageUrl,
276         requestFn: requestFn,
277         cancelFn: cancelFn,
278         requestAnimFrame: requestAnimFrame,
279         cancelAnimFrame: cancelAnimFrame
280 });
281
282 // @class Class
283 // @aka L.Class
284
285 // @section
286 // @uninheritable
287
288 // Thanks to John Resig and Dean Edwards for inspiration!
289
290 function Class() {}
291
292 Class.extend = function (props) {
293
294         // @function extend(props: Object): Function
295         // [Extends the current class](#class-inheritance) given the properties to be included.
296         // Returns a Javascript function that is a class constructor (to be called with `new`).
297         var NewClass = function () {
298
299                 // call the constructor
300                 if (this.initialize) {
301                         this.initialize.apply(this, arguments);
302                 }
303
304                 // call all constructor hooks
305                 this.callInitHooks();
306         };
307
308         var parentProto = NewClass.__super__ = this.prototype;
309
310         var proto = create(parentProto);
311         proto.constructor = NewClass;
312
313         NewClass.prototype = proto;
314
315         // inherit parent's statics
316         for (var i in this) {
317                 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
318                         NewClass[i] = this[i];
319                 }
320         }
321
322         // mix static properties into the class
323         if (props.statics) {
324                 extend(NewClass, props.statics);
325                 delete props.statics;
326         }
327
328         // mix includes into the prototype
329         if (props.includes) {
330                 checkDeprecatedMixinEvents(props.includes);
331                 extend.apply(null, [proto].concat(props.includes));
332                 delete props.includes;
333         }
334
335         // merge options
336         if (proto.options) {
337                 props.options = extend(create(proto.options), props.options);
338         }
339
340         // mix given properties into the prototype
341         extend(proto, props);
342
343         proto._initHooks = [];
344
345         // add method for calling all hooks
346         proto.callInitHooks = function () {
347
348                 if (this._initHooksCalled) { return; }
349
350                 if (parentProto.callInitHooks) {
351                         parentProto.callInitHooks.call(this);
352                 }
353
354                 this._initHooksCalled = true;
355
356                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357                         proto._initHooks[i].call(this);
358                 }
359         };
360
361         return NewClass;
362 };
363
364
365 // @function include(properties: Object): this
366 // [Includes a mixin](#class-includes) into the current class.
367 Class.include = function (props) {
368         extend(this.prototype, props);
369         return this;
370 };
371
372 // @function mergeOptions(options: Object): this
373 // [Merges `options`](#class-options) into the defaults of the class.
374 Class.mergeOptions = function (options) {
375         extend(this.prototype.options, options);
376         return this;
377 };
378
379 // @function addInitHook(fn: Function): this
380 // Adds a [constructor hook](#class-constructor-hooks) to the class.
381 Class.addInitHook = function (fn) { // (Function) || (String, args...)
382         var args = Array.prototype.slice.call(arguments, 1);
383
384         var init = typeof fn === 'function' ? fn : function () {
385                 this[fn].apply(this, args);
386         };
387
388         this.prototype._initHooks = this.prototype._initHooks || [];
389         this.prototype._initHooks.push(init);
390         return this;
391 };
392
393 function checkDeprecatedMixinEvents(includes) {
394         if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
395
396         includes = isArray(includes) ? includes : [includes];
397
398         for (var i = 0; i < includes.length; i++) {
399                 if (includes[i] === L.Mixin.Events) {
400                         console.warn('Deprecated include of L.Mixin.Events: ' +
401                                 'this property will be removed in future releases, ' +
402                                 'please inherit from L.Evented instead.', new Error().stack);
403                 }
404         }
405 }
406
407 /*
408  * @class Evented
409  * @aka L.Evented
410  * @inherits Class
411  *
412  * 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).
413  *
414  * @example
415  *
416  * ```js
417  * map.on('click', function(e) {
418  *      alert(e.latlng);
419  * } );
420  * ```
421  *
422  * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
423  *
424  * ```js
425  * function onClick(e) { ... }
426  *
427  * map.on('click', onClick);
428  * map.off('click', onClick);
429  * ```
430  */
431
432 var Events = {
433         /* @method on(type: String, fn: Function, context?: Object): this
434          * 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'`).
435          *
436          * @alternative
437          * @method on(eventMap: Object): this
438          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
439          */
440         on: function (types, fn, context) {
441
442                 // types can be a map of types/handlers
443                 if (typeof types === 'object') {
444                         for (var type in types) {
445                                 // we don't process space-separated events here for performance;
446                                 // it's a hot path since Layer uses the on(obj) syntax
447                                 this._on(type, types[type], fn);
448                         }
449
450                 } else {
451                         // types can be a string of space-separated words
452                         types = splitWords(types);
453
454                         for (var i = 0, len = types.length; i < len; i++) {
455                                 this._on(types[i], fn, context);
456                         }
457                 }
458
459                 return this;
460         },
461
462         /* @method off(type: String, fn?: Function, context?: Object): this
463          * 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.
464          *
465          * @alternative
466          * @method off(eventMap: Object): this
467          * Removes a set of type/listener pairs.
468          *
469          * @alternative
470          * @method off: this
471          * Removes all listeners to all events on the object.
472          */
473         off: function (types, fn, context) {
474
475                 if (!types) {
476                         // clear all listeners if called without arguments
477                         delete this._events;
478
479                 } else if (typeof types === 'object') {
480                         for (var type in types) {
481                                 this._off(type, types[type], fn);
482                         }
483
484                 } else {
485                         types = splitWords(types);
486
487                         for (var i = 0, len = types.length; i < len; i++) {
488                                 this._off(types[i], fn, context);
489                         }
490                 }
491
492                 return this;
493         },
494
495         // attach listener (without syntactic sugar now)
496         _on: function (type, fn, context) {
497                 this._events = this._events || {};
498
499                 /* get/init listeners for type */
500                 var typeListeners = this._events[type];
501                 if (!typeListeners) {
502                         typeListeners = [];
503                         this._events[type] = typeListeners;
504                 }
505
506                 if (context === this) {
507                         // Less memory footprint.
508                         context = undefined;
509                 }
510                 var newListener = {fn: fn, ctx: context},
511                     listeners = typeListeners;
512
513                 // check if fn already there
514                 for (var i = 0, len = listeners.length; i < len; i++) {
515                         if (listeners[i].fn === fn && listeners[i].ctx === context) {
516                                 return;
517                         }
518                 }
519
520                 listeners.push(newListener);
521         },
522
523         _off: function (type, fn, context) {
524                 var listeners,
525                     i,
526                     len;
527
528                 if (!this._events) { return; }
529
530                 listeners = this._events[type];
531
532                 if (!listeners) {
533                         return;
534                 }
535
536                 if (!fn) {
537                         // Set all removed listeners to noop so they are not called if remove happens in fire
538                         for (i = 0, len = listeners.length; i < len; i++) {
539                                 listeners[i].fn = falseFn;
540                         }
541                         // clear all listeners for a type if function isn't specified
542                         delete this._events[type];
543                         return;
544                 }
545
546                 if (context === this) {
547                         context = undefined;
548                 }
549
550                 if (listeners) {
551
552                         // find fn and remove it
553                         for (i = 0, len = listeners.length; i < len; i++) {
554                                 var l = listeners[i];
555                                 if (l.ctx !== context) { continue; }
556                                 if (l.fn === fn) {
557
558                                         // set the removed listener to noop so that's not called if remove happens in fire
559                                         l.fn = falseFn;
560
561                                         if (this._firingCount) {
562                                                 /* copy array in case events are being fired */
563                                                 this._events[type] = listeners = listeners.slice();
564                                         }
565                                         listeners.splice(i, 1);
566
567                                         return;
568                                 }
569                         }
570                 }
571         },
572
573         // @method fire(type: String, data?: Object, propagate?: Boolean): this
574         // Fires an event of the specified type. You can optionally provide an data
575         // object — the first argument of the listener function will contain its
576         // properties. The event can optionally be propagated to event parents.
577         fire: function (type, data, propagate) {
578                 if (!this.listens(type, propagate)) { return this; }
579
580                 var event = extend({}, data, {
581                         type: type,
582                         target: this,
583                         sourceTarget: data && data.sourceTarget || this
584                 });
585
586                 if (this._events) {
587                         var listeners = this._events[type];
588
589                         if (listeners) {
590                                 this._firingCount = (this._firingCount + 1) || 1;
591                                 for (var i = 0, len = listeners.length; i < len; i++) {
592                                         var l = listeners[i];
593                                         l.fn.call(l.ctx || this, event);
594                                 }
595
596                                 this._firingCount--;
597                         }
598                 }
599
600                 if (propagate) {
601                         // propagate the event to parents (set with addEventParent)
602                         this._propagateEvent(event);
603                 }
604
605                 return this;
606         },
607
608         // @method listens(type: String): Boolean
609         // Returns `true` if a particular event type has any listeners attached to it.
610         listens: function (type, propagate) {
611                 var listeners = this._events && this._events[type];
612                 if (listeners && listeners.length) { return true; }
613
614                 if (propagate) {
615                         // also check parents for listeners if event propagates
616                         for (var id in this._eventParents) {
617                                 if (this._eventParents[id].listens(type, propagate)) { return true; }
618                         }
619                 }
620                 return false;
621         },
622
623         // @method once(…): this
624         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
625         once: function (types, fn, context) {
626
627                 if (typeof types === 'object') {
628                         for (var type in types) {
629                                 this.once(type, types[type], fn);
630                         }
631                         return this;
632                 }
633
634                 var handler = bind(function () {
635                         this
636                             .off(types, fn, context)
637                             .off(types, handler, context);
638                 }, this);
639
640                 // add a listener that's executed once and removed after that
641                 return this
642                     .on(types, fn, context)
643                     .on(types, handler, context);
644         },
645
646         // @method addEventParent(obj: Evented): this
647         // Adds an event parent - an `Evented` that will receive propagated events
648         addEventParent: function (obj) {
649                 this._eventParents = this._eventParents || {};
650                 this._eventParents[stamp(obj)] = obj;
651                 return this;
652         },
653
654         // @method removeEventParent(obj: Evented): this
655         // Removes an event parent, so it will stop receiving propagated events
656         removeEventParent: function (obj) {
657                 if (this._eventParents) {
658                         delete this._eventParents[stamp(obj)];
659                 }
660                 return this;
661         },
662
663         _propagateEvent: function (e) {
664                 for (var id in this._eventParents) {
665                         this._eventParents[id].fire(e.type, extend({
666                                 layer: e.target,
667                                 propagatedFrom: e.target
668                         }, e), true);
669                 }
670         }
671 };
672
673 // aliases; we should ditch those eventually
674
675 // @method addEventListener(…): this
676 // Alias to [`on(…)`](#evented-on)
677 Events.addEventListener = Events.on;
678
679 // @method removeEventListener(…): this
680 // Alias to [`off(…)`](#evented-off)
681
682 // @method clearAllEventListeners(…): this
683 // Alias to [`off()`](#evented-off)
684 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
685
686 // @method addOneTimeEventListener(…): this
687 // Alias to [`once(…)`](#evented-once)
688 Events.addOneTimeEventListener = Events.once;
689
690 // @method fireEvent(…): this
691 // Alias to [`fire(…)`](#evented-fire)
692 Events.fireEvent = Events.fire;
693
694 // @method hasEventListeners(…): Boolean
695 // Alias to [`listens(…)`](#evented-listens)
696 Events.hasEventListeners = Events.listens;
697
698 var Evented = Class.extend(Events);
699
700 /*
701  * @class Point
702  * @aka L.Point
703  *
704  * Represents a point with `x` and `y` coordinates in pixels.
705  *
706  * @example
707  *
708  * ```js
709  * var point = L.point(200, 300);
710  * ```
711  *
712  * 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:
713  *
714  * ```js
715  * map.panBy([200, 300]);
716  * map.panBy(L.point(200, 300));
717  * ```
718  *
719  * Note that `Point` does not inherit from Leafet's `Class` object,
720  * which means new classes can't inherit from it, and new methods
721  * can't be added to it with the `include` function.
722  */
723
724 function Point(x, y, round) {
725         // @property x: Number; The `x` coordinate of the point
726         this.x = (round ? Math.round(x) : x);
727         // @property y: Number; The `y` coordinate of the point
728         this.y = (round ? Math.round(y) : y);
729 }
730
731 var trunc = Math.trunc || function (v) {
732         return v > 0 ? Math.floor(v) : Math.ceil(v);
733 };
734
735 Point.prototype = {
736
737         // @method clone(): Point
738         // Returns a copy of the current point.
739         clone: function () {
740                 return new Point(this.x, this.y);
741         },
742
743         // @method add(otherPoint: Point): Point
744         // Returns the result of addition of the current and the given points.
745         add: function (point) {
746                 // non-destructive, returns a new point
747                 return this.clone()._add(toPoint(point));
748         },
749
750         _add: function (point) {
751                 // destructive, used directly for performance in situations where it's safe to modify existing point
752                 this.x += point.x;
753                 this.y += point.y;
754                 return this;
755         },
756
757         // @method subtract(otherPoint: Point): Point
758         // Returns the result of subtraction of the given point from the current.
759         subtract: function (point) {
760                 return this.clone()._subtract(toPoint(point));
761         },
762
763         _subtract: function (point) {
764                 this.x -= point.x;
765                 this.y -= point.y;
766                 return this;
767         },
768
769         // @method divideBy(num: Number): Point
770         // Returns the result of division of the current point by the given number.
771         divideBy: function (num) {
772                 return this.clone()._divideBy(num);
773         },
774
775         _divideBy: function (num) {
776                 this.x /= num;
777                 this.y /= num;
778                 return this;
779         },
780
781         // @method multiplyBy(num: Number): Point
782         // Returns the result of multiplication of the current point by the given number.
783         multiplyBy: function (num) {
784                 return this.clone()._multiplyBy(num);
785         },
786
787         _multiplyBy: function (num) {
788                 this.x *= num;
789                 this.y *= num;
790                 return this;
791         },
792
793         // @method scaleBy(scale: Point): Point
794         // Multiply each coordinate of the current point by each coordinate of
795         // `scale`. In linear algebra terms, multiply the point by the
796         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
797         // defined by `scale`.
798         scaleBy: function (point) {
799                 return new Point(this.x * point.x, this.y * point.y);
800         },
801
802         // @method unscaleBy(scale: Point): Point
803         // Inverse of `scaleBy`. Divide each coordinate of the current point by
804         // each coordinate of `scale`.
805         unscaleBy: function (point) {
806                 return new Point(this.x / point.x, this.y / point.y);
807         },
808
809         // @method round(): Point
810         // Returns a copy of the current point with rounded coordinates.
811         round: function () {
812                 return this.clone()._round();
813         },
814
815         _round: function () {
816                 this.x = Math.round(this.x);
817                 this.y = Math.round(this.y);
818                 return this;
819         },
820
821         // @method floor(): Point
822         // Returns a copy of the current point with floored coordinates (rounded down).
823         floor: function () {
824                 return this.clone()._floor();
825         },
826
827         _floor: function () {
828                 this.x = Math.floor(this.x);
829                 this.y = Math.floor(this.y);
830                 return this;
831         },
832
833         // @method ceil(): Point
834         // Returns a copy of the current point with ceiled coordinates (rounded up).
835         ceil: function () {
836                 return this.clone()._ceil();
837         },
838
839         _ceil: function () {
840                 this.x = Math.ceil(this.x);
841                 this.y = Math.ceil(this.y);
842                 return this;
843         },
844
845         // @method trunc(): Point
846         // Returns a copy of the current point with truncated coordinates (rounded towards zero).
847         trunc: function () {
848                 return this.clone()._trunc();
849         },
850
851         _trunc: function () {
852                 this.x = trunc(this.x);
853                 this.y = trunc(this.y);
854                 return this;
855         },
856
857         // @method distanceTo(otherPoint: Point): Number
858         // Returns the cartesian distance between the current and the given points.
859         distanceTo: function (point) {
860                 point = toPoint(point);
861
862                 var x = point.x - this.x,
863                     y = point.y - this.y;
864
865                 return Math.sqrt(x * x + y * y);
866         },
867
868         // @method equals(otherPoint: Point): Boolean
869         // Returns `true` if the given point has the same coordinates.
870         equals: function (point) {
871                 point = toPoint(point);
872
873                 return point.x === this.x &&
874                        point.y === this.y;
875         },
876
877         // @method contains(otherPoint: Point): Boolean
878         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
879         contains: function (point) {
880                 point = toPoint(point);
881
882                 return Math.abs(point.x) <= Math.abs(this.x) &&
883                        Math.abs(point.y) <= Math.abs(this.y);
884         },
885
886         // @method toString(): String
887         // Returns a string representation of the point for debugging purposes.
888         toString: function () {
889                 return 'Point(' +
890                         formatNum(this.x) + ', ' +
891                         formatNum(this.y) + ')';
892         }
893 };
894
895 // @factory L.point(x: Number, y: Number, round?: Boolean)
896 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
897
898 // @alternative
899 // @factory L.point(coords: Number[])
900 // Expects an array of the form `[x, y]` instead.
901
902 // @alternative
903 // @factory L.point(coords: Object)
904 // Expects a plain object of the form `{x: Number, y: Number}` instead.
905 function toPoint(x, y, round) {
906         if (x instanceof Point) {
907                 return x;
908         }
909         if (isArray(x)) {
910                 return new Point(x[0], x[1]);
911         }
912         if (x === undefined || x === null) {
913                 return x;
914         }
915         if (typeof x === 'object' && 'x' in x && 'y' in x) {
916                 return new Point(x.x, x.y);
917         }
918         return new Point(x, y, round);
919 }
920
921 /*
922  * @class Bounds
923  * @aka L.Bounds
924  *
925  * Represents a rectangular area in pixel coordinates.
926  *
927  * @example
928  *
929  * ```js
930  * var p1 = L.point(10, 10),
931  * p2 = L.point(40, 60),
932  * bounds = L.bounds(p1, p2);
933  * ```
934  *
935  * 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:
936  *
937  * ```js
938  * otherBounds.intersects([[10, 10], [40, 60]]);
939  * ```
940  *
941  * Note that `Bounds` does not inherit from Leafet's `Class` object,
942  * which means new classes can't inherit from it, and new methods
943  * can't be added to it with the `include` function.
944  */
945
946 function Bounds(a, b) {
947         if (!a) { return; }
948
949         var points = b ? [a, b] : a;
950
951         for (var i = 0, len = points.length; i < len; i++) {
952                 this.extend(points[i]);
953         }
954 }
955
956 Bounds.prototype = {
957         // @method extend(point: Point): this
958         // Extends the bounds to contain the given point.
959         extend: function (point) { // (Point)
960                 point = toPoint(point);
961
962                 // @property min: Point
963                 // The top left corner of the rectangle.
964                 // @property max: Point
965                 // The bottom right corner of the rectangle.
966                 if (!this.min && !this.max) {
967                         this.min = point.clone();
968                         this.max = point.clone();
969                 } else {
970                         this.min.x = Math.min(point.x, this.min.x);
971                         this.max.x = Math.max(point.x, this.max.x);
972                         this.min.y = Math.min(point.y, this.min.y);
973                         this.max.y = Math.max(point.y, this.max.y);
974                 }
975                 return this;
976         },
977
978         // @method getCenter(round?: Boolean): Point
979         // Returns the center point of the bounds.
980         getCenter: function (round) {
981                 return new Point(
982                         (this.min.x + this.max.x) / 2,
983                         (this.min.y + this.max.y) / 2, round);
984         },
985
986         // @method getBottomLeft(): Point
987         // Returns the bottom-left point of the bounds.
988         getBottomLeft: function () {
989                 return new Point(this.min.x, this.max.y);
990         },
991
992         // @method getTopRight(): Point
993         // Returns the top-right point of the bounds.
994         getTopRight: function () { // -> Point
995                 return new Point(this.max.x, this.min.y);
996         },
997
998         // @method getTopLeft(): Point
999         // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
1000         getTopLeft: function () {
1001                 return this.min; // left, top
1002         },
1003
1004         // @method getBottomRight(): Point
1005         // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1006         getBottomRight: function () {
1007                 return this.max; // right, bottom
1008         },
1009
1010         // @method getSize(): Point
1011         // Returns the size of the given bounds
1012         getSize: function () {
1013                 return this.max.subtract(this.min);
1014         },
1015
1016         // @method contains(otherBounds: Bounds): Boolean
1017         // Returns `true` if the rectangle contains the given one.
1018         // @alternative
1019         // @method contains(point: Point): Boolean
1020         // Returns `true` if the rectangle contains the given point.
1021         contains: function (obj) {
1022                 var min, max;
1023
1024                 if (typeof obj[0] === 'number' || obj instanceof Point) {
1025                         obj = toPoint(obj);
1026                 } else {
1027                         obj = toBounds(obj);
1028                 }
1029
1030                 if (obj instanceof Bounds) {
1031                         min = obj.min;
1032                         max = obj.max;
1033                 } else {
1034                         min = max = obj;
1035                 }
1036
1037                 return (min.x >= this.min.x) &&
1038                        (max.x <= this.max.x) &&
1039                        (min.y >= this.min.y) &&
1040                        (max.y <= this.max.y);
1041         },
1042
1043         // @method intersects(otherBounds: Bounds): Boolean
1044         // Returns `true` if the rectangle intersects the given bounds. Two bounds
1045         // intersect if they have at least one point in common.
1046         intersects: function (bounds) { // (Bounds) -> Boolean
1047                 bounds = toBounds(bounds);
1048
1049                 var min = this.min,
1050                     max = this.max,
1051                     min2 = bounds.min,
1052                     max2 = bounds.max,
1053                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1054                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1055
1056                 return xIntersects && yIntersects;
1057         },
1058
1059         // @method overlaps(otherBounds: Bounds): Boolean
1060         // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1061         // overlap if their intersection is an area.
1062         overlaps: function (bounds) { // (Bounds) -> Boolean
1063                 bounds = toBounds(bounds);
1064
1065                 var min = this.min,
1066                     max = this.max,
1067                     min2 = bounds.min,
1068                     max2 = bounds.max,
1069                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1070                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1071
1072                 return xOverlaps && yOverlaps;
1073         },
1074
1075         isValid: function () {
1076                 return !!(this.min && this.max);
1077         }
1078 };
1079
1080
1081 // @factory L.bounds(corner1: Point, corner2: Point)
1082 // Creates a Bounds object from two corners coordinate pairs.
1083 // @alternative
1084 // @factory L.bounds(points: Point[])
1085 // Creates a Bounds object from the given array of points.
1086 function toBounds(a, b) {
1087         if (!a || a instanceof Bounds) {
1088                 return a;
1089         }
1090         return new Bounds(a, b);
1091 }
1092
1093 /*
1094  * @class LatLngBounds
1095  * @aka L.LatLngBounds
1096  *
1097  * Represents a rectangular geographical area on a map.
1098  *
1099  * @example
1100  *
1101  * ```js
1102  * var corner1 = L.latLng(40.712, -74.227),
1103  * corner2 = L.latLng(40.774, -74.125),
1104  * bounds = L.latLngBounds(corner1, corner2);
1105  * ```
1106  *
1107  * 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:
1108  *
1109  * ```js
1110  * map.fitBounds([
1111  *      [40.712, -74.227],
1112  *      [40.774, -74.125]
1113  * ]);
1114  * ```
1115  *
1116  * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
1117  *
1118  * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
1119  * which means new classes can't inherit from it, and new methods
1120  * can't be added to it with the `include` function.
1121  */
1122
1123 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1124         if (!corner1) { return; }
1125
1126         var latlngs = corner2 ? [corner1, corner2] : corner1;
1127
1128         for (var i = 0, len = latlngs.length; i < len; i++) {
1129                 this.extend(latlngs[i]);
1130         }
1131 }
1132
1133 LatLngBounds.prototype = {
1134
1135         // @method extend(latlng: LatLng): this
1136         // Extend the bounds to contain the given point
1137
1138         // @alternative
1139         // @method extend(otherBounds: LatLngBounds): this
1140         // Extend the bounds to contain the given bounds
1141         extend: function (obj) {
1142                 var sw = this._southWest,
1143                     ne = this._northEast,
1144                     sw2, ne2;
1145
1146                 if (obj instanceof LatLng) {
1147                         sw2 = obj;
1148                         ne2 = obj;
1149
1150                 } else if (obj instanceof LatLngBounds) {
1151                         sw2 = obj._southWest;
1152                         ne2 = obj._northEast;
1153
1154                         if (!sw2 || !ne2) { return this; }
1155
1156                 } else {
1157                         return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1158                 }
1159
1160                 if (!sw && !ne) {
1161                         this._southWest = new LatLng(sw2.lat, sw2.lng);
1162                         this._northEast = new LatLng(ne2.lat, ne2.lng);
1163                 } else {
1164                         sw.lat = Math.min(sw2.lat, sw.lat);
1165                         sw.lng = Math.min(sw2.lng, sw.lng);
1166                         ne.lat = Math.max(ne2.lat, ne.lat);
1167                         ne.lng = Math.max(ne2.lng, ne.lng);
1168                 }
1169
1170                 return this;
1171         },
1172
1173         // @method pad(bufferRatio: Number): LatLngBounds
1174         // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1175         // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1176         // Negative values will retract the bounds.
1177         pad: function (bufferRatio) {
1178                 var sw = this._southWest,
1179                     ne = this._northEast,
1180                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1181                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1182
1183                 return new LatLngBounds(
1184                         new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1185                         new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1186         },
1187
1188         // @method getCenter(): LatLng
1189         // Returns the center point of the bounds.
1190         getCenter: function () {
1191                 return new LatLng(
1192                         (this._southWest.lat + this._northEast.lat) / 2,
1193                         (this._southWest.lng + this._northEast.lng) / 2);
1194         },
1195
1196         // @method getSouthWest(): LatLng
1197         // Returns the south-west point of the bounds.
1198         getSouthWest: function () {
1199                 return this._southWest;
1200         },
1201
1202         // @method getNorthEast(): LatLng
1203         // Returns the north-east point of the bounds.
1204         getNorthEast: function () {
1205                 return this._northEast;
1206         },
1207
1208         // @method getNorthWest(): LatLng
1209         // Returns the north-west point of the bounds.
1210         getNorthWest: function () {
1211                 return new LatLng(this.getNorth(), this.getWest());
1212         },
1213
1214         // @method getSouthEast(): LatLng
1215         // Returns the south-east point of the bounds.
1216         getSouthEast: function () {
1217                 return new LatLng(this.getSouth(), this.getEast());
1218         },
1219
1220         // @method getWest(): Number
1221         // Returns the west longitude of the bounds
1222         getWest: function () {
1223                 return this._southWest.lng;
1224         },
1225
1226         // @method getSouth(): Number
1227         // Returns the south latitude of the bounds
1228         getSouth: function () {
1229                 return this._southWest.lat;
1230         },
1231
1232         // @method getEast(): Number
1233         // Returns the east longitude of the bounds
1234         getEast: function () {
1235                 return this._northEast.lng;
1236         },
1237
1238         // @method getNorth(): Number
1239         // Returns the north latitude of the bounds
1240         getNorth: function () {
1241                 return this._northEast.lat;
1242         },
1243
1244         // @method contains(otherBounds: LatLngBounds): Boolean
1245         // Returns `true` if the rectangle contains the given one.
1246
1247         // @alternative
1248         // @method contains (latlng: LatLng): Boolean
1249         // Returns `true` if the rectangle contains the given point.
1250         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1251                 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1252                         obj = toLatLng(obj);
1253                 } else {
1254                         obj = toLatLngBounds(obj);
1255                 }
1256
1257                 var sw = this._southWest,
1258                     ne = this._northEast,
1259                     sw2, ne2;
1260
1261                 if (obj instanceof LatLngBounds) {
1262                         sw2 = obj.getSouthWest();
1263                         ne2 = obj.getNorthEast();
1264                 } else {
1265                         sw2 = ne2 = obj;
1266                 }
1267
1268                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1269                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1270         },
1271
1272         // @method intersects(otherBounds: LatLngBounds): Boolean
1273         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1274         intersects: function (bounds) {
1275                 bounds = toLatLngBounds(bounds);
1276
1277                 var sw = this._southWest,
1278                     ne = this._northEast,
1279                     sw2 = bounds.getSouthWest(),
1280                     ne2 = bounds.getNorthEast(),
1281
1282                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1283                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1284
1285                 return latIntersects && lngIntersects;
1286         },
1287
1288         // @method overlaps(otherBounds: Bounds): Boolean
1289         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1290         overlaps: function (bounds) {
1291                 bounds = toLatLngBounds(bounds);
1292
1293                 var sw = this._southWest,
1294                     ne = this._northEast,
1295                     sw2 = bounds.getSouthWest(),
1296                     ne2 = bounds.getNorthEast(),
1297
1298                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1299                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1300
1301                 return latOverlaps && lngOverlaps;
1302         },
1303
1304         // @method toBBoxString(): String
1305         // 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.
1306         toBBoxString: function () {
1307                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1308         },
1309
1310         // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1311         // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
1312         equals: function (bounds, maxMargin) {
1313                 if (!bounds) { return false; }
1314
1315                 bounds = toLatLngBounds(bounds);
1316
1317                 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1318                        this._northEast.equals(bounds.getNorthEast(), maxMargin);
1319         },
1320
1321         // @method isValid(): Boolean
1322         // Returns `true` if the bounds are properly initialized.
1323         isValid: function () {
1324                 return !!(this._southWest && this._northEast);
1325         }
1326 };
1327
1328 // TODO International date line?
1329
1330 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1331 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1332
1333 // @alternative
1334 // @factory L.latLngBounds(latlngs: LatLng[])
1335 // 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).
1336 function toLatLngBounds(a, b) {
1337         if (a instanceof LatLngBounds) {
1338                 return a;
1339         }
1340         return new LatLngBounds(a, b);
1341 }
1342
1343 /* @class LatLng
1344  * @aka L.LatLng
1345  *
1346  * Represents a geographical point with a certain latitude and longitude.
1347  *
1348  * @example
1349  *
1350  * ```
1351  * var latlng = L.latLng(50.5, 30.5);
1352  * ```
1353  *
1354  * 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:
1355  *
1356  * ```
1357  * map.panTo([50, 30]);
1358  * map.panTo({lon: 30, lat: 50});
1359  * map.panTo({lat: 50, lng: 30});
1360  * map.panTo(L.latLng(50, 30));
1361  * ```
1362  *
1363  * Note that `LatLng` does not inherit from Leafet's `Class` object,
1364  * which means new classes can't inherit from it, and new methods
1365  * can't be added to it with the `include` function.
1366  */
1367
1368 function LatLng(lat, lng, alt) {
1369         if (isNaN(lat) || isNaN(lng)) {
1370                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1371         }
1372
1373         // @property lat: Number
1374         // Latitude in degrees
1375         this.lat = +lat;
1376
1377         // @property lng: Number
1378         // Longitude in degrees
1379         this.lng = +lng;
1380
1381         // @property alt: Number
1382         // Altitude in meters (optional)
1383         if (alt !== undefined) {
1384                 this.alt = +alt;
1385         }
1386 }
1387
1388 LatLng.prototype = {
1389         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1390         // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
1391         equals: function (obj, maxMargin) {
1392                 if (!obj) { return false; }
1393
1394                 obj = toLatLng(obj);
1395
1396                 var margin = Math.max(
1397                         Math.abs(this.lat - obj.lat),
1398                         Math.abs(this.lng - obj.lng));
1399
1400                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1401         },
1402
1403         // @method toString(): String
1404         // Returns a string representation of the point (for debugging purposes).
1405         toString: function (precision) {
1406                 return 'LatLng(' +
1407                         formatNum(this.lat, precision) + ', ' +
1408                         formatNum(this.lng, precision) + ')';
1409         },
1410
1411         // @method distanceTo(otherLatLng: LatLng): Number
1412         // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
1413         distanceTo: function (other) {
1414                 return Earth.distance(this, toLatLng(other));
1415         },
1416
1417         // @method wrap(): LatLng
1418         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1419         wrap: function () {
1420                 return Earth.wrapLatLng(this);
1421         },
1422
1423         // @method toBounds(sizeInMeters: Number): LatLngBounds
1424         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1425         toBounds: function (sizeInMeters) {
1426                 var latAccuracy = 180 * sizeInMeters / 40075017,
1427                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1428
1429                 return toLatLngBounds(
1430                         [this.lat - latAccuracy, this.lng - lngAccuracy],
1431                         [this.lat + latAccuracy, this.lng + lngAccuracy]);
1432         },
1433
1434         clone: function () {
1435                 return new LatLng(this.lat, this.lng, this.alt);
1436         }
1437 };
1438
1439
1440
1441 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1442 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1443
1444 // @alternative
1445 // @factory L.latLng(coords: Array): LatLng
1446 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1447
1448 // @alternative
1449 // @factory L.latLng(coords: Object): LatLng
1450 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1451
1452 function toLatLng(a, b, c) {
1453         if (a instanceof LatLng) {
1454                 return a;
1455         }
1456         if (isArray(a) && typeof a[0] !== 'object') {
1457                 if (a.length === 3) {
1458                         return new LatLng(a[0], a[1], a[2]);
1459                 }
1460                 if (a.length === 2) {
1461                         return new LatLng(a[0], a[1]);
1462                 }
1463                 return null;
1464         }
1465         if (a === undefined || a === null) {
1466                 return a;
1467         }
1468         if (typeof a === 'object' && 'lat' in a) {
1469                 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1470         }
1471         if (b === undefined) {
1472                 return null;
1473         }
1474         return new LatLng(a, b, c);
1475 }
1476
1477 /*
1478  * @namespace CRS
1479  * @crs L.CRS.Base
1480  * Object that defines coordinate reference systems for projecting
1481  * geographical points into pixel (screen) coordinates and back (and to
1482  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1483  * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1484  *
1485  * Leaflet defines the most usual CRSs by default. If you want to use a
1486  * CRS not defined by default, take a look at the
1487  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1488  *
1489  * Note that the CRS instances do not inherit from Leafet's `Class` object,
1490  * and can't be instantiated. Also, new classes can't inherit from them,
1491  * and methods can't be added to them with the `include` function.
1492  */
1493
1494 var CRS = {
1495         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1496         // Projects geographical coordinates into pixel coordinates for a given zoom.
1497         latLngToPoint: function (latlng, zoom) {
1498                 var projectedPoint = this.projection.project(latlng),
1499                     scale = this.scale(zoom);
1500
1501                 return this.transformation._transform(projectedPoint, scale);
1502         },
1503
1504         // @method pointToLatLng(point: Point, zoom: Number): LatLng
1505         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1506         // zoom into geographical coordinates.
1507         pointToLatLng: function (point, zoom) {
1508                 var scale = this.scale(zoom),
1509                     untransformedPoint = this.transformation.untransform(point, scale);
1510
1511                 return this.projection.unproject(untransformedPoint);
1512         },
1513
1514         // @method project(latlng: LatLng): Point
1515         // Projects geographical coordinates into coordinates in units accepted for
1516         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1517         project: function (latlng) {
1518                 return this.projection.project(latlng);
1519         },
1520
1521         // @method unproject(point: Point): LatLng
1522         // Given a projected coordinate returns the corresponding LatLng.
1523         // The inverse of `project`.
1524         unproject: function (point) {
1525                 return this.projection.unproject(point);
1526         },
1527
1528         // @method scale(zoom: Number): Number
1529         // Returns the scale used when transforming projected coordinates into
1530         // pixel coordinates for a particular zoom. For example, it returns
1531         // `256 * 2^zoom` for Mercator-based CRS.
1532         scale: function (zoom) {
1533                 return 256 * Math.pow(2, zoom);
1534         },
1535
1536         // @method zoom(scale: Number): Number
1537         // Inverse of `scale()`, returns the zoom level corresponding to a scale
1538         // factor of `scale`.
1539         zoom: function (scale) {
1540                 return Math.log(scale / 256) / Math.LN2;
1541         },
1542
1543         // @method getProjectedBounds(zoom: Number): Bounds
1544         // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1545         getProjectedBounds: function (zoom) {
1546                 if (this.infinite) { return null; }
1547
1548                 var b = this.projection.bounds,
1549                     s = this.scale(zoom),
1550                     min = this.transformation.transform(b.min, s),
1551                     max = this.transformation.transform(b.max, s);
1552
1553                 return new Bounds(min, max);
1554         },
1555
1556         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1557         // Returns the distance between two geographical coordinates.
1558
1559         // @property code: String
1560         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1561         //
1562         // @property wrapLng: Number[]
1563         // An array of two numbers defining whether the longitude (horizontal) coordinate
1564         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1565         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1566         //
1567         // @property wrapLat: Number[]
1568         // Like `wrapLng`, but for the latitude (vertical) axis.
1569
1570         // wrapLng: [min, max],
1571         // wrapLat: [min, max],
1572
1573         // @property infinite: Boolean
1574         // If true, the coordinate space will be unbounded (infinite in both axes)
1575         infinite: false,
1576
1577         // @method wrapLatLng(latlng: LatLng): LatLng
1578         // Returns a `LatLng` where lat and lng has been wrapped according to the
1579         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1580         wrapLatLng: function (latlng) {
1581                 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1582                     lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1583                     alt = latlng.alt;
1584
1585                 return new LatLng(lat, lng, alt);
1586         },
1587
1588         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1589         // Returns a `LatLngBounds` with the same size as the given one, ensuring
1590         // that its center is within the CRS's bounds.
1591         // Only accepts actual `L.LatLngBounds` instances, not arrays.
1592         wrapLatLngBounds: function (bounds) {
1593                 var center = bounds.getCenter(),
1594                     newCenter = this.wrapLatLng(center),
1595                     latShift = center.lat - newCenter.lat,
1596                     lngShift = center.lng - newCenter.lng;
1597
1598                 if (latShift === 0 && lngShift === 0) {
1599                         return bounds;
1600                 }
1601
1602                 var sw = bounds.getSouthWest(),
1603                     ne = bounds.getNorthEast(),
1604                     newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1605                     newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1606
1607                 return new LatLngBounds(newSw, newNe);
1608         }
1609 };
1610
1611 /*
1612  * @namespace CRS
1613  * @crs L.CRS.Earth
1614  *
1615  * Serves as the base for CRS that are global such that they cover the earth.
1616  * Can only be used as the base for other CRS and cannot be used directly,
1617  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1618  * meters.
1619  */
1620
1621 var Earth = extend({}, CRS, {
1622         wrapLng: [-180, 180],
1623
1624         // Mean Earth Radius, as recommended for use by
1625         // the International Union of Geodesy and Geophysics,
1626         // see http://rosettacode.org/wiki/Haversine_formula
1627         R: 6371000,
1628
1629         // distance between two geographical points using spherical law of cosines approximation
1630         distance: function (latlng1, latlng2) {
1631                 var rad = Math.PI / 180,
1632                     lat1 = latlng1.lat * rad,
1633                     lat2 = latlng2.lat * rad,
1634                     sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1635                     sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1636                     a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1637                     c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1638                 return this.R * c;
1639         }
1640 });
1641
1642 /*
1643  * @namespace Projection
1644  * @projection L.Projection.SphericalMercator
1645  *
1646  * Spherical Mercator projection — the most common projection for online maps,
1647  * used by almost all free and commercial tile providers. Assumes that Earth is
1648  * a sphere. Used by the `EPSG:3857` CRS.
1649  */
1650
1651 var SphericalMercator = {
1652
1653         R: 6378137,
1654         MAX_LATITUDE: 85.0511287798,
1655
1656         project: function (latlng) {
1657                 var d = Math.PI / 180,
1658                     max = this.MAX_LATITUDE,
1659                     lat = Math.max(Math.min(max, latlng.lat), -max),
1660                     sin = Math.sin(lat * d);
1661
1662                 return new Point(
1663                         this.R * latlng.lng * d,
1664                         this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1665         },
1666
1667         unproject: function (point) {
1668                 var d = 180 / Math.PI;
1669
1670                 return new LatLng(
1671                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1672                         point.x * d / this.R);
1673         },
1674
1675         bounds: (function () {
1676                 var d = 6378137 * Math.PI;
1677                 return new Bounds([-d, -d], [d, d]);
1678         })()
1679 };
1680
1681 /*
1682  * @class Transformation
1683  * @aka L.Transformation
1684  *
1685  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1686  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1687  * the reverse. Used by Leaflet in its projections code.
1688  *
1689  * @example
1690  *
1691  * ```js
1692  * var transformation = L.transformation(2, 5, -1, 10),
1693  *      p = L.point(1, 2),
1694  *      p2 = transformation.transform(p), //  L.point(7, 8)
1695  *      p3 = transformation.untransform(p2); //  L.point(1, 2)
1696  * ```
1697  */
1698
1699
1700 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1701 // Creates a `Transformation` object with the given coefficients.
1702 function Transformation(a, b, c, d) {
1703         if (isArray(a)) {
1704                 // use array properties
1705                 this._a = a[0];
1706                 this._b = a[1];
1707                 this._c = a[2];
1708                 this._d = a[3];
1709                 return;
1710         }
1711         this._a = a;
1712         this._b = b;
1713         this._c = c;
1714         this._d = d;
1715 }
1716
1717 Transformation.prototype = {
1718         // @method transform(point: Point, scale?: Number): Point
1719         // Returns a transformed point, optionally multiplied by the given scale.
1720         // Only accepts actual `L.Point` instances, not arrays.
1721         transform: function (point, scale) { // (Point, Number) -> Point
1722                 return this._transform(point.clone(), scale);
1723         },
1724
1725         // destructive transform (faster)
1726         _transform: function (point, scale) {
1727                 scale = scale || 1;
1728                 point.x = scale * (this._a * point.x + this._b);
1729                 point.y = scale * (this._c * point.y + this._d);
1730                 return point;
1731         },
1732
1733         // @method untransform(point: Point, scale?: Number): Point
1734         // Returns the reverse transformation of the given point, optionally divided
1735         // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1736         untransform: function (point, scale) {
1737                 scale = scale || 1;
1738                 return new Point(
1739                         (point.x / scale - this._b) / this._a,
1740                         (point.y / scale - this._d) / this._c);
1741         }
1742 };
1743
1744 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1745
1746 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1747 // Instantiates a Transformation object with the given coefficients.
1748
1749 // @alternative
1750 // @factory L.transformation(coefficients: Array): Transformation
1751 // Expects an coefficients array of the form
1752 // `[a: Number, b: Number, c: Number, d: Number]`.
1753
1754 function toTransformation(a, b, c, d) {
1755         return new Transformation(a, b, c, d);
1756 }
1757
1758 /*
1759  * @namespace CRS
1760  * @crs L.CRS.EPSG3857
1761  *
1762  * The most common CRS for online maps, used by almost all free and commercial
1763  * tile providers. Uses Spherical Mercator projection. Set in by default in
1764  * Map's `crs` option.
1765  */
1766
1767 var EPSG3857 = extend({}, Earth, {
1768         code: 'EPSG:3857',
1769         projection: SphericalMercator,
1770
1771         transformation: (function () {
1772                 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1773                 return toTransformation(scale, 0.5, -scale, 0.5);
1774         }())
1775 });
1776
1777 var EPSG900913 = extend({}, EPSG3857, {
1778         code: 'EPSG:900913'
1779 });
1780
1781 // @namespace SVG; @section
1782 // There are several static functions which can be called without instantiating L.SVG:
1783
1784 // @function create(name: String): SVGElement
1785 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1786 // corresponding to the class name passed. For example, using 'line' will return
1787 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1788 function svgCreate(name) {
1789         return document.createElementNS('http://www.w3.org/2000/svg', name);
1790 }
1791
1792 // @function pointsToPath(rings: Point[], closed: Boolean): String
1793 // Generates a SVG path string for multiple rings, with each ring turning
1794 // into "M..L..L.." instructions
1795 function pointsToPath(rings, closed) {
1796         var str = '',
1797         i, j, len, len2, points, p;
1798
1799         for (i = 0, len = rings.length; i < len; i++) {
1800                 points = rings[i];
1801
1802                 for (j = 0, len2 = points.length; j < len2; j++) {
1803                         p = points[j];
1804                         str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1805                 }
1806
1807                 // closes the ring for polygons; "x" is VML syntax
1808                 str += closed ? (svg ? 'z' : 'x') : '';
1809         }
1810
1811         // SVG complains about empty path strings
1812         return str || 'M0 0';
1813 }
1814
1815 /*
1816  * @namespace Browser
1817  * @aka L.Browser
1818  *
1819  * A namespace with static properties for browser/feature detection used by Leaflet internally.
1820  *
1821  * @example
1822  *
1823  * ```js
1824  * if (L.Browser.ielt9) {
1825  *   alert('Upgrade your browser, dude!');
1826  * }
1827  * ```
1828  */
1829
1830 var style$1 = document.documentElement.style;
1831
1832 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1833 var ie = 'ActiveXObject' in window;
1834
1835 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1836 var ielt9 = ie && !document.addEventListener;
1837
1838 // @property edge: Boolean; `true` for the Edge web browser.
1839 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1840
1841 // @property webkit: Boolean;
1842 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1843 var webkit = userAgentContains('webkit');
1844
1845 // @property android: Boolean
1846 // `true` for any browser running on an Android platform.
1847 var android = userAgentContains('android');
1848
1849 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1850 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1851
1852 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1853 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1854 // @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1855 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1856
1857 // @property opera: Boolean; `true` for the Opera browser
1858 var opera = !!window.opera;
1859
1860 // @property chrome: Boolean; `true` for the Chrome browser.
1861 var chrome = userAgentContains('chrome');
1862
1863 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1864 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1865
1866 // @property safari: Boolean; `true` for the Safari browser.
1867 var safari = !chrome && userAgentContains('safari');
1868
1869 var phantom = userAgentContains('phantom');
1870
1871 // @property opera12: Boolean
1872 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1873 var opera12 = 'OTransition' in style$1;
1874
1875 // @property win: Boolean; `true` when the browser is running in a Windows platform
1876 var win = navigator.platform.indexOf('Win') === 0;
1877
1878 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1879 var ie3d = ie && ('transition' in style$1);
1880
1881 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1882 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1883
1884 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1885 var gecko3d = 'MozPerspective' in style$1;
1886
1887 // @property any3d: Boolean
1888 // `true` for all browsers supporting CSS transforms.
1889 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1890
1891 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1892 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1893
1894 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1895 var mobileWebkit = mobile && webkit;
1896
1897 // @property mobileWebkit3d: Boolean
1898 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1899 var mobileWebkit3d = mobile && webkit3d;
1900
1901 // @property msPointer: Boolean
1902 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1903 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1904
1905 // @property pointer: Boolean
1906 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1907 var pointer = !!(window.PointerEvent || msPointer);
1908
1909 // @property touch: Boolean
1910 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1911 // This does not necessarily mean that the browser is running in a computer with
1912 // a touchscreen, it only means that the browser is capable of understanding
1913 // touch events.
1914 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1915                 (window.DocumentTouch && document instanceof window.DocumentTouch));
1916
1917 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1918 var mobileOpera = mobile && opera;
1919
1920 // @property mobileGecko: Boolean
1921 // `true` for gecko-based browsers running in a mobile device.
1922 var mobileGecko = mobile && gecko;
1923
1924 // @property retina: Boolean
1925 // `true` for browsers on a high-resolution "retina" screen.
1926 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1927
1928
1929 // @property canvas: Boolean
1930 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1931 var canvas = (function () {
1932         return !!document.createElement('canvas').getContext;
1933 }());
1934
1935 // @property svg: Boolean
1936 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1937 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1938
1939 // @property vml: Boolean
1940 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1941 var vml = !svg && (function () {
1942         try {
1943                 var div = document.createElement('div');
1944                 div.innerHTML = '<v:shape adj="1"/>';
1945
1946                 var shape = div.firstChild;
1947                 shape.style.behavior = 'url(#default#VML)';
1948
1949                 return shape && (typeof shape.adj === 'object');
1950
1951         } catch (e) {
1952                 return false;
1953         }
1954 }());
1955
1956
1957 function userAgentContains(str) {
1958         return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1959 }
1960
1961
1962 var Browser = (Object.freeze || Object)({
1963         ie: ie,
1964         ielt9: ielt9,
1965         edge: edge,
1966         webkit: webkit,
1967         android: android,
1968         android23: android23,
1969         androidStock: androidStock,
1970         opera: opera,
1971         chrome: chrome,
1972         gecko: gecko,
1973         safari: safari,
1974         phantom: phantom,
1975         opera12: opera12,
1976         win: win,
1977         ie3d: ie3d,
1978         webkit3d: webkit3d,
1979         gecko3d: gecko3d,
1980         any3d: any3d,
1981         mobile: mobile,
1982         mobileWebkit: mobileWebkit,
1983         mobileWebkit3d: mobileWebkit3d,
1984         msPointer: msPointer,
1985         pointer: pointer,
1986         touch: touch,
1987         mobileOpera: mobileOpera,
1988         mobileGecko: mobileGecko,
1989         retina: retina,
1990         canvas: canvas,
1991         svg: svg,
1992         vml: vml
1993 });
1994
1995 /*
1996  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1997  */
1998
1999
2000 var POINTER_DOWN =   msPointer ? 'MSPointerDown'   : 'pointerdown';
2001 var POINTER_MOVE =   msPointer ? 'MSPointerMove'   : 'pointermove';
2002 var POINTER_UP =     msPointer ? 'MSPointerUp'     : 'pointerup';
2003 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2004 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2005
2006 var _pointers = {};
2007 var _pointerDocListener = false;
2008
2009 // DomEvent.DoubleTap needs to know about this
2010 var _pointersCount = 0;
2011
2012 // Provides a touch events wrapper for (ms)pointer events.
2013 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2014
2015 function addPointerListener(obj, type, handler, id) {
2016         if (type === 'touchstart') {
2017                 _addPointerStart(obj, handler, id);
2018
2019         } else if (type === 'touchmove') {
2020                 _addPointerMove(obj, handler, id);
2021
2022         } else if (type === 'touchend') {
2023                 _addPointerEnd(obj, handler, id);
2024         }
2025
2026         return this;
2027 }
2028
2029 function removePointerListener(obj, type, id) {
2030         var handler = obj['_leaflet_' + type + id];
2031
2032         if (type === 'touchstart') {
2033                 obj.removeEventListener(POINTER_DOWN, handler, false);
2034
2035         } else if (type === 'touchmove') {
2036                 obj.removeEventListener(POINTER_MOVE, handler, false);
2037
2038         } else if (type === 'touchend') {
2039                 obj.removeEventListener(POINTER_UP, handler, false);
2040                 obj.removeEventListener(POINTER_CANCEL, handler, false);
2041         }
2042
2043         return this;
2044 }
2045
2046 function _addPointerStart(obj, handler, id) {
2047         var onDown = bind(function (e) {
2048                 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2049                         // In IE11, some touch events needs to fire for form controls, or
2050                         // the controls will stop working. We keep a whitelist of tag names that
2051                         // need these events. For other target tags, we prevent default on the event.
2052                         if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2053                                 preventDefault(e);
2054                         } else {
2055                                 return;
2056                         }
2057                 }
2058
2059                 _handlePointer(e, handler);
2060         });
2061
2062         obj['_leaflet_touchstart' + id] = onDown;
2063         obj.addEventListener(POINTER_DOWN, onDown, false);
2064
2065         // need to keep track of what pointers and how many are active to provide e.touches emulation
2066         if (!_pointerDocListener) {
2067                 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2068                 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2069                 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2070                 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2071                 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2072
2073                 _pointerDocListener = true;
2074         }
2075 }
2076
2077 function _globalPointerDown(e) {
2078         _pointers[e.pointerId] = e;
2079         _pointersCount++;
2080 }
2081
2082 function _globalPointerMove(e) {
2083         if (_pointers[e.pointerId]) {
2084                 _pointers[e.pointerId] = e;
2085         }
2086 }
2087
2088 function _globalPointerUp(e) {
2089         delete _pointers[e.pointerId];
2090         _pointersCount--;
2091 }
2092
2093 function _handlePointer(e, handler) {
2094         e.touches = [];
2095         for (var i in _pointers) {
2096                 e.touches.push(_pointers[i]);
2097         }
2098         e.changedTouches = [e];
2099
2100         handler(e);
2101 }
2102
2103 function _addPointerMove(obj, handler, id) {
2104         var onMove = function (e) {
2105                 // don't fire touch moves when mouse isn't down
2106                 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2107
2108                 _handlePointer(e, handler);
2109         };
2110
2111         obj['_leaflet_touchmove' + id] = onMove;
2112         obj.addEventListener(POINTER_MOVE, onMove, false);
2113 }
2114
2115 function _addPointerEnd(obj, handler, id) {
2116         var onUp = function (e) {
2117                 _handlePointer(e, handler);
2118         };
2119
2120         obj['_leaflet_touchend' + id] = onUp;
2121         obj.addEventListener(POINTER_UP, onUp, false);
2122         obj.addEventListener(POINTER_CANCEL, onUp, false);
2123 }
2124
2125 /*
2126  * Extends the event handling code with double tap support for mobile browsers.
2127  */
2128
2129 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2130 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2131 var _pre = '_leaflet_';
2132
2133 // inspired by Zepto touch code by Thomas Fuchs
2134 function addDoubleTapListener(obj, handler, id) {
2135         var last, touch$$1,
2136             doubleTap = false,
2137             delay = 250;
2138
2139         function onTouchStart(e) {
2140                 var count;
2141
2142                 if (pointer) {
2143                         if ((!edge) || e.pointerType === 'mouse') { return; }
2144                         count = _pointersCount;
2145                 } else {
2146                         count = e.touches.length;
2147                 }
2148
2149                 if (count > 1) { return; }
2150
2151                 var now = Date.now(),
2152                     delta = now - (last || now);
2153
2154                 touch$$1 = e.touches ? e.touches[0] : e;
2155                 doubleTap = (delta > 0 && delta <= delay);
2156                 last = now;
2157         }
2158
2159         function onTouchEnd(e) {
2160                 if (doubleTap && !touch$$1.cancelBubble) {
2161                         if (pointer) {
2162                                 if ((!edge) || e.pointerType === 'mouse') { return; }
2163                                 // work around .type being readonly with MSPointer* events
2164                                 var newTouch = {},
2165                                     prop, i;
2166
2167                                 for (i in touch$$1) {
2168                                         prop = touch$$1[i];
2169                                         newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2170                                 }
2171                                 touch$$1 = newTouch;
2172                         }
2173                         touch$$1.type = 'dblclick';
2174                         handler(touch$$1);
2175                         last = null;
2176                 }
2177         }
2178
2179         obj[_pre + _touchstart + id] = onTouchStart;
2180         obj[_pre + _touchend + id] = onTouchEnd;
2181         obj[_pre + 'dblclick' + id] = handler;
2182
2183         obj.addEventListener(_touchstart, onTouchStart, false);
2184         obj.addEventListener(_touchend, onTouchEnd, false);
2185
2186         // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2187         // the browser doesn't fire touchend/pointerup events but does fire
2188         // native dblclicks. See #4127.
2189         // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2190         obj.addEventListener('dblclick', handler, false);
2191
2192         return this;
2193 }
2194
2195 function removeDoubleTapListener(obj, id) {
2196         var touchstart = obj[_pre + _touchstart + id],
2197             touchend = obj[_pre + _touchend + id],
2198             dblclick = obj[_pre + 'dblclick' + id];
2199
2200         obj.removeEventListener(_touchstart, touchstart, false);
2201         obj.removeEventListener(_touchend, touchend, false);
2202         if (!edge) {
2203                 obj.removeEventListener('dblclick', dblclick, false);
2204         }
2205
2206         return this;
2207 }
2208
2209 /*
2210  * @namespace DomEvent
2211  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2212  */
2213
2214 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2215
2216 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2217 // Adds a listener function (`fn`) to a particular DOM event type of the
2218 // element `el`. You can optionally specify the context of the listener
2219 // (object the `this` keyword will point to). You can also pass several
2220 // space-separated types (e.g. `'click dblclick'`).
2221
2222 // @alternative
2223 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2224 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2225 function on(obj, types, fn, context) {
2226
2227         if (typeof types === 'object') {
2228                 for (var type in types) {
2229                         addOne(obj, type, types[type], fn);
2230                 }
2231         } else {
2232                 types = splitWords(types);
2233
2234                 for (var i = 0, len = types.length; i < len; i++) {
2235                         addOne(obj, types[i], fn, context);
2236                 }
2237         }
2238
2239         return this;
2240 }
2241
2242 var eventsKey = '_leaflet_events';
2243
2244 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2245 // Removes a previously added listener function.
2246 // Note that if you passed a custom context to on, you must pass the same
2247 // context to `off` in order to remove the listener.
2248
2249 // @alternative
2250 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2251 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2252 function off(obj, types, fn, context) {
2253
2254         if (typeof types === 'object') {
2255                 for (var type in types) {
2256                         removeOne(obj, type, types[type], fn);
2257                 }
2258         } else if (types) {
2259                 types = splitWords(types);
2260
2261                 for (var i = 0, len = types.length; i < len; i++) {
2262                         removeOne(obj, types[i], fn, context);
2263                 }
2264         } else {
2265                 for (var j in obj[eventsKey]) {
2266                         removeOne(obj, j, obj[eventsKey][j]);
2267                 }
2268                 delete obj[eventsKey];
2269         }
2270
2271         return this;
2272 }
2273
2274 function addOne(obj, type, fn, context) {
2275         var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2276
2277         if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2278
2279         var handler = function (e) {
2280                 return fn.call(context || obj, e || window.event);
2281         };
2282
2283         var originalHandler = handler;
2284
2285         if (pointer && type.indexOf('touch') === 0) {
2286                 // Needs DomEvent.Pointer.js
2287                 addPointerListener(obj, type, handler, id);
2288
2289         } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2290                    !(pointer && chrome)) {
2291                 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2292                 // See #5180
2293                 addDoubleTapListener(obj, handler, id);
2294
2295         } else if ('addEventListener' in obj) {
2296
2297                 if (type === 'mousewheel') {
2298                         obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2299
2300                 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2301                         handler = function (e) {
2302                                 e = e || window.event;
2303                                 if (isExternalTarget(obj, e)) {
2304                                         originalHandler(e);
2305                                 }
2306                         };
2307                         obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2308
2309                 } else {
2310                         if (type === 'click' && android) {
2311                                 handler = function (e) {
2312                                         filterClick(e, originalHandler);
2313                                 };
2314                         }
2315                         obj.addEventListener(type, handler, false);
2316                 }
2317
2318         } else if ('attachEvent' in obj) {
2319                 obj.attachEvent('on' + type, handler);
2320         }
2321
2322         obj[eventsKey] = obj[eventsKey] || {};
2323         obj[eventsKey][id] = handler;
2324 }
2325
2326 function removeOne(obj, type, fn, context) {
2327
2328         var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2329             handler = obj[eventsKey] && obj[eventsKey][id];
2330
2331         if (!handler) { return this; }
2332
2333         if (pointer && type.indexOf('touch') === 0) {
2334                 removePointerListener(obj, type, id);
2335
2336         } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2337                    !(pointer && chrome)) {
2338                 removeDoubleTapListener(obj, id);
2339
2340         } else if ('removeEventListener' in obj) {
2341
2342                 if (type === 'mousewheel') {
2343                         obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2344
2345                 } else {
2346                         obj.removeEventListener(
2347                                 type === 'mouseenter' ? 'mouseover' :
2348                                 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2349                 }
2350
2351         } else if ('detachEvent' in obj) {
2352                 obj.detachEvent('on' + type, handler);
2353         }
2354
2355         obj[eventsKey][id] = null;
2356 }
2357
2358 // @function stopPropagation(ev: DOMEvent): this
2359 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2360 // ```js
2361 // L.DomEvent.on(div, 'click', function (ev) {
2362 //      L.DomEvent.stopPropagation(ev);
2363 // });
2364 // ```
2365 function stopPropagation(e) {
2366
2367         if (e.stopPropagation) {
2368                 e.stopPropagation();
2369         } else if (e.originalEvent) {  // In case of Leaflet event.
2370                 e.originalEvent._stopped = true;
2371         } else {
2372                 e.cancelBubble = true;
2373         }
2374         skipped(e);
2375
2376         return this;
2377 }
2378
2379 // @function disableScrollPropagation(el: HTMLElement): this
2380 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2381 function disableScrollPropagation(el) {
2382         addOne(el, 'mousewheel', stopPropagation);
2383         return this;
2384 }
2385
2386 // @function disableClickPropagation(el: HTMLElement): this
2387 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2388 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2389 function disableClickPropagation(el) {
2390         on(el, 'mousedown touchstart dblclick', stopPropagation);
2391         addOne(el, 'click', fakeStop);
2392         return this;
2393 }
2394
2395 // @function preventDefault(ev: DOMEvent): this
2396 // Prevents the default action of the DOM Event `ev` from happening (such as
2397 // following a link in the href of the a element, or doing a POST request
2398 // with page reload when a `<form>` is submitted).
2399 // Use it inside listener functions.
2400 function preventDefault(e) {
2401         if (e.preventDefault) {
2402                 e.preventDefault();
2403         } else {
2404                 e.returnValue = false;
2405         }
2406         return this;
2407 }
2408
2409 // @function stop(ev: DOMEvent): this
2410 // Does `stopPropagation` and `preventDefault` at the same time.
2411 function stop(e) {
2412         preventDefault(e);
2413         stopPropagation(e);
2414         return this;
2415 }
2416
2417 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2418 // Gets normalized mouse position from a DOM event relative to the
2419 // `container` or to the whole page if not specified.
2420 function getMousePosition(e, container) {
2421         if (!container) {
2422                 return new Point(e.clientX, e.clientY);
2423         }
2424
2425         var rect = container.getBoundingClientRect();
2426
2427         var scaleX = rect.width / container.offsetWidth || 1;
2428         var scaleY = rect.height / container.offsetHeight || 1;
2429         return new Point(
2430                 e.clientX / scaleX - rect.left - container.clientLeft,
2431                 e.clientY / scaleY - rect.top - container.clientTop);
2432 }
2433
2434 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2435 // and Firefox scrolls device pixels, not CSS pixels
2436 var wheelPxFactor =
2437         (win && chrome) ? 2 * window.devicePixelRatio :
2438         gecko ? window.devicePixelRatio : 1;
2439
2440 // @function getWheelDelta(ev: DOMEvent): Number
2441 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2442 // pixels scrolled (negative if scrolling down).
2443 // Events from pointing devices without precise scrolling are mapped to
2444 // a best guess of 60 pixels.
2445 function getWheelDelta(e) {
2446         return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2447                (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2448                (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2449                (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2450                (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
2451                e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2452                (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2453                e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2454                0;
2455 }
2456
2457 var skipEvents = {};
2458
2459 function fakeStop(e) {
2460         // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2461         skipEvents[e.type] = true;
2462 }
2463
2464 function skipped(e) {
2465         var events = skipEvents[e.type];
2466         // reset when checking, as it's only used in map container and propagates outside of the map
2467         skipEvents[e.type] = false;
2468         return events;
2469 }
2470
2471 // check if element really left/entered the event target (for mouseenter/mouseleave)
2472 function isExternalTarget(el, e) {
2473
2474         var related = e.relatedTarget;
2475
2476         if (!related) { return true; }
2477
2478         try {
2479                 while (related && (related !== el)) {
2480                         related = related.parentNode;
2481                 }
2482         } catch (err) {
2483                 return false;
2484         }
2485         return (related !== el);
2486 }
2487
2488 var lastClick;
2489
2490 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2491 function filterClick(e, handler) {
2492         var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2493             elapsed = lastClick && (timeStamp - lastClick);
2494
2495         // are they closer together than 500ms yet more than 100ms?
2496         // Android typically triggers them ~300ms apart while multiple listeners
2497         // on the same event should be triggered far faster;
2498         // or check if click is simulated on the element, and if it is, reject any non-simulated events
2499
2500         if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2501                 stop(e);
2502                 return;
2503         }
2504         lastClick = timeStamp;
2505
2506         handler(e);
2507 }
2508
2509
2510
2511
2512 var DomEvent = (Object.freeze || Object)({
2513         on: on,
2514         off: off,
2515         stopPropagation: stopPropagation,
2516         disableScrollPropagation: disableScrollPropagation,
2517         disableClickPropagation: disableClickPropagation,
2518         preventDefault: preventDefault,
2519         stop: stop,
2520         getMousePosition: getMousePosition,
2521         getWheelDelta: getWheelDelta,
2522         fakeStop: fakeStop,
2523         skipped: skipped,
2524         isExternalTarget: isExternalTarget,
2525         addListener: on,
2526         removeListener: off
2527 });
2528
2529 /*
2530  * @namespace DomUtil
2531  *
2532  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2533  * tree, used by Leaflet internally.
2534  *
2535  * Most functions expecting or returning a `HTMLElement` also work for
2536  * SVG elements. The only difference is that classes refer to CSS classes
2537  * in HTML and SVG classes in SVG.
2538  */
2539
2540
2541 // @property TRANSFORM: String
2542 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2543 var TRANSFORM = testProp(
2544         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2545
2546 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2547 // the same for the transitionend event, in particular the Android 4.1 stock browser
2548
2549 // @property TRANSITION: String
2550 // Vendor-prefixed transition style name.
2551 var TRANSITION = testProp(
2552         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2553
2554 // @property TRANSITION_END: String
2555 // Vendor-prefixed transitionend event name.
2556 var TRANSITION_END =
2557         TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2558
2559
2560 // @function get(id: String|HTMLElement): HTMLElement
2561 // Returns an element given its DOM id, or returns the element itself
2562 // if it was passed directly.
2563 function get(id) {
2564         return typeof id === 'string' ? document.getElementById(id) : id;
2565 }
2566
2567 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2568 // Returns the value for a certain style attribute on an element,
2569 // including computed values or values set through CSS.
2570 function getStyle(el, style) {
2571         var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2572
2573         if ((!value || value === 'auto') && document.defaultView) {
2574                 var css = document.defaultView.getComputedStyle(el, null);
2575                 value = css ? css[style] : null;
2576         }
2577         return value === 'auto' ? null : value;
2578 }
2579
2580 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2581 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2582 function create$1(tagName, className, container) {
2583         var el = document.createElement(tagName);
2584         el.className = className || '';
2585
2586         if (container) {
2587                 container.appendChild(el);
2588         }
2589         return el;
2590 }
2591
2592 // @function remove(el: HTMLElement)
2593 // Removes `el` from its parent element
2594 function remove(el) {
2595         var parent = el.parentNode;
2596         if (parent) {
2597                 parent.removeChild(el);
2598         }
2599 }
2600
2601 // @function empty(el: HTMLElement)
2602 // Removes all of `el`'s children elements from `el`
2603 function empty(el) {
2604         while (el.firstChild) {
2605                 el.removeChild(el.firstChild);
2606         }
2607 }
2608
2609 // @function toFront(el: HTMLElement)
2610 // Makes `el` the last child of its parent, so it renders in front of the other children.
2611 function toFront(el) {
2612         var parent = el.parentNode;
2613         if (parent.lastChild !== el) {
2614                 parent.appendChild(el);
2615         }
2616 }
2617
2618 // @function toBack(el: HTMLElement)
2619 // Makes `el` the first child of its parent, so it renders behind the other children.
2620 function toBack(el) {
2621         var parent = el.parentNode;
2622         if (parent.firstChild !== el) {
2623                 parent.insertBefore(el, parent.firstChild);
2624         }
2625 }
2626
2627 // @function hasClass(el: HTMLElement, name: String): Boolean
2628 // Returns `true` if the element's class attribute contains `name`.
2629 function hasClass(el, name) {
2630         if (el.classList !== undefined) {
2631                 return el.classList.contains(name);
2632         }
2633         var className = getClass(el);
2634         return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2635 }
2636
2637 // @function addClass(el: HTMLElement, name: String)
2638 // Adds `name` to the element's class attribute.
2639 function addClass(el, name) {
2640         if (el.classList !== undefined) {
2641                 var classes = splitWords(name);
2642                 for (var i = 0, len = classes.length; i < len; i++) {
2643                         el.classList.add(classes[i]);
2644                 }
2645         } else if (!hasClass(el, name)) {
2646                 var className = getClass(el);
2647                 setClass(el, (className ? className + ' ' : '') + name);
2648         }
2649 }
2650
2651 // @function removeClass(el: HTMLElement, name: String)
2652 // Removes `name` from the element's class attribute.
2653 function removeClass(el, name) {
2654         if (el.classList !== undefined) {
2655                 el.classList.remove(name);
2656         } else {
2657                 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2658         }
2659 }
2660
2661 // @function setClass(el: HTMLElement, name: String)
2662 // Sets the element's class.
2663 function setClass(el, name) {
2664         if (el.className.baseVal === undefined) {
2665                 el.className = name;
2666         } else {
2667                 // in case of SVG element
2668                 el.className.baseVal = name;
2669         }
2670 }
2671
2672 // @function getClass(el: HTMLElement): String
2673 // Returns the element's class.
2674 function getClass(el) {
2675         return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2676 }
2677
2678 // @function setOpacity(el: HTMLElement, opacity: Number)
2679 // Set the opacity of an element (including old IE support).
2680 // `opacity` must be a number from `0` to `1`.
2681 function setOpacity(el, value) {
2682         if ('opacity' in el.style) {
2683                 el.style.opacity = value;
2684         } else if ('filter' in el.style) {
2685                 _setOpacityIE(el, value);
2686         }
2687 }
2688
2689 function _setOpacityIE(el, value) {
2690         var filter = false,
2691             filterName = 'DXImageTransform.Microsoft.Alpha';
2692
2693         // filters collection throws an error if we try to retrieve a filter that doesn't exist
2694         try {
2695                 filter = el.filters.item(filterName);
2696         } catch (e) {
2697                 // don't set opacity to 1 if we haven't already set an opacity,
2698                 // it isn't needed and breaks transparent pngs.
2699                 if (value === 1) { return; }
2700         }
2701
2702         value = Math.round(value * 100);
2703
2704         if (filter) {
2705                 filter.Enabled = (value !== 100);
2706                 filter.Opacity = value;
2707         } else {
2708                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2709         }
2710 }
2711
2712 // @function testProp(props: String[]): String|false
2713 // Goes through the array of style names and returns the first name
2714 // that is a valid style name for an element. If no such name is found,
2715 // it returns false. Useful for vendor-prefixed styles like `transform`.
2716 function testProp(props) {
2717         var style = document.documentElement.style;
2718
2719         for (var i = 0; i < props.length; i++) {
2720                 if (props[i] in style) {
2721                         return props[i];
2722                 }
2723         }
2724         return false;
2725 }
2726
2727 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2728 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2729 // and optionally scaled by `scale`. Does not have an effect if the
2730 // browser doesn't support 3D CSS transforms.
2731 function setTransform(el, offset, scale) {
2732         var pos = offset || new Point(0, 0);
2733
2734         el.style[TRANSFORM] =
2735                 (ie3d ?
2736                         'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2737                         'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2738                 (scale ? ' scale(' + scale + ')' : '');
2739 }
2740
2741 // @function setPosition(el: HTMLElement, position: Point)
2742 // Sets the position of `el` to coordinates specified by `position`,
2743 // using CSS translate or top/left positioning depending on the browser
2744 // (used by Leaflet internally to position its layers).
2745 function setPosition(el, point) {
2746
2747         /*eslint-disable */
2748         el._leaflet_pos = point;
2749         /* eslint-enable */
2750
2751         if (any3d) {
2752                 setTransform(el, point);
2753         } else {
2754                 el.style.left = point.x + 'px';
2755                 el.style.top = point.y + 'px';
2756         }
2757 }
2758
2759 // @function getPosition(el: HTMLElement): Point
2760 // Returns the coordinates of an element previously positioned with setPosition.
2761 function getPosition(el) {
2762         // this method is only used for elements previously positioned using setPosition,
2763         // so it's safe to cache the position for performance
2764
2765         return el._leaflet_pos || new Point(0, 0);
2766 }
2767
2768 // @function disableTextSelection()
2769 // Prevents the user from generating `selectstart` DOM events, usually generated
2770 // when the user drags the mouse through a page with text. Used internally
2771 // by Leaflet to override the behaviour of any click-and-drag interaction on
2772 // the map. Affects drag interactions on the whole document.
2773
2774 // @function enableTextSelection()
2775 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2776 var disableTextSelection;
2777 var enableTextSelection;
2778 var _userSelect;
2779 if ('onselectstart' in document) {
2780         disableTextSelection = function () {
2781                 on(window, 'selectstart', preventDefault);
2782         };
2783         enableTextSelection = function () {
2784                 off(window, 'selectstart', preventDefault);
2785         };
2786 } else {
2787         var userSelectProperty = testProp(
2788                 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2789
2790         disableTextSelection = function () {
2791                 if (userSelectProperty) {
2792                         var style = document.documentElement.style;
2793                         _userSelect = style[userSelectProperty];
2794                         style[userSelectProperty] = 'none';
2795                 }
2796         };
2797         enableTextSelection = function () {
2798                 if (userSelectProperty) {
2799                         document.documentElement.style[userSelectProperty] = _userSelect;
2800                         _userSelect = undefined;
2801                 }
2802         };
2803 }
2804
2805 // @function disableImageDrag()
2806 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2807 // for `dragstart` DOM events, usually generated when the user drags an image.
2808 function disableImageDrag() {
2809         on(window, 'dragstart', preventDefault);
2810 }
2811
2812 // @function enableImageDrag()
2813 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2814 function enableImageDrag() {
2815         off(window, 'dragstart', preventDefault);
2816 }
2817
2818 var _outlineElement;
2819 var _outlineStyle;
2820 // @function preventOutline(el: HTMLElement)
2821 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2822 // of the element `el` invisible. Used internally by Leaflet to prevent
2823 // focusable elements from displaying an outline when the user performs a
2824 // drag interaction on them.
2825 function preventOutline(element) {
2826         while (element.tabIndex === -1) {
2827                 element = element.parentNode;
2828         }
2829         if (!element.style) { return; }
2830         restoreOutline();
2831         _outlineElement = element;
2832         _outlineStyle = element.style.outline;
2833         element.style.outline = 'none';
2834         on(window, 'keydown', restoreOutline);
2835 }
2836
2837 // @function restoreOutline()
2838 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2839 function restoreOutline() {
2840         if (!_outlineElement) { return; }
2841         _outlineElement.style.outline = _outlineStyle;
2842         _outlineElement = undefined;
2843         _outlineStyle = undefined;
2844         off(window, 'keydown', restoreOutline);
2845 }
2846
2847
2848 var DomUtil = (Object.freeze || Object)({
2849         TRANSFORM: TRANSFORM,
2850         TRANSITION: TRANSITION,
2851         TRANSITION_END: TRANSITION_END,
2852         get: get,
2853         getStyle: getStyle,
2854         create: create$1,
2855         remove: remove,
2856         empty: empty,
2857         toFront: toFront,
2858         toBack: toBack,
2859         hasClass: hasClass,
2860         addClass: addClass,
2861         removeClass: removeClass,
2862         setClass: setClass,
2863         getClass: getClass,
2864         setOpacity: setOpacity,
2865         testProp: testProp,
2866         setTransform: setTransform,
2867         setPosition: setPosition,
2868         getPosition: getPosition,
2869         disableTextSelection: disableTextSelection,
2870         enableTextSelection: enableTextSelection,
2871         disableImageDrag: disableImageDrag,
2872         enableImageDrag: enableImageDrag,
2873         preventOutline: preventOutline,
2874         restoreOutline: restoreOutline
2875 });
2876
2877 /*
2878  * @class PosAnimation
2879  * @aka L.PosAnimation
2880  * @inherits Evented
2881  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2882  *
2883  * @example
2884  * ```js
2885  * var fx = new L.PosAnimation();
2886  * fx.run(el, [300, 500], 0.5);
2887  * ```
2888  *
2889  * @constructor L.PosAnimation()
2890  * Creates a `PosAnimation` object.
2891  *
2892  */
2893
2894 var PosAnimation = Evented.extend({
2895
2896         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2897         // Run an animation of a given element to a new position, optionally setting
2898         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2899         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2900         // `0.5` by default).
2901         run: function (el, newPos, duration, easeLinearity) {
2902                 this.stop();
2903
2904                 this._el = el;
2905                 this._inProgress = true;
2906                 this._duration = duration || 0.25;
2907                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2908
2909                 this._startPos = getPosition(el);
2910                 this._offset = newPos.subtract(this._startPos);
2911                 this._startTime = +new Date();
2912
2913                 // @event start: Event
2914                 // Fired when the animation starts
2915                 this.fire('start');
2916
2917                 this._animate();
2918         },
2919
2920         // @method stop()
2921         // Stops the animation (if currently running).
2922         stop: function () {
2923                 if (!this._inProgress) { return; }
2924
2925                 this._step(true);
2926                 this._complete();
2927         },
2928
2929         _animate: function () {
2930                 // animation loop
2931                 this._animId = requestAnimFrame(this._animate, this);
2932                 this._step();
2933         },
2934
2935         _step: function (round) {
2936                 var elapsed = (+new Date()) - this._startTime,
2937                     duration = this._duration * 1000;
2938
2939                 if (elapsed < duration) {
2940                         this._runFrame(this._easeOut(elapsed / duration), round);
2941                 } else {
2942                         this._runFrame(1);
2943                         this._complete();
2944                 }
2945         },
2946
2947         _runFrame: function (progress, round) {
2948                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2949                 if (round) {
2950                         pos._round();
2951                 }
2952                 setPosition(this._el, pos);
2953
2954                 // @event step: Event
2955                 // Fired continuously during the animation.
2956                 this.fire('step');
2957         },
2958
2959         _complete: function () {
2960                 cancelAnimFrame(this._animId);
2961
2962                 this._inProgress = false;
2963                 // @event end: Event
2964                 // Fired when the animation ends.
2965                 this.fire('end');
2966         },
2967
2968         _easeOut: function (t) {
2969                 return 1 - Math.pow(1 - t, this._easeOutPower);
2970         }
2971 });
2972
2973 /*
2974  * @class Map
2975  * @aka L.Map
2976  * @inherits Evented
2977  *
2978  * The central class of the API — it is used to create a map on a page and manipulate it.
2979  *
2980  * @example
2981  *
2982  * ```js
2983  * // initialize the map on the "map" div with a given center and zoom
2984  * var map = L.map('map', {
2985  *      center: [51.505, -0.09],
2986  *      zoom: 13
2987  * });
2988  * ```
2989  *
2990  */
2991
2992 var Map = Evented.extend({
2993
2994         options: {
2995                 // @section Map State Options
2996                 // @option crs: CRS = L.CRS.EPSG3857
2997                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2998                 // sure what it means.
2999                 crs: EPSG3857,
3000
3001                 // @option center: LatLng = undefined
3002                 // Initial geographic center of the map
3003                 center: undefined,
3004
3005                 // @option zoom: Number = undefined
3006                 // Initial map zoom level
3007                 zoom: undefined,
3008
3009                 // @option minZoom: Number = *
3010                 // Minimum zoom level of the map.
3011                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3012                 // the lowest of their `minZoom` options will be used instead.
3013                 minZoom: undefined,
3014
3015                 // @option maxZoom: Number = *
3016                 // Maximum zoom level of the map.
3017                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3018                 // the highest of their `maxZoom` options will be used instead.
3019                 maxZoom: undefined,
3020
3021                 // @option layers: Layer[] = []
3022                 // Array of layers that will be added to the map initially
3023                 layers: [],
3024
3025                 // @option maxBounds: LatLngBounds = null
3026                 // When this option is set, the map restricts the view to the given
3027                 // geographical bounds, bouncing the user back if the user tries to pan
3028                 // outside the view. To set the restriction dynamically, use
3029                 // [`setMaxBounds`](#map-setmaxbounds) method.
3030                 maxBounds: undefined,
3031
3032                 // @option renderer: Renderer = *
3033                 // The default method for drawing vector layers on the map. `L.SVG`
3034                 // or `L.Canvas` by default depending on browser support.
3035                 renderer: undefined,
3036
3037
3038                 // @section Animation Options
3039                 // @option zoomAnimation: Boolean = true
3040                 // Whether the map zoom animation is enabled. By default it's enabled
3041                 // in all browsers that support CSS3 Transitions except Android.
3042                 zoomAnimation: true,
3043
3044                 // @option zoomAnimationThreshold: Number = 4
3045                 // Won't animate zoom if the zoom difference exceeds this value.
3046                 zoomAnimationThreshold: 4,
3047
3048                 // @option fadeAnimation: Boolean = true
3049                 // Whether the tile fade animation is enabled. By default it's enabled
3050                 // in all browsers that support CSS3 Transitions except Android.
3051                 fadeAnimation: true,
3052
3053                 // @option markerZoomAnimation: Boolean = true
3054                 // Whether markers animate their zoom with the zoom animation, if disabled
3055                 // they will disappear for the length of the animation. By default it's
3056                 // enabled in all browsers that support CSS3 Transitions except Android.
3057                 markerZoomAnimation: true,
3058
3059                 // @option transform3DLimit: Number = 2^23
3060                 // Defines the maximum size of a CSS translation transform. The default
3061                 // value should not be changed unless a web browser positions layers in
3062                 // the wrong place after doing a large `panBy`.
3063                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3064
3065                 // @section Interaction Options
3066                 // @option zoomSnap: Number = 1
3067                 // Forces the map's zoom level to always be a multiple of this, particularly
3068                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3069                 // By default, the zoom level snaps to the nearest integer; lower values
3070                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3071                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3072                 zoomSnap: 1,
3073
3074                 // @option zoomDelta: Number = 1
3075                 // Controls how much the map's zoom level will change after a
3076                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3077                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3078                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3079                 zoomDelta: 1,
3080
3081                 // @option trackResize: Boolean = true
3082                 // Whether the map automatically handles browser window resize to update itself.
3083                 trackResize: true
3084         },
3085
3086         initialize: function (id, options) { // (HTMLElement or String, Object)
3087                 options = setOptions(this, options);
3088
3089                 this._initContainer(id);
3090                 this._initLayout();
3091
3092                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3093                 this._onResize = bind(this._onResize, this);
3094
3095                 this._initEvents();
3096
3097                 if (options.maxBounds) {
3098                         this.setMaxBounds(options.maxBounds);
3099                 }
3100
3101                 if (options.zoom !== undefined) {
3102                         this._zoom = this._limitZoom(options.zoom);
3103                 }
3104
3105                 if (options.center && options.zoom !== undefined) {
3106                         this.setView(toLatLng(options.center), options.zoom, {reset: true});
3107                 }
3108
3109                 this._handlers = [];
3110                 this._layers = {};
3111                 this._zoomBoundLayers = {};
3112                 this._sizeChanged = true;
3113
3114                 this.callInitHooks();
3115
3116                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3117                 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3118                                 this.options.zoomAnimation;
3119
3120                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3121                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3122                 if (this._zoomAnimated) {
3123                         this._createAnimProxy();
3124                         on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3125                 }
3126
3127                 this._addLayers(this.options.layers);
3128         },
3129
3130
3131         // @section Methods for modifying map state
3132
3133         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3134         // Sets the view of the map (geographical center and zoom) with the given
3135         // animation options.
3136         setView: function (center, zoom, options) {
3137
3138                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3139                 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3140                 options = options || {};
3141
3142                 this._stop();
3143
3144                 if (this._loaded && !options.reset && options !== true) {
3145
3146                         if (options.animate !== undefined) {
3147                                 options.zoom = extend({animate: options.animate}, options.zoom);
3148                                 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3149                         }
3150
3151                         // try animating pan or zoom
3152                         var moved = (this._zoom !== zoom) ?
3153                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3154                                 this._tryAnimatedPan(center, options.pan);
3155
3156                         if (moved) {
3157                                 // prevent resize handler call, the view will refresh after animation anyway
3158                                 clearTimeout(this._sizeTimer);
3159                                 return this;
3160                         }
3161                 }
3162
3163                 // animation didn't start, just reset the map view
3164                 this._resetView(center, zoom);
3165
3166                 return this;
3167         },
3168
3169         // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3170         // Sets the zoom of the map.
3171         setZoom: function (zoom, options) {
3172                 if (!this._loaded) {
3173                         this._zoom = zoom;
3174                         return this;
3175                 }
3176                 return this.setView(this.getCenter(), zoom, {zoom: options});
3177         },
3178
3179         // @method zoomIn(delta?: Number, options?: Zoom options): this
3180         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3181         zoomIn: function (delta, options) {
3182                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3183                 return this.setZoom(this._zoom + delta, options);
3184         },
3185
3186         // @method zoomOut(delta?: Number, options?: Zoom options): this
3187         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3188         zoomOut: function (delta, options) {
3189                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3190                 return this.setZoom(this._zoom - delta, options);
3191         },
3192
3193         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3194         // Zooms the map while keeping a specified geographical point on the map
3195         // stationary (e.g. used internally for scroll zoom and double-click zoom).
3196         // @alternative
3197         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3198         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3199         setZoomAround: function (latlng, zoom, options) {
3200                 var scale = this.getZoomScale(zoom),
3201                     viewHalf = this.getSize().divideBy(2),
3202                     containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3203
3204                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3205                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3206
3207                 return this.setView(newCenter, zoom, {zoom: options});
3208         },
3209
3210         _getBoundsCenterZoom: function (bounds, options) {
3211
3212                 options = options || {};
3213                 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3214
3215                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3216                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3217
3218                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3219
3220                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3221
3222                 if (zoom === Infinity) {
3223                         return {
3224                                 center: bounds.getCenter(),
3225                                 zoom: zoom
3226                         };
3227                 }
3228
3229                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3230
3231                     swPoint = this.project(bounds.getSouthWest(), zoom),
3232                     nePoint = this.project(bounds.getNorthEast(), zoom),
3233                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3234
3235                 return {
3236                         center: center,
3237                         zoom: zoom
3238                 };
3239         },
3240
3241         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3242         // Sets a map view that contains the given geographical bounds with the
3243         // maximum zoom level possible.
3244         fitBounds: function (bounds, options) {
3245
3246                 bounds = toLatLngBounds(bounds);
3247
3248                 if (!bounds.isValid()) {
3249                         throw new Error('Bounds are not valid.');
3250                 }
3251
3252                 var target = this._getBoundsCenterZoom(bounds, options);
3253                 return this.setView(target.center, target.zoom, options);
3254         },
3255
3256         // @method fitWorld(options?: fitBounds options): this
3257         // Sets a map view that mostly contains the whole world with the maximum
3258         // zoom level possible.
3259         fitWorld: function (options) {
3260                 return this.fitBounds([[-90, -180], [90, 180]], options);
3261         },
3262
3263         // @method panTo(latlng: LatLng, options?: Pan options): this
3264         // Pans the map to a given center.
3265         panTo: function (center, options) { // (LatLng)
3266                 return this.setView(center, this._zoom, {pan: options});
3267         },
3268
3269         // @method panBy(offset: Point, options?: Pan options): this
3270         // Pans the map by a given number of pixels (animated).
3271         panBy: function (offset, options) {
3272                 offset = toPoint(offset).round();
3273                 options = options || {};
3274
3275                 if (!offset.x && !offset.y) {
3276                         return this.fire('moveend');
3277                 }
3278                 // If we pan too far, Chrome gets issues with tiles
3279                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3280                 if (options.animate !== true && !this.getSize().contains(offset)) {
3281                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3282                         return this;
3283                 }
3284
3285                 if (!this._panAnim) {
3286                         this._panAnim = new PosAnimation();
3287
3288                         this._panAnim.on({
3289                                 'step': this._onPanTransitionStep,
3290                                 'end': this._onPanTransitionEnd
3291                         }, this);
3292                 }
3293
3294                 // don't fire movestart if animating inertia
3295                 if (!options.noMoveStart) {
3296                         this.fire('movestart');
3297                 }
3298
3299                 // animate pan unless animate: false specified
3300                 if (options.animate !== false) {
3301                         addClass(this._mapPane, 'leaflet-pan-anim');
3302
3303                         var newPos = this._getMapPanePos().subtract(offset).round();
3304                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3305                 } else {
3306                         this._rawPanBy(offset);
3307                         this.fire('move').fire('moveend');
3308                 }
3309
3310                 return this;
3311         },
3312
3313         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3314         // Sets the view of the map (geographical center and zoom) performing a smooth
3315         // pan-zoom animation.
3316         flyTo: function (targetCenter, targetZoom, options) {
3317
3318                 options = options || {};
3319                 if (options.animate === false || !any3d) {
3320                         return this.setView(targetCenter, targetZoom, options);
3321                 }
3322
3323                 this._stop();
3324
3325                 var from = this.project(this.getCenter()),
3326                     to = this.project(targetCenter),
3327                     size = this.getSize(),
3328                     startZoom = this._zoom;
3329
3330                 targetCenter = toLatLng(targetCenter);
3331                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3332
3333                 var w0 = Math.max(size.x, size.y),
3334                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3335                     u1 = (to.distanceTo(from)) || 1,
3336                     rho = 1.42,
3337                     rho2 = rho * rho;
3338
3339                 function r(i) {
3340                         var s1 = i ? -1 : 1,
3341                             s2 = i ? w1 : w0,
3342                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3343                             b1 = 2 * s2 * rho2 * u1,
3344                             b = t1 / b1,
3345                             sq = Math.sqrt(b * b + 1) - b;
3346
3347                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
3348                             // thus triggering an infinite loop in flyTo
3349                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
3350
3351                         return log;
3352                 }
3353
3354                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3355                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3356                 function tanh(n) { return sinh(n) / cosh(n); }
3357
3358                 var r0 = r(0);
3359
3360                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3361                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3362
3363                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3364
3365                 var start = Date.now(),
3366                     S = (r(1) - r0) / rho,
3367                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3368
3369                 function frame() {
3370                         var t = (Date.now() - start) / duration,
3371                             s = easeOut(t) * S;
3372
3373                         if (t <= 1) {
3374                                 this._flyToFrame = requestAnimFrame(frame, this);
3375
3376                                 this._move(
3377                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3378                                         this.getScaleZoom(w0 / w(s), startZoom),
3379                                         {flyTo: true});
3380
3381                         } else {
3382                                 this
3383                                         ._move(targetCenter, targetZoom)
3384                                         ._moveEnd(true);
3385                         }
3386                 }
3387
3388                 this._moveStart(true, options.noMoveStart);
3389
3390                 frame.call(this);
3391                 return this;
3392         },
3393
3394         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3395         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3396         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3397         flyToBounds: function (bounds, options) {
3398                 var target = this._getBoundsCenterZoom(bounds, options);
3399                 return this.flyTo(target.center, target.zoom, options);
3400         },
3401
3402         // @method setMaxBounds(bounds: Bounds): this
3403         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3404         setMaxBounds: function (bounds) {
3405                 bounds = toLatLngBounds(bounds);
3406
3407                 if (!bounds.isValid()) {
3408                         this.options.maxBounds = null;
3409                         return this.off('moveend', this._panInsideMaxBounds);
3410                 } else if (this.options.maxBounds) {
3411                         this.off('moveend', this._panInsideMaxBounds);
3412                 }
3413
3414                 this.options.maxBounds = bounds;
3415
3416                 if (this._loaded) {
3417                         this._panInsideMaxBounds();
3418                 }
3419
3420                 return this.on('moveend', this._panInsideMaxBounds);
3421         },
3422
3423         // @method setMinZoom(zoom: Number): this
3424         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3425         setMinZoom: function (zoom) {
3426                 var oldZoom = this.options.minZoom;
3427                 this.options.minZoom = zoom;
3428
3429                 if (this._loaded && oldZoom !== zoom) {
3430                         this.fire('zoomlevelschange');
3431
3432                         if (this.getZoom() < this.options.minZoom) {
3433                                 return this.setZoom(zoom);
3434                         }
3435                 }
3436
3437                 return this;
3438         },
3439
3440         // @method setMaxZoom(zoom: Number): this
3441         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3442         setMaxZoom: function (zoom) {
3443                 var oldZoom = this.options.maxZoom;
3444                 this.options.maxZoom = zoom;
3445
3446                 if (this._loaded && oldZoom !== zoom) {
3447                         this.fire('zoomlevelschange');
3448
3449                         if (this.getZoom() > this.options.maxZoom) {
3450                                 return this.setZoom(zoom);
3451                         }
3452                 }
3453
3454                 return this;
3455         },
3456
3457         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3458         // 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.
3459         panInsideBounds: function (bounds, options) {
3460                 this._enforcingBounds = true;
3461                 var center = this.getCenter(),
3462                     newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3463
3464                 if (!center.equals(newCenter)) {
3465                         this.panTo(newCenter, options);
3466                 }
3467
3468                 this._enforcingBounds = false;
3469                 return this;
3470         },
3471
3472         // @method invalidateSize(options: Zoom/pan options): this
3473         // Checks if the map container size changed and updates the map if so —
3474         // call it after you've changed the map size dynamically, also animating
3475         // pan by default. If `options.pan` is `false`, panning will not occur.
3476         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3477         // that it doesn't happen often even if the method is called many
3478         // times in a row.
3479
3480         // @alternative
3481         // @method invalidateSize(animate: Boolean): this
3482         // Checks if the map container size changed and updates the map if so —
3483         // call it after you've changed the map size dynamically, also animating
3484         // pan by default.
3485         invalidateSize: function (options) {
3486                 if (!this._loaded) { return this; }
3487
3488                 options = extend({
3489                         animate: false,
3490                         pan: true
3491                 }, options === true ? {animate: true} : options);
3492
3493                 var oldSize = this.getSize();
3494                 this._sizeChanged = true;
3495                 this._lastCenter = null;
3496
3497                 var newSize = this.getSize(),
3498                     oldCenter = oldSize.divideBy(2).round(),
3499                     newCenter = newSize.divideBy(2).round(),
3500                     offset = oldCenter.subtract(newCenter);
3501
3502                 if (!offset.x && !offset.y) { return this; }
3503
3504                 if (options.animate && options.pan) {
3505                         this.panBy(offset);
3506
3507                 } else {
3508                         if (options.pan) {
3509                                 this._rawPanBy(offset);
3510                         }
3511
3512                         this.fire('move');
3513
3514                         if (options.debounceMoveend) {
3515                                 clearTimeout(this._sizeTimer);
3516                                 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3517                         } else {
3518                                 this.fire('moveend');
3519                         }
3520                 }
3521
3522                 // @section Map state change events
3523                 // @event resize: ResizeEvent
3524                 // Fired when the map is resized.
3525                 return this.fire('resize', {
3526                         oldSize: oldSize,
3527                         newSize: newSize
3528                 });
3529         },
3530
3531         // @section Methods for modifying map state
3532         // @method stop(): this
3533         // Stops the currently running `panTo` or `flyTo` animation, if any.
3534         stop: function () {
3535                 this.setZoom(this._limitZoom(this._zoom));
3536                 if (!this.options.zoomSnap) {
3537                         this.fire('viewreset');
3538                 }
3539                 return this._stop();
3540         },
3541
3542         // @section Geolocation methods
3543         // @method locate(options?: Locate options): this
3544         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3545         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3546         // and optionally sets the map view to the user's location with respect to
3547         // detection accuracy (or to the world view if geolocation failed).
3548         // Note that, if your page doesn't use HTTPS, this method will fail in
3549         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3550         // See `Locate options` for more details.
3551         locate: function (options) {
3552
3553                 options = this._locateOptions = extend({
3554                         timeout: 10000,
3555                         watch: false
3556                         // setView: false
3557                         // maxZoom: <Number>
3558                         // maximumAge: 0
3559                         // enableHighAccuracy: false
3560                 }, options);
3561
3562                 if (!('geolocation' in navigator)) {
3563                         this._handleGeolocationError({
3564                                 code: 0,
3565                                 message: 'Geolocation not supported.'
3566                         });
3567                         return this;
3568                 }
3569
3570                 var onResponse = bind(this._handleGeolocationResponse, this),
3571                     onError = bind(this._handleGeolocationError, this);
3572
3573                 if (options.watch) {
3574                         this._locationWatchId =
3575                                 navigator.geolocation.watchPosition(onResponse, onError, options);
3576                 } else {
3577                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3578                 }
3579                 return this;
3580         },
3581
3582         // @method stopLocate(): this
3583         // Stops watching location previously initiated by `map.locate({watch: true})`
3584         // and aborts resetting the map view if map.locate was called with
3585         // `{setView: true}`.
3586         stopLocate: function () {
3587                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3588                         navigator.geolocation.clearWatch(this._locationWatchId);
3589                 }
3590                 if (this._locateOptions) {
3591                         this._locateOptions.setView = false;
3592                 }
3593                 return this;
3594         },
3595
3596         _handleGeolocationError: function (error) {
3597                 var c = error.code,
3598                     message = error.message ||
3599                             (c === 1 ? 'permission denied' :
3600                             (c === 2 ? 'position unavailable' : 'timeout'));
3601
3602                 if (this._locateOptions.setView && !this._loaded) {
3603                         this.fitWorld();
3604                 }
3605
3606                 // @section Location events
3607                 // @event locationerror: ErrorEvent
3608                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3609                 this.fire('locationerror', {
3610                         code: c,
3611                         message: 'Geolocation error: ' + message + '.'
3612                 });
3613         },
3614
3615         _handleGeolocationResponse: function (pos) {
3616                 var lat = pos.coords.latitude,
3617                     lng = pos.coords.longitude,
3618                     latlng = new LatLng(lat, lng),
3619                     bounds = latlng.toBounds(pos.coords.accuracy),
3620                     options = this._locateOptions;
3621
3622                 if (options.setView) {
3623                         var zoom = this.getBoundsZoom(bounds);
3624                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3625                 }
3626
3627                 var data = {
3628                         latlng: latlng,
3629                         bounds: bounds,
3630                         timestamp: pos.timestamp
3631                 };
3632
3633                 for (var i in pos.coords) {
3634                         if (typeof pos.coords[i] === 'number') {
3635                                 data[i] = pos.coords[i];
3636                         }
3637                 }
3638
3639                 // @event locationfound: LocationEvent
3640                 // Fired when geolocation (using the [`locate`](#map-locate) method)
3641                 // went successfully.
3642                 this.fire('locationfound', data);
3643         },
3644
3645         // TODO Appropriate docs section?
3646         // @section Other Methods
3647         // @method addHandler(name: String, HandlerClass: Function): this
3648         // Adds a new `Handler` to the map, given its name and constructor function.
3649         addHandler: function (name, HandlerClass) {
3650                 if (!HandlerClass) { return this; }
3651
3652                 var handler = this[name] = new HandlerClass(this);
3653
3654                 this._handlers.push(handler);
3655
3656                 if (this.options[name]) {
3657                         handler.enable();
3658                 }
3659
3660                 return this;
3661         },
3662
3663         // @method remove(): this
3664         // Destroys the map and clears all related event listeners.
3665         remove: function () {
3666
3667                 this._initEvents(true);
3668
3669                 if (this._containerId !== this._container._leaflet_id) {
3670                         throw new Error('Map container is being reused by another instance');
3671                 }
3672
3673                 try {
3674                         // throws error in IE6-8
3675                         delete this._container._leaflet_id;
3676                         delete this._containerId;
3677                 } catch (e) {
3678                         /*eslint-disable */
3679                         this._container._leaflet_id = undefined;
3680                         /* eslint-enable */
3681                         this._containerId = undefined;
3682                 }
3683
3684                 if (this._locationWatchId !== undefined) {
3685                         this.stopLocate();
3686                 }
3687
3688                 this._stop();
3689
3690                 remove(this._mapPane);
3691
3692                 if (this._clearControlPos) {
3693                         this._clearControlPos();
3694                 }
3695
3696                 this._clearHandlers();
3697
3698                 if (this._loaded) {
3699                         // @section Map state change events
3700                         // @event unload: Event
3701                         // Fired when the map is destroyed with [remove](#map-remove) method.
3702                         this.fire('unload');
3703                 }
3704
3705                 var i;
3706                 for (i in this._layers) {
3707                         this._layers[i].remove();
3708                 }
3709                 for (i in this._panes) {
3710                         remove(this._panes[i]);
3711                 }
3712
3713                 this._layers = [];
3714                 this._panes = [];
3715                 delete this._mapPane;
3716                 delete this._renderer;
3717
3718                 return this;
3719         },
3720
3721         // @section Other Methods
3722         // @method createPane(name: String, container?: HTMLElement): HTMLElement
3723         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3724         // then returns it. The pane is created as a child of `container`, or
3725         // as a child of the main map pane if not set.
3726         createPane: function (name, container) {
3727                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3728                     pane = create$1('div', className, container || this._mapPane);
3729
3730                 if (name) {
3731                         this._panes[name] = pane;
3732                 }
3733                 return pane;
3734         },
3735
3736         // @section Methods for Getting Map State
3737
3738         // @method getCenter(): LatLng
3739         // Returns the geographical center of the map view
3740         getCenter: function () {
3741                 this._checkIfLoaded();
3742
3743                 if (this._lastCenter && !this._moved()) {
3744                         return this._lastCenter;
3745                 }
3746                 return this.layerPointToLatLng(this._getCenterLayerPoint());
3747         },
3748
3749         // @method getZoom(): Number
3750         // Returns the current zoom level of the map view
3751         getZoom: function () {
3752                 return this._zoom;
3753         },
3754
3755         // @method getBounds(): LatLngBounds
3756         // Returns the geographical bounds visible in the current map view
3757         getBounds: function () {
3758                 var bounds = this.getPixelBounds(),
3759                     sw = this.unproject(bounds.getBottomLeft()),
3760                     ne = this.unproject(bounds.getTopRight());
3761
3762                 return new LatLngBounds(sw, ne);
3763         },
3764
3765         // @method getMinZoom(): Number
3766         // 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.
3767         getMinZoom: function () {
3768                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3769         },
3770
3771         // @method getMaxZoom(): Number
3772         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3773         getMaxZoom: function () {
3774                 return this.options.maxZoom === undefined ?
3775                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3776                         this.options.maxZoom;
3777         },
3778
3779         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3780         // Returns the maximum zoom level on which the given bounds fit to the map
3781         // view in its entirety. If `inside` (optional) is set to `true`, the method
3782         // instead returns the minimum zoom level on which the map view fits into
3783         // the given bounds in its entirety.
3784         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3785                 bounds = toLatLngBounds(bounds);
3786                 padding = toPoint(padding || [0, 0]);
3787
3788                 var zoom = this.getZoom() || 0,
3789                     min = this.getMinZoom(),
3790                     max = this.getMaxZoom(),
3791                     nw = bounds.getNorthWest(),
3792                     se = bounds.getSouthEast(),
3793                     size = this.getSize().subtract(padding),
3794                     boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3795                     snap = any3d ? this.options.zoomSnap : 1,
3796                     scalex = size.x / boundsSize.x,
3797                     scaley = size.y / boundsSize.y,
3798                     scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3799
3800                 zoom = this.getScaleZoom(scale, zoom);
3801
3802                 if (snap) {
3803                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3804                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3805                 }
3806
3807                 return Math.max(min, Math.min(max, zoom));
3808         },
3809
3810         // @method getSize(): Point
3811         // Returns the current size of the map container (in pixels).
3812         getSize: function () {
3813                 if (!this._size || this._sizeChanged) {
3814                         this._size = new Point(
3815                                 this._container.clientWidth || 0,
3816                                 this._container.clientHeight || 0);
3817
3818                         this._sizeChanged = false;
3819                 }
3820                 return this._size.clone();
3821         },
3822
3823         // @method getPixelBounds(): Bounds
3824         // Returns the bounds of the current map view in projected pixel
3825         // coordinates (sometimes useful in layer and overlay implementations).
3826         getPixelBounds: function (center, zoom) {
3827                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3828                 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3829         },
3830
3831         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3832         // the map pane? "left point of the map layer" can be confusing, specially
3833         // since there can be negative offsets.
3834         // @method getPixelOrigin(): Point
3835         // Returns the projected pixel coordinates of the top left point of
3836         // the map layer (useful in custom layer and overlay implementations).
3837         getPixelOrigin: function () {
3838                 this._checkIfLoaded();
3839                 return this._pixelOrigin;
3840         },
3841
3842         // @method getPixelWorldBounds(zoom?: Number): Bounds
3843         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3844         // If `zoom` is omitted, the map's current zoom level is used.
3845         getPixelWorldBounds: function (zoom) {
3846                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3847         },
3848
3849         // @section Other Methods
3850
3851         // @method getPane(pane: String|HTMLElement): HTMLElement
3852         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3853         getPane: function (pane) {
3854                 return typeof pane === 'string' ? this._panes[pane] : pane;
3855         },
3856
3857         // @method getPanes(): Object
3858         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3859         // the panes as values.
3860         getPanes: function () {
3861                 return this._panes;
3862         },
3863
3864         // @method getContainer: HTMLElement
3865         // Returns the HTML element that contains the map.
3866         getContainer: function () {
3867                 return this._container;
3868         },
3869
3870
3871         // @section Conversion Methods
3872
3873         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3874         // Returns the scale factor to be applied to a map transition from zoom level
3875         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3876         getZoomScale: function (toZoom, fromZoom) {
3877                 // TODO replace with universal implementation after refactoring projections
3878                 var crs = this.options.crs;
3879                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3880                 return crs.scale(toZoom) / crs.scale(fromZoom);
3881         },
3882
3883         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3884         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3885         // level and everything is scaled by a factor of `scale`. Inverse of
3886         // [`getZoomScale`](#map-getZoomScale).
3887         getScaleZoom: function (scale, fromZoom) {
3888                 var crs = this.options.crs;
3889                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3890                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3891                 return isNaN(zoom) ? Infinity : zoom;
3892         },
3893
3894         // @method project(latlng: LatLng, zoom: Number): Point
3895         // Projects a geographical coordinate `LatLng` according to the projection
3896         // of the map's CRS, then scales it according to `zoom` and the CRS's
3897         // `Transformation`. The result is pixel coordinate relative to
3898         // the CRS origin.
3899         project: function (latlng, zoom) {
3900                 zoom = zoom === undefined ? this._zoom : zoom;
3901                 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3902         },
3903
3904         // @method unproject(point: Point, zoom: Number): LatLng
3905         // Inverse of [`project`](#map-project).
3906         unproject: function (point, zoom) {
3907                 zoom = zoom === undefined ? this._zoom : zoom;
3908                 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3909         },
3910
3911         // @method layerPointToLatLng(point: Point): LatLng
3912         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3913         // returns the corresponding geographical coordinate (for the current zoom level).
3914         layerPointToLatLng: function (point) {
3915                 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3916                 return this.unproject(projectedPoint);
3917         },
3918
3919         // @method latLngToLayerPoint(latlng: LatLng): Point
3920         // Given a geographical coordinate, returns the corresponding pixel coordinate
3921         // relative to the [origin pixel](#map-getpixelorigin).
3922         latLngToLayerPoint: function (latlng) {
3923                 var projectedPoint = this.project(toLatLng(latlng))._round();
3924                 return projectedPoint._subtract(this.getPixelOrigin());
3925         },
3926
3927         // @method wrapLatLng(latlng: LatLng): LatLng
3928         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3929         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3930         // CRS's bounds.
3931         // By default this means longitude is wrapped around the dateline so its
3932         // value is between -180 and +180 degrees.
3933         wrapLatLng: function (latlng) {
3934                 return this.options.crs.wrapLatLng(toLatLng(latlng));
3935         },
3936
3937         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3938         // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3939         // its center is within the CRS's bounds.
3940         // By default this means the center longitude is wrapped around the dateline so its
3941         // value is between -180 and +180 degrees, and the majority of the bounds
3942         // overlaps the CRS's bounds.
3943         wrapLatLngBounds: function (latlng) {
3944                 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3945         },
3946
3947         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3948         // Returns the distance between two geographical coordinates according to
3949         // the map's CRS. By default this measures distance in meters.
3950         distance: function (latlng1, latlng2) {
3951                 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3952         },
3953
3954         // @method containerPointToLayerPoint(point: Point): Point
3955         // Given a pixel coordinate relative to the map container, returns the corresponding
3956         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3957         containerPointToLayerPoint: function (point) { // (Point)
3958                 return toPoint(point).subtract(this._getMapPanePos());
3959         },
3960
3961         // @method layerPointToContainerPoint(point: Point): Point
3962         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3963         // returns the corresponding pixel coordinate relative to the map container.
3964         layerPointToContainerPoint: function (point) { // (Point)
3965                 return toPoint(point).add(this._getMapPanePos());
3966         },
3967
3968         // @method containerPointToLatLng(point: Point): LatLng
3969         // Given a pixel coordinate relative to the map container, returns
3970         // the corresponding geographical coordinate (for the current zoom level).
3971         containerPointToLatLng: function (point) {
3972                 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
3973                 return this.layerPointToLatLng(layerPoint);
3974         },
3975
3976         // @method latLngToContainerPoint(latlng: LatLng): Point
3977         // Given a geographical coordinate, returns the corresponding pixel coordinate
3978         // relative to the map container.
3979         latLngToContainerPoint: function (latlng) {
3980                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
3981         },
3982
3983         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3984         // Given a MouseEvent object, returns the pixel coordinate relative to the
3985         // map container where the event took place.
3986         mouseEventToContainerPoint: function (e) {
3987                 return getMousePosition(e, this._container);
3988         },
3989
3990         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3991         // Given a MouseEvent object, returns the pixel coordinate relative to
3992         // the [origin pixel](#map-getpixelorigin) where the event took place.
3993         mouseEventToLayerPoint: function (e) {
3994                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3995         },
3996
3997         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3998         // Given a MouseEvent object, returns geographical coordinate where the
3999         // event took place.
4000         mouseEventToLatLng: function (e) { // (MouseEvent)
4001                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4002         },
4003
4004
4005         // map initialization methods
4006
4007         _initContainer: function (id) {
4008                 var container = this._container = get(id);
4009
4010                 if (!container) {
4011                         throw new Error('Map container not found.');
4012                 } else if (container._leaflet_id) {
4013                         throw new Error('Map container is already initialized.');
4014                 }
4015
4016                 on(container, 'scroll', this._onScroll, this);
4017                 this._containerId = stamp(container);
4018         },
4019
4020         _initLayout: function () {
4021                 var container = this._container;
4022
4023                 this._fadeAnimated = this.options.fadeAnimation && any3d;
4024
4025                 addClass(container, 'leaflet-container' +
4026                         (touch ? ' leaflet-touch' : '') +
4027                         (retina ? ' leaflet-retina' : '') +
4028                         (ielt9 ? ' leaflet-oldie' : '') +
4029                         (safari ? ' leaflet-safari' : '') +
4030                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4031
4032                 var position = getStyle(container, 'position');
4033
4034                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4035                         container.style.position = 'relative';
4036                 }
4037
4038                 this._initPanes();
4039
4040                 if (this._initControlPos) {
4041                         this._initControlPos();
4042                 }
4043         },
4044
4045         _initPanes: function () {
4046                 var panes = this._panes = {};
4047                 this._paneRenderers = {};
4048
4049                 // @section
4050                 //
4051                 // Panes are DOM elements used to control the ordering of layers on the map. You
4052                 // can access panes with [`map.getPane`](#map-getpane) or
4053                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4054                 // [`map.createPane`](#map-createpane) method.
4055                 //
4056                 // Every map has the following default panes that differ only in zIndex.
4057                 //
4058                 // @pane mapPane: HTMLElement = 'auto'
4059                 // Pane that contains all other map panes
4060
4061                 this._mapPane = this.createPane('mapPane', this._container);
4062                 setPosition(this._mapPane, new Point(0, 0));
4063
4064                 // @pane tilePane: HTMLElement = 200
4065                 // Pane for `GridLayer`s and `TileLayer`s
4066                 this.createPane('tilePane');
4067                 // @pane overlayPane: HTMLElement = 400
4068                 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4069                 this.createPane('shadowPane');
4070                 // @pane shadowPane: HTMLElement = 500
4071                 // Pane for overlay shadows (e.g. `Marker` shadows)
4072                 this.createPane('overlayPane');
4073                 // @pane markerPane: HTMLElement = 600
4074                 // Pane for `Icon`s of `Marker`s
4075                 this.createPane('markerPane');
4076                 // @pane tooltipPane: HTMLElement = 650
4077                 // Pane for `Tooltip`s.
4078                 this.createPane('tooltipPane');
4079                 // @pane popupPane: HTMLElement = 700
4080                 // Pane for `Popup`s.
4081                 this.createPane('popupPane');
4082
4083                 if (!this.options.markerZoomAnimation) {
4084                         addClass(panes.markerPane, 'leaflet-zoom-hide');
4085                         addClass(panes.shadowPane, 'leaflet-zoom-hide');
4086                 }
4087         },
4088
4089
4090         // private methods that modify map state
4091
4092         // @section Map state change events
4093         _resetView: function (center, zoom) {
4094                 setPosition(this._mapPane, new Point(0, 0));
4095
4096                 var loading = !this._loaded;
4097                 this._loaded = true;
4098                 zoom = this._limitZoom(zoom);
4099
4100                 this.fire('viewprereset');
4101
4102                 var zoomChanged = this._zoom !== zoom;
4103                 this
4104                         ._moveStart(zoomChanged, false)
4105                         ._move(center, zoom)
4106                         ._moveEnd(zoomChanged);
4107
4108                 // @event viewreset: Event
4109                 // Fired when the map needs to redraw its content (this usually happens
4110                 // on map zoom or load). Very useful for creating custom overlays.
4111                 this.fire('viewreset');
4112
4113                 // @event load: Event
4114                 // Fired when the map is initialized (when its center and zoom are set
4115                 // for the first time).
4116                 if (loading) {
4117                         this.fire('load');
4118                 }
4119         },
4120
4121         _moveStart: function (zoomChanged, noMoveStart) {
4122                 // @event zoomstart: Event
4123                 // Fired when the map zoom is about to change (e.g. before zoom animation).
4124                 // @event movestart: Event
4125                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4126                 if (zoomChanged) {
4127                         this.fire('zoomstart');
4128                 }
4129                 if (!noMoveStart) {
4130                         this.fire('movestart');
4131                 }
4132                 return this;
4133         },
4134
4135         _move: function (center, zoom, data) {
4136                 if (zoom === undefined) {
4137                         zoom = this._zoom;
4138                 }
4139                 var zoomChanged = this._zoom !== zoom;
4140
4141                 this._zoom = zoom;
4142                 this._lastCenter = center;
4143                 this._pixelOrigin = this._getNewPixelOrigin(center);
4144
4145                 // @event zoom: Event
4146                 // Fired repeatedly during any change in zoom level, including zoom
4147                 // and fly animations.
4148                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
4149                         this.fire('zoom', data);
4150                 }
4151
4152                 // @event move: Event
4153                 // Fired repeatedly during any movement of the map, including pan and
4154                 // fly animations.
4155                 return this.fire('move', data);
4156         },
4157
4158         _moveEnd: function (zoomChanged) {
4159                 // @event zoomend: Event
4160                 // Fired when the map has changed, after any animations.
4161                 if (zoomChanged) {
4162                         this.fire('zoomend');
4163                 }
4164
4165                 // @event moveend: Event
4166                 // Fired when the center of the map stops changing (e.g. user stopped
4167                 // dragging the map).
4168                 return this.fire('moveend');
4169         },
4170
4171         _stop: function () {
4172                 cancelAnimFrame(this._flyToFrame);
4173                 if (this._panAnim) {
4174                         this._panAnim.stop();
4175                 }
4176                 return this;
4177         },
4178
4179         _rawPanBy: function (offset) {
4180                 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4181         },
4182
4183         _getZoomSpan: function () {
4184                 return this.getMaxZoom() - this.getMinZoom();
4185         },
4186
4187         _panInsideMaxBounds: function () {
4188                 if (!this._enforcingBounds) {
4189                         this.panInsideBounds(this.options.maxBounds);
4190                 }
4191         },
4192
4193         _checkIfLoaded: function () {
4194                 if (!this._loaded) {
4195                         throw new Error('Set map center and zoom first.');
4196                 }
4197         },
4198
4199         // DOM event handling
4200
4201         // @section Interaction events
4202         _initEvents: function (remove$$1) {
4203                 this._targets = {};
4204                 this._targets[stamp(this._container)] = this;
4205
4206                 var onOff = remove$$1 ? off : on;
4207
4208                 // @event click: MouseEvent
4209                 // Fired when the user clicks (or taps) the map.
4210                 // @event dblclick: MouseEvent
4211                 // Fired when the user double-clicks (or double-taps) the map.
4212                 // @event mousedown: MouseEvent
4213                 // Fired when the user pushes the mouse button on the map.
4214                 // @event mouseup: MouseEvent
4215                 // Fired when the user releases the mouse button on the map.
4216                 // @event mouseover: MouseEvent
4217                 // Fired when the mouse enters the map.
4218                 // @event mouseout: MouseEvent
4219                 // Fired when the mouse leaves the map.
4220                 // @event mousemove: MouseEvent
4221                 // Fired while the mouse moves over the map.
4222                 // @event contextmenu: MouseEvent
4223                 // Fired when the user pushes the right mouse button on the map, prevents
4224                 // default browser context menu from showing if there are listeners on
4225                 // this event. Also fired on mobile when the user holds a single touch
4226                 // for a second (also called long press).
4227                 // @event keypress: KeyboardEvent
4228                 // Fired when the user presses a key from the keyboard while the map is focused.
4229                 onOff(this._container, 'click dblclick mousedown mouseup ' +
4230                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4231
4232                 if (this.options.trackResize) {
4233                         onOff(window, 'resize', this._onResize, this);
4234                 }
4235
4236                 if (any3d && this.options.transform3DLimit) {
4237                         (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4238                 }
4239         },
4240
4241         _onResize: function () {
4242                 cancelAnimFrame(this._resizeRequest);
4243                 this._resizeRequest = requestAnimFrame(
4244                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
4245         },
4246
4247         _onScroll: function () {
4248                 this._container.scrollTop  = 0;
4249                 this._container.scrollLeft = 0;
4250         },
4251
4252         _onMoveEnd: function () {
4253                 var pos = this._getMapPanePos();
4254                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4255                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4256                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4257                         this._resetView(this.getCenter(), this.getZoom());
4258                 }
4259         },
4260
4261         _findEventTargets: function (e, type) {
4262                 var targets = [],
4263                     target,
4264                     isHover = type === 'mouseout' || type === 'mouseover',
4265                     src = e.target || e.srcElement,
4266                     dragging = false;
4267
4268                 while (src) {
4269                         target = this._targets[stamp(src)];
4270                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4271                                 // Prevent firing click after you just dragged an object.
4272                                 dragging = true;
4273                                 break;
4274                         }
4275                         if (target && target.listens(type, true)) {
4276                                 if (isHover && !isExternalTarget(src, e)) { break; }
4277                                 targets.push(target);
4278                                 if (isHover) { break; }
4279                         }
4280                         if (src === this._container) { break; }
4281                         src = src.parentNode;
4282                 }
4283                 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4284                         targets = [this];
4285                 }
4286                 return targets;
4287         },
4288
4289         _handleDOMEvent: function (e) {
4290                 if (!this._loaded || skipped(e)) { return; }
4291
4292                 var type = e.type;
4293
4294                 if (type === 'mousedown' || type === 'keypress') {
4295                         // prevents outline when clicking on keyboard-focusable element
4296                         preventOutline(e.target || e.srcElement);
4297                 }
4298
4299                 this._fireDOMEvent(e, type);
4300         },
4301
4302         _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4303
4304         _fireDOMEvent: function (e, type, targets) {
4305
4306                 if (e.type === 'click') {
4307                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4308                         // @event preclick: MouseEvent
4309                         // Fired before mouse click on the map (sometimes useful when you
4310                         // want something to happen on click before any existing click
4311                         // handlers start running).
4312                         var synth = extend({}, e);
4313                         synth.type = 'preclick';
4314                         this._fireDOMEvent(synth, synth.type, targets);
4315                 }
4316
4317                 if (e._stopped) { return; }
4318
4319                 // Find the layer the event is propagating from and its parents.
4320                 targets = (targets || []).concat(this._findEventTargets(e, type));
4321
4322                 if (!targets.length) { return; }
4323
4324                 var target = targets[0];
4325                 if (type === 'contextmenu' && target.listens(type, true)) {
4326                         preventDefault(e);
4327                 }
4328
4329                 var data = {
4330                         originalEvent: e
4331                 };
4332
4333                 if (e.type !== 'keypress') {
4334                         var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4335                         data.containerPoint = isMarker ?
4336                                 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4337                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4338                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4339                 }
4340
4341                 for (var i = 0; i < targets.length; i++) {
4342                         targets[i].fire(type, data, true);
4343                         if (data.originalEvent._stopped ||
4344                                 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4345                 }
4346         },
4347
4348         _draggableMoved: function (obj) {
4349                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4350                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4351         },
4352
4353         _clearHandlers: function () {
4354                 for (var i = 0, len = this._handlers.length; i < len; i++) {
4355                         this._handlers[i].disable();
4356                 }
4357         },
4358
4359         // @section Other Methods
4360
4361         // @method whenReady(fn: Function, context?: Object): this
4362         // Runs the given function `fn` when the map gets initialized with
4363         // a view (center and zoom) and at least one layer, or immediately
4364         // if it's already initialized, optionally passing a function context.
4365         whenReady: function (callback, context) {
4366                 if (this._loaded) {
4367                         callback.call(context || this, {target: this});
4368                 } else {
4369                         this.on('load', callback, context);
4370                 }
4371                 return this;
4372         },
4373
4374
4375         // private methods for getting map state
4376
4377         _getMapPanePos: function () {
4378                 return getPosition(this._mapPane) || new Point(0, 0);
4379         },
4380
4381         _moved: function () {
4382                 var pos = this._getMapPanePos();
4383                 return pos && !pos.equals([0, 0]);
4384         },
4385
4386         _getTopLeftPoint: function (center, zoom) {
4387                 var pixelOrigin = center && zoom !== undefined ?
4388                         this._getNewPixelOrigin(center, zoom) :
4389                         this.getPixelOrigin();
4390                 return pixelOrigin.subtract(this._getMapPanePos());
4391         },
4392
4393         _getNewPixelOrigin: function (center, zoom) {
4394                 var viewHalf = this.getSize()._divideBy(2);
4395                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4396         },
4397
4398         _latLngToNewLayerPoint: function (latlng, zoom, center) {
4399                 var topLeft = this._getNewPixelOrigin(center, zoom);
4400                 return this.project(latlng, zoom)._subtract(topLeft);
4401         },
4402
4403         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4404                 var topLeft = this._getNewPixelOrigin(center, zoom);
4405                 return toBounds([
4406                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4407                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4408                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4409                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4410                 ]);
4411         },
4412
4413         // layer point of the current center
4414         _getCenterLayerPoint: function () {
4415                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4416         },
4417
4418         // offset of the specified place to the current center in pixels
4419         _getCenterOffset: function (latlng) {
4420                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4421         },
4422
4423         // adjust center for view to get inside bounds
4424         _limitCenter: function (center, zoom, bounds) {
4425
4426                 if (!bounds) { return center; }
4427
4428                 var centerPoint = this.project(center, zoom),
4429                     viewHalf = this.getSize().divideBy(2),
4430                     viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4431                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4432
4433                 // If offset is less than a pixel, ignore.
4434                 // This prevents unstable projections from getting into
4435                 // an infinite loop of tiny offsets.
4436                 if (offset.round().equals([0, 0])) {
4437                         return center;
4438                 }
4439
4440                 return this.unproject(centerPoint.add(offset), zoom);
4441         },
4442
4443         // adjust offset for view to get inside bounds
4444         _limitOffset: function (offset, bounds) {
4445                 if (!bounds) { return offset; }
4446
4447                 var viewBounds = this.getPixelBounds(),
4448                     newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4449
4450                 return offset.add(this._getBoundsOffset(newBounds, bounds));
4451         },
4452
4453         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4454         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4455                 var projectedMaxBounds = toBounds(
4456                         this.project(maxBounds.getNorthEast(), zoom),
4457                         this.project(maxBounds.getSouthWest(), zoom)
4458                     ),
4459                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4460                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4461
4462                     dx = this._rebound(minOffset.x, -maxOffset.x),
4463                     dy = this._rebound(minOffset.y, -maxOffset.y);
4464
4465                 return new Point(dx, dy);
4466         },
4467
4468         _rebound: function (left, right) {
4469                 return left + right > 0 ?
4470                         Math.round(left - right) / 2 :
4471                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4472         },
4473
4474         _limitZoom: function (zoom) {
4475                 var min = this.getMinZoom(),
4476                     max = this.getMaxZoom(),
4477                     snap = any3d ? this.options.zoomSnap : 1;
4478                 if (snap) {
4479                         zoom = Math.round(zoom / snap) * snap;
4480                 }
4481                 return Math.max(min, Math.min(max, zoom));
4482         },
4483
4484         _onPanTransitionStep: function () {
4485                 this.fire('move');
4486         },
4487
4488         _onPanTransitionEnd: function () {
4489                 removeClass(this._mapPane, 'leaflet-pan-anim');
4490                 this.fire('moveend');
4491         },
4492
4493         _tryAnimatedPan: function (center, options) {
4494                 // difference between the new and current centers in pixels
4495                 var offset = this._getCenterOffset(center)._trunc();
4496
4497                 // don't animate too far unless animate: true specified in options
4498                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4499
4500                 this.panBy(offset, options);
4501
4502                 return true;
4503         },
4504
4505         _createAnimProxy: function () {
4506
4507                 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4508                 this._panes.mapPane.appendChild(proxy);
4509
4510                 this.on('zoomanim', function (e) {
4511                         var prop = TRANSFORM,
4512                             transform = this._proxy.style[prop];
4513
4514                         setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4515
4516                         // workaround for case when transform is the same and so transitionend event is not fired
4517                         if (transform === this._proxy.style[prop] && this._animatingZoom) {
4518                                 this._onZoomTransitionEnd();
4519                         }
4520                 }, this);
4521
4522                 this.on('load moveend', function () {
4523                         var c = this.getCenter(),
4524                             z = this.getZoom();
4525                         setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4526                 }, this);
4527
4528                 this._on('unload', this._destroyAnimProxy, this);
4529         },
4530
4531         _destroyAnimProxy: function () {
4532                 remove(this._proxy);
4533                 delete this._proxy;
4534         },
4535
4536         _catchTransitionEnd: function (e) {
4537                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4538                         this._onZoomTransitionEnd();
4539                 }
4540         },
4541
4542         _nothingToAnimate: function () {
4543                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4544         },
4545
4546         _tryAnimatedZoom: function (center, zoom, options) {
4547
4548                 if (this._animatingZoom) { return true; }
4549
4550                 options = options || {};
4551
4552                 // don't animate if disabled, not supported or zoom difference is too large
4553                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4554                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4555
4556                 // offset is the pixel coords of the zoom origin relative to the current center
4557                 var scale = this.getZoomScale(zoom),
4558                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4559
4560                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4561                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4562
4563                 requestAnimFrame(function () {
4564                         this
4565                             ._moveStart(true, false)
4566                             ._animateZoom(center, zoom, true);
4567                 }, this);
4568
4569                 return true;
4570         },
4571
4572         _animateZoom: function (center, zoom, startAnim, noUpdate) {
4573                 if (!this._mapPane) { return; }
4574
4575                 if (startAnim) {
4576                         this._animatingZoom = true;
4577
4578                         // remember what center/zoom to set after animation
4579                         this._animateToCenter = center;
4580                         this._animateToZoom = zoom;
4581
4582                         addClass(this._mapPane, 'leaflet-zoom-anim');
4583                 }
4584
4585                 // @event zoomanim: ZoomAnimEvent
4586                 // Fired on every frame of a zoom animation
4587                 this.fire('zoomanim', {
4588                         center: center,
4589                         zoom: zoom,
4590                         noUpdate: noUpdate
4591                 });
4592
4593                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4594                 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4595         },
4596
4597         _onZoomTransitionEnd: function () {
4598                 if (!this._animatingZoom) { return; }
4599
4600                 if (this._mapPane) {
4601                         removeClass(this._mapPane, 'leaflet-zoom-anim');
4602                 }
4603
4604                 this._animatingZoom = false;
4605
4606                 this._move(this._animateToCenter, this._animateToZoom);
4607
4608                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4609                 requestAnimFrame(function () {
4610                         this._moveEnd(true);
4611                 }, this);
4612         }
4613 });
4614
4615 // @section
4616
4617 // @factory L.map(id: String, options?: Map options)
4618 // Instantiates a map object given the DOM ID of a `<div>` element
4619 // and optionally an object literal with `Map options`.
4620 //
4621 // @alternative
4622 // @factory L.map(el: HTMLElement, options?: Map options)
4623 // Instantiates a map object given an instance of a `<div>` HTML element
4624 // and optionally an object literal with `Map options`.
4625 function createMap(id, options) {
4626         return new Map(id, options);
4627 }
4628
4629 /*
4630  * @class Control
4631  * @aka L.Control
4632  * @inherits Class
4633  *
4634  * L.Control is a base class for implementing map controls. Handles positioning.
4635  * All other controls extend from this class.
4636  */
4637
4638 var Control = Class.extend({
4639         // @section
4640         // @aka Control options
4641         options: {
4642                 // @option position: String = 'topright'
4643                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4644                 // `'topright'`, `'bottomleft'` or `'bottomright'`
4645                 position: 'topright'
4646         },
4647
4648         initialize: function (options) {
4649                 setOptions(this, options);
4650         },
4651
4652         /* @section
4653          * Classes extending L.Control will inherit the following methods:
4654          *
4655          * @method getPosition: string
4656          * Returns the position of the control.
4657          */
4658         getPosition: function () {
4659                 return this.options.position;
4660         },
4661
4662         // @method setPosition(position: string): this
4663         // Sets the position of the control.
4664         setPosition: function (position) {
4665                 var map = this._map;
4666
4667                 if (map) {
4668                         map.removeControl(this);
4669                 }
4670
4671                 this.options.position = position;
4672
4673                 if (map) {
4674                         map.addControl(this);
4675                 }
4676
4677                 return this;
4678         },
4679
4680         // @method getContainer: HTMLElement
4681         // Returns the HTMLElement that contains the control.
4682         getContainer: function () {
4683                 return this._container;
4684         },
4685
4686         // @method addTo(map: Map): this
4687         // Adds the control to the given map.
4688         addTo: function (map) {
4689                 this.remove();
4690                 this._map = map;
4691
4692                 var container = this._container = this.onAdd(map),
4693                     pos = this.getPosition(),
4694                     corner = map._controlCorners[pos];
4695
4696                 addClass(container, 'leaflet-control');
4697
4698                 if (pos.indexOf('bottom') !== -1) {
4699                         corner.insertBefore(container, corner.firstChild);
4700                 } else {
4701                         corner.appendChild(container);
4702                 }
4703
4704                 return this;
4705         },
4706
4707         // @method remove: this
4708         // Removes the control from the map it is currently active on.
4709         remove: function () {
4710                 if (!this._map) {
4711                         return this;
4712                 }
4713
4714                 remove(this._container);
4715
4716                 if (this.onRemove) {
4717                         this.onRemove(this._map);
4718                 }
4719
4720                 this._map = null;
4721
4722                 return this;
4723         },
4724
4725         _refocusOnMap: function (e) {
4726                 // if map exists and event is not a keyboard event
4727                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4728                         this._map.getContainer().focus();
4729                 }
4730         }
4731 });
4732
4733 var control = function (options) {
4734         return new Control(options);
4735 };
4736
4737 /* @section Extension methods
4738  * @uninheritable
4739  *
4740  * Every control should extend from `L.Control` and (re-)implement the following methods.
4741  *
4742  * @method onAdd(map: Map): HTMLElement
4743  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4744  *
4745  * @method onRemove(map: Map)
4746  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4747  */
4748
4749 /* @namespace Map
4750  * @section Methods for Layers and Controls
4751  */
4752 Map.include({
4753         // @method addControl(control: Control): this
4754         // Adds the given control to the map
4755         addControl: function (control) {
4756                 control.addTo(this);
4757                 return this;
4758         },
4759
4760         // @method removeControl(control: Control): this
4761         // Removes the given control from the map
4762         removeControl: function (control) {
4763                 control.remove();
4764                 return this;
4765         },
4766
4767         _initControlPos: function () {
4768                 var corners = this._controlCorners = {},
4769                     l = 'leaflet-',
4770                     container = this._controlContainer =
4771                             create$1('div', l + 'control-container', this._container);
4772
4773                 function createCorner(vSide, hSide) {
4774                         var className = l + vSide + ' ' + l + hSide;
4775
4776                         corners[vSide + hSide] = create$1('div', className, container);
4777                 }
4778
4779                 createCorner('top', 'left');
4780                 createCorner('top', 'right');
4781                 createCorner('bottom', 'left');
4782                 createCorner('bottom', 'right');
4783         },
4784
4785         _clearControlPos: function () {
4786                 for (var i in this._controlCorners) {
4787                         remove(this._controlCorners[i]);
4788                 }
4789                 remove(this._controlContainer);
4790                 delete this._controlCorners;
4791                 delete this._controlContainer;
4792         }
4793 });
4794
4795 /*
4796  * @class Control.Layers
4797  * @aka L.Control.Layers
4798  * @inherits Control
4799  *
4800  * 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/)). Extends `Control`.
4801  *
4802  * @example
4803  *
4804  * ```js
4805  * var baseLayers = {
4806  *      "Mapbox": mapbox,
4807  *      "OpenStreetMap": osm
4808  * };
4809  *
4810  * var overlays = {
4811  *      "Marker": marker,
4812  *      "Roads": roadsLayer
4813  * };
4814  *
4815  * L.control.layers(baseLayers, overlays).addTo(map);
4816  * ```
4817  *
4818  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4819  *
4820  * ```js
4821  * {
4822  *     "<someName1>": layer1,
4823  *     "<someName2>": layer2
4824  * }
4825  * ```
4826  *
4827  * The layer names can contain HTML, which allows you to add additional styling to the items:
4828  *
4829  * ```js
4830  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4831  * ```
4832  */
4833
4834 var Layers = Control.extend({
4835         // @section
4836         // @aka Control.Layers options
4837         options: {
4838                 // @option collapsed: Boolean = true
4839                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4840                 collapsed: true,
4841                 position: 'topright',
4842
4843                 // @option autoZIndex: Boolean = true
4844                 // 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.
4845                 autoZIndex: true,
4846
4847                 // @option hideSingleBase: Boolean = false
4848                 // If `true`, the base layers in the control will be hidden when there is only one.
4849                 hideSingleBase: false,
4850
4851                 // @option sortLayers: Boolean = false
4852                 // Whether to sort the layers. When `false`, layers will keep the order
4853                 // in which they were added to the control.
4854                 sortLayers: false,
4855
4856                 // @option sortFunction: Function = *
4857                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4858                 // that will be used for sorting the layers, when `sortLayers` is `true`.
4859                 // The function receives both the `L.Layer` instances and their names, as in
4860                 // `sortFunction(layerA, layerB, nameA, nameB)`.
4861                 // By default, it sorts layers alphabetically by their name.
4862                 sortFunction: function (layerA, layerB, nameA, nameB) {
4863                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4864                 }
4865         },
4866
4867         initialize: function (baseLayers, overlays, options) {
4868                 setOptions(this, options);
4869
4870                 this._layerControlInputs = [];
4871                 this._layers = [];
4872                 this._lastZIndex = 0;
4873                 this._handlingClick = false;
4874
4875                 for (var i in baseLayers) {
4876                         this._addLayer(baseLayers[i], i);
4877                 }
4878
4879                 for (i in overlays) {
4880                         this._addLayer(overlays[i], i, true);
4881                 }
4882         },
4883
4884         onAdd: function (map) {
4885                 this._initLayout();
4886                 this._update();
4887
4888                 this._map = map;
4889                 map.on('zoomend', this._checkDisabledLayers, this);
4890
4891                 for (var i = 0; i < this._layers.length; i++) {
4892                         this._layers[i].layer.on('add remove', this._onLayerChange, this);
4893                 }
4894
4895                 return this._container;
4896         },
4897
4898         addTo: function (map) {
4899                 Control.prototype.addTo.call(this, map);
4900                 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4901                 return this._expandIfNotCollapsed();
4902         },
4903
4904         onRemove: function () {
4905                 this._map.off('zoomend', this._checkDisabledLayers, this);
4906
4907                 for (var i = 0; i < this._layers.length; i++) {
4908                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
4909                 }
4910         },
4911
4912         // @method addBaseLayer(layer: Layer, name: String): this
4913         // Adds a base layer (radio button entry) with the given name to the control.
4914         addBaseLayer: function (layer, name) {
4915                 this._addLayer(layer, name);
4916                 return (this._map) ? this._update() : this;
4917         },
4918
4919         // @method addOverlay(layer: Layer, name: String): this
4920         // Adds an overlay (checkbox entry) with the given name to the control.
4921         addOverlay: function (layer, name) {
4922                 this._addLayer(layer, name, true);
4923                 return (this._map) ? this._update() : this;
4924         },
4925
4926         // @method removeLayer(layer: Layer): this
4927         // Remove the given layer from the control.
4928         removeLayer: function (layer) {
4929                 layer.off('add remove', this._onLayerChange, this);
4930
4931                 var obj = this._getLayer(stamp(layer));
4932                 if (obj) {
4933                         this._layers.splice(this._layers.indexOf(obj), 1);
4934                 }
4935                 return (this._map) ? this._update() : this;
4936         },
4937
4938         // @method expand(): this
4939         // Expand the control container if collapsed.
4940         expand: function () {
4941                 addClass(this._container, 'leaflet-control-layers-expanded');
4942                 this._form.style.height = null;
4943                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
4944                 if (acceptableHeight < this._form.clientHeight) {
4945                         addClass(this._form, 'leaflet-control-layers-scrollbar');
4946                         this._form.style.height = acceptableHeight + 'px';
4947                 } else {
4948                         removeClass(this._form, 'leaflet-control-layers-scrollbar');
4949                 }
4950                 this._checkDisabledLayers();
4951                 return this;
4952         },
4953
4954         // @method collapse(): this
4955         // Collapse the control container if expanded.
4956         collapse: function () {
4957                 removeClass(this._container, 'leaflet-control-layers-expanded');
4958                 return this;
4959         },
4960
4961         _initLayout: function () {
4962                 var className = 'leaflet-control-layers',
4963                     container = this._container = create$1('div', className),
4964                     collapsed = this.options.collapsed;
4965
4966                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
4967                 container.setAttribute('aria-haspopup', true);
4968
4969                 disableClickPropagation(container);
4970                 disableScrollPropagation(container);
4971
4972                 var form = this._form = create$1('form', className + '-list');
4973
4974                 if (collapsed) {
4975                         this._map.on('click', this.collapse, this);
4976
4977                         if (!android) {
4978                                 on(container, {
4979                                         mouseenter: this.expand,
4980                                         mouseleave: this.collapse
4981                                 }, this);
4982                         }
4983                 }
4984
4985                 var link = this._layersLink = create$1('a', className + '-toggle', container);
4986                 link.href = '#';
4987                 link.title = 'Layers';
4988
4989                 if (touch) {
4990                         on(link, 'click', stop);
4991                         on(link, 'click', this.expand, this);
4992                 } else {
4993                         on(link, 'focus', this.expand, this);
4994                 }
4995
4996                 if (!collapsed) {
4997                         this.expand();
4998                 }
4999
5000                 this._baseLayersList = create$1('div', className + '-base', form);
5001                 this._separator = create$1('div', className + '-separator', form);
5002                 this._overlaysList = create$1('div', className + '-overlays', form);
5003
5004                 container.appendChild(form);
5005         },
5006
5007         _getLayer: function (id) {
5008                 for (var i = 0; i < this._layers.length; i++) {
5009
5010                         if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5011                                 return this._layers[i];
5012                         }
5013                 }
5014         },
5015
5016         _addLayer: function (layer, name, overlay) {
5017                 if (this._map) {
5018                         layer.on('add remove', this._onLayerChange, this);
5019                 }
5020
5021                 this._layers.push({
5022                         layer: layer,
5023                         name: name,
5024                         overlay: overlay
5025                 });
5026
5027                 if (this.options.sortLayers) {
5028                         this._layers.sort(bind(function (a, b) {
5029                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5030                         }, this));
5031                 }
5032
5033                 if (this.options.autoZIndex && layer.setZIndex) {
5034                         this._lastZIndex++;
5035                         layer.setZIndex(this._lastZIndex);
5036                 }
5037
5038                 this._expandIfNotCollapsed();
5039         },
5040
5041         _update: function () {
5042                 if (!this._container) { return this; }
5043
5044                 empty(this._baseLayersList);
5045                 empty(this._overlaysList);
5046
5047                 this._layerControlInputs = [];
5048                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5049
5050                 for (i = 0; i < this._layers.length; i++) {
5051                         obj = this._layers[i];
5052                         this._addItem(obj);
5053                         overlaysPresent = overlaysPresent || obj.overlay;
5054                         baseLayersPresent = baseLayersPresent || !obj.overlay;
5055                         baseLayersCount += !obj.overlay ? 1 : 0;
5056                 }
5057
5058                 // Hide base layers section if there's only one layer.
5059                 if (this.options.hideSingleBase) {
5060                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5061                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5062                 }
5063
5064                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5065
5066                 return this;
5067         },
5068
5069         _onLayerChange: function (e) {
5070                 if (!this._handlingClick) {
5071                         this._update();
5072                 }
5073
5074                 var obj = this._getLayer(stamp(e.target));
5075
5076                 // @namespace Map
5077                 // @section Layer events
5078                 // @event baselayerchange: LayersControlEvent
5079                 // Fired when the base layer is changed through the [layer control](#control-layers).
5080                 // @event overlayadd: LayersControlEvent
5081                 // Fired when an overlay is selected through the [layer control](#control-layers).
5082                 // @event overlayremove: LayersControlEvent
5083                 // Fired when an overlay is deselected through the [layer control](#control-layers).
5084                 // @namespace Control.Layers
5085                 var type = obj.overlay ?
5086                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5087                         (e.type === 'add' ? 'baselayerchange' : null);
5088
5089                 if (type) {
5090                         this._map.fire(type, obj);
5091                 }
5092         },
5093
5094         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5095         _createRadioElement: function (name, checked) {
5096
5097                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5098                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5099
5100                 var radioFragment = document.createElement('div');
5101                 radioFragment.innerHTML = radioHtml;
5102
5103                 return radioFragment.firstChild;
5104         },
5105
5106         _addItem: function (obj) {
5107                 var label = document.createElement('label'),
5108                     checked = this._map.hasLayer(obj.layer),
5109                     input;
5110
5111                 if (obj.overlay) {
5112                         input = document.createElement('input');
5113                         input.type = 'checkbox';
5114                         input.className = 'leaflet-control-layers-selector';
5115                         input.defaultChecked = checked;
5116                 } else {
5117                         input = this._createRadioElement('leaflet-base-layers', checked);
5118                 }
5119
5120                 this._layerControlInputs.push(input);
5121                 input.layerId = stamp(obj.layer);
5122
5123                 on(input, 'click', this._onInputClick, this);
5124
5125                 var name = document.createElement('span');
5126                 name.innerHTML = ' ' + obj.name;
5127
5128                 // Helps from preventing layer control flicker when checkboxes are disabled
5129                 // https://github.com/Leaflet/Leaflet/issues/2771
5130                 var holder = document.createElement('div');
5131
5132                 label.appendChild(holder);
5133                 holder.appendChild(input);
5134                 holder.appendChild(name);
5135
5136                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5137                 container.appendChild(label);
5138
5139                 this._checkDisabledLayers();
5140                 return label;
5141         },
5142
5143         _onInputClick: function () {
5144                 var inputs = this._layerControlInputs,
5145                     input, layer;
5146                 var addedLayers = [],
5147                     removedLayers = [];
5148
5149                 this._handlingClick = true;
5150
5151                 for (var i = inputs.length - 1; i >= 0; i--) {
5152                         input = inputs[i];
5153                         layer = this._getLayer(input.layerId).layer;
5154
5155                         if (input.checked) {
5156                                 addedLayers.push(layer);
5157                         } else if (!input.checked) {
5158                                 removedLayers.push(layer);
5159                         }
5160                 }
5161
5162                 // Bugfix issue 2318: Should remove all old layers before readding new ones
5163                 for (i = 0; i < removedLayers.length; i++) {
5164                         if (this._map.hasLayer(removedLayers[i])) {
5165                                 this._map.removeLayer(removedLayers[i]);
5166                         }
5167                 }
5168                 for (i = 0; i < addedLayers.length; i++) {
5169                         if (!this._map.hasLayer(addedLayers[i])) {
5170                                 this._map.addLayer(addedLayers[i]);
5171                         }
5172                 }
5173
5174                 this._handlingClick = false;
5175
5176                 this._refocusOnMap();
5177         },
5178
5179         _checkDisabledLayers: function () {
5180                 var inputs = this._layerControlInputs,
5181                     input,
5182                     layer,
5183                     zoom = this._map.getZoom();
5184
5185                 for (var i = inputs.length - 1; i >= 0; i--) {
5186                         input = inputs[i];
5187                         layer = this._getLayer(input.layerId).layer;
5188                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5189                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5190
5191                 }
5192         },
5193
5194         _expandIfNotCollapsed: function () {
5195                 if (this._map && !this.options.collapsed) {
5196                         this.expand();
5197                 }
5198                 return this;
5199         },
5200
5201         _expand: function () {
5202                 // Backward compatibility, remove me in 1.1.
5203                 return this.expand();
5204         },
5205
5206         _collapse: function () {
5207                 // Backward compatibility, remove me in 1.1.
5208                 return this.collapse();
5209         }
5210
5211 });
5212
5213
5214 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5215 // 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.
5216 var layers = function (baseLayers, overlays, options) {
5217         return new Layers(baseLayers, overlays, options);
5218 };
5219
5220 /*
5221  * @class Control.Zoom
5222  * @aka L.Control.Zoom
5223  * @inherits Control
5224  *
5225  * 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`.
5226  */
5227
5228 var Zoom = Control.extend({
5229         // @section
5230         // @aka Control.Zoom options
5231         options: {
5232                 position: 'topleft',
5233
5234                 // @option zoomInText: String = '+'
5235                 // The text set on the 'zoom in' button.
5236                 zoomInText: '+',
5237
5238                 // @option zoomInTitle: String = 'Zoom in'
5239                 // The title set on the 'zoom in' button.
5240                 zoomInTitle: 'Zoom in',
5241
5242                 // @option zoomOutText: String = '&#x2212;'
5243                 // The text set on the 'zoom out' button.
5244                 zoomOutText: '&#x2212;',
5245
5246                 // @option zoomOutTitle: String = 'Zoom out'
5247                 // The title set on the 'zoom out' button.
5248                 zoomOutTitle: 'Zoom out'
5249         },
5250
5251         onAdd: function (map) {
5252                 var zoomName = 'leaflet-control-zoom',
5253                     container = create$1('div', zoomName + ' leaflet-bar'),
5254                     options = this.options;
5255
5256                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
5257                         zoomName + '-in',  container, this._zoomIn);
5258                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5259                         zoomName + '-out', container, this._zoomOut);
5260
5261                 this._updateDisabled();
5262                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5263
5264                 return container;
5265         },
5266
5267         onRemove: function (map) {
5268                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5269         },
5270
5271         disable: function () {
5272                 this._disabled = true;
5273                 this._updateDisabled();
5274                 return this;
5275         },
5276
5277         enable: function () {
5278                 this._disabled = false;
5279                 this._updateDisabled();
5280                 return this;
5281         },
5282
5283         _zoomIn: function (e) {
5284                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5285                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5286                 }
5287         },
5288
5289         _zoomOut: function (e) {
5290                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5291                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5292                 }
5293         },
5294
5295         _createButton: function (html, title, className, container, fn) {
5296                 var link = create$1('a', className, container);
5297                 link.innerHTML = html;
5298                 link.href = '#';
5299                 link.title = title;
5300
5301                 /*
5302                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5303                  */
5304                 link.setAttribute('role', 'button');
5305                 link.setAttribute('aria-label', title);
5306
5307                 disableClickPropagation(link);
5308                 on(link, 'click', stop);
5309                 on(link, 'click', fn, this);
5310                 on(link, 'click', this._refocusOnMap, this);
5311
5312                 return link;
5313         },
5314
5315         _updateDisabled: function () {
5316                 var map = this._map,
5317                     className = 'leaflet-disabled';
5318
5319                 removeClass(this._zoomInButton, className);
5320                 removeClass(this._zoomOutButton, className);
5321
5322                 if (this._disabled || map._zoom === map.getMinZoom()) {
5323                         addClass(this._zoomOutButton, className);
5324                 }
5325                 if (this._disabled || map._zoom === map.getMaxZoom()) {
5326                         addClass(this._zoomInButton, className);
5327                 }
5328         }
5329 });
5330
5331 // @namespace Map
5332 // @section Control options
5333 // @option zoomControl: Boolean = true
5334 // Whether a [zoom control](#control-zoom) is added to the map by default.
5335 Map.mergeOptions({
5336         zoomControl: true
5337 });
5338
5339 Map.addInitHook(function () {
5340         if (this.options.zoomControl) {
5341                 this.zoomControl = new Zoom();
5342                 this.addControl(this.zoomControl);
5343         }
5344 });
5345
5346 // @namespace Control.Zoom
5347 // @factory L.control.zoom(options: Control.Zoom options)
5348 // Creates a zoom control
5349 var zoom = function (options) {
5350         return new Zoom(options);
5351 };
5352
5353 /*
5354  * @class Control.Scale
5355  * @aka L.Control.Scale
5356  * @inherits Control
5357  *
5358  * 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`.
5359  *
5360  * @example
5361  *
5362  * ```js
5363  * L.control.scale().addTo(map);
5364  * ```
5365  */
5366
5367 var Scale = Control.extend({
5368         // @section
5369         // @aka Control.Scale options
5370         options: {
5371                 position: 'bottomleft',
5372
5373                 // @option maxWidth: Number = 100
5374                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5375                 maxWidth: 100,
5376
5377                 // @option metric: Boolean = True
5378                 // Whether to show the metric scale line (m/km).
5379                 metric: true,
5380
5381                 // @option imperial: Boolean = True
5382                 // Whether to show the imperial scale line (mi/ft).
5383                 imperial: true
5384
5385                 // @option updateWhenIdle: Boolean = false
5386                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5387         },
5388
5389         onAdd: function (map) {
5390                 var className = 'leaflet-control-scale',
5391                     container = create$1('div', className),
5392                     options = this.options;
5393
5394                 this._addScales(options, className + '-line', container);
5395
5396                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5397                 map.whenReady(this._update, this);
5398
5399                 return container;
5400         },
5401
5402         onRemove: function (map) {
5403                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5404         },
5405
5406         _addScales: function (options, className, container) {
5407                 if (options.metric) {
5408                         this._mScale = create$1('div', className, container);
5409                 }
5410                 if (options.imperial) {
5411                         this._iScale = create$1('div', className, container);
5412                 }
5413         },
5414
5415         _update: function () {
5416                 var map = this._map,
5417                     y = map.getSize().y / 2;
5418
5419                 var maxMeters = map.distance(
5420                         map.containerPointToLatLng([0, y]),
5421                         map.containerPointToLatLng([this.options.maxWidth, y]));
5422
5423                 this._updateScales(maxMeters);
5424         },
5425
5426         _updateScales: function (maxMeters) {
5427                 if (this.options.metric && maxMeters) {
5428                         this._updateMetric(maxMeters);
5429                 }
5430                 if (this.options.imperial && maxMeters) {
5431                         this._updateImperial(maxMeters);
5432                 }
5433         },
5434
5435         _updateMetric: function (maxMeters) {
5436                 var meters = this._getRoundNum(maxMeters),
5437                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5438
5439                 this._updateScale(this._mScale, label, meters / maxMeters);
5440         },
5441
5442         _updateImperial: function (maxMeters) {
5443                 var maxFeet = maxMeters * 3.2808399,
5444                     maxMiles, miles, feet;
5445
5446                 if (maxFeet > 5280) {
5447                         maxMiles = maxFeet / 5280;
5448                         miles = this._getRoundNum(maxMiles);
5449                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5450
5451                 } else {
5452                         feet = this._getRoundNum(maxFeet);
5453                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5454                 }
5455         },
5456
5457         _updateScale: function (scale, text, ratio) {
5458                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5459                 scale.innerHTML = text;
5460         },
5461
5462         _getRoundNum: function (num) {
5463                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5464                     d = num / pow10;
5465
5466                 d = d >= 10 ? 10 :
5467                     d >= 5 ? 5 :
5468                     d >= 3 ? 3 :
5469                     d >= 2 ? 2 : 1;
5470
5471                 return pow10 * d;
5472         }
5473 });
5474
5475
5476 // @factory L.control.scale(options?: Control.Scale options)
5477 // Creates an scale control with the given options.
5478 var scale = function (options) {
5479         return new Scale(options);
5480 };
5481
5482 /*
5483  * @class Control.Attribution
5484  * @aka L.Control.Attribution
5485  * @inherits Control
5486  *
5487  * 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.
5488  */
5489
5490 var Attribution = Control.extend({
5491         // @section
5492         // @aka Control.Attribution options
5493         options: {
5494                 position: 'bottomright',
5495
5496                 // @option prefix: String = 'Leaflet'
5497                 // The HTML text shown before the attributions. Pass `false` to disable.
5498                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5499         },
5500
5501         initialize: function (options) {
5502                 setOptions(this, options);
5503
5504                 this._attributions = {};
5505         },
5506
5507         onAdd: function (map) {
5508                 map.attributionControl = this;
5509                 this._container = create$1('div', 'leaflet-control-attribution');
5510                 disableClickPropagation(this._container);
5511
5512                 // TODO ugly, refactor
5513                 for (var i in map._layers) {
5514                         if (map._layers[i].getAttribution) {
5515                                 this.addAttribution(map._layers[i].getAttribution());
5516                         }
5517                 }
5518
5519                 this._update();
5520
5521                 return this._container;
5522         },
5523
5524         // @method setPrefix(prefix: String): this
5525         // Sets the text before the attributions.
5526         setPrefix: function (prefix) {
5527                 this.options.prefix = prefix;
5528                 this._update();
5529                 return this;
5530         },
5531
5532         // @method addAttribution(text: String): this
5533         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5534         addAttribution: function (text) {
5535                 if (!text) { return this; }
5536
5537                 if (!this._attributions[text]) {
5538                         this._attributions[text] = 0;
5539                 }
5540                 this._attributions[text]++;
5541
5542                 this._update();
5543
5544                 return this;
5545         },
5546
5547         // @method removeAttribution(text: String): this
5548         // Removes an attribution text.
5549         removeAttribution: function (text) {
5550                 if (!text) { return this; }
5551
5552                 if (this._attributions[text]) {
5553                         this._attributions[text]--;
5554                         this._update();
5555                 }
5556
5557                 return this;
5558         },
5559
5560         _update: function () {
5561                 if (!this._map) { return; }
5562
5563                 var attribs = [];
5564
5565                 for (var i in this._attributions) {
5566                         if (this._attributions[i]) {
5567                                 attribs.push(i);
5568                         }
5569                 }
5570
5571                 var prefixAndAttribs = [];
5572
5573                 if (this.options.prefix) {
5574                         prefixAndAttribs.push(this.options.prefix);
5575                 }
5576                 if (attribs.length) {
5577                         prefixAndAttribs.push(attribs.join(', '));
5578                 }
5579
5580                 this._container.innerHTML = prefixAndAttribs.join(' | ');
5581         }
5582 });
5583
5584 // @namespace Map
5585 // @section Control options
5586 // @option attributionControl: Boolean = true
5587 // Whether a [attribution control](#control-attribution) is added to the map by default.
5588 Map.mergeOptions({
5589         attributionControl: true
5590 });
5591
5592 Map.addInitHook(function () {
5593         if (this.options.attributionControl) {
5594                 new Attribution().addTo(this);
5595         }
5596 });
5597
5598 // @namespace Control.Attribution
5599 // @factory L.control.attribution(options: Control.Attribution options)
5600 // Creates an attribution control.
5601 var attribution = function (options) {
5602         return new Attribution(options);
5603 };
5604
5605 Control.Layers = Layers;
5606 Control.Zoom = Zoom;
5607 Control.Scale = Scale;
5608 Control.Attribution = Attribution;
5609
5610 control.layers = layers;
5611 control.zoom = zoom;
5612 control.scale = scale;
5613 control.attribution = attribution;
5614
5615 /*
5616         L.Handler is a base class for handler classes that are used internally to inject
5617         interaction features like dragging to classes like Map and Marker.
5618 */
5619
5620 // @class Handler
5621 // @aka L.Handler
5622 // Abstract class for map interaction handlers
5623
5624 var Handler = Class.extend({
5625         initialize: function (map) {
5626                 this._map = map;
5627         },
5628
5629         // @method enable(): this
5630         // Enables the handler
5631         enable: function () {
5632                 if (this._enabled) { return this; }
5633
5634                 this._enabled = true;
5635                 this.addHooks();
5636                 return this;
5637         },
5638
5639         // @method disable(): this
5640         // Disables the handler
5641         disable: function () {
5642                 if (!this._enabled) { return this; }
5643
5644                 this._enabled = false;
5645                 this.removeHooks();
5646                 return this;
5647         },
5648
5649         // @method enabled(): Boolean
5650         // Returns `true` if the handler is enabled
5651         enabled: function () {
5652                 return !!this._enabled;
5653         }
5654
5655         // @section Extension methods
5656         // Classes inheriting from `Handler` must implement the two following methods:
5657         // @method addHooks()
5658         // Called when the handler is enabled, should add event hooks.
5659         // @method removeHooks()
5660         // Called when the handler is disabled, should remove the event hooks added previously.
5661 });
5662
5663 // @section There is static function which can be called without instantiating L.Handler:
5664 // @function addTo(map: Map, name: String): this
5665 // Adds a new Handler to the given map with the given name.
5666 Handler.addTo = function (map, name) {
5667         map.addHandler(name, this);
5668         return this;
5669 };
5670
5671 var Mixin = {Events: Events};
5672
5673 /*
5674  * @class Draggable
5675  * @aka L.Draggable
5676  * @inherits Evented
5677  *
5678  * A class for making DOM elements draggable (including touch support).
5679  * Used internally for map and marker dragging. Only works for elements
5680  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5681  *
5682  * @example
5683  * ```js
5684  * var draggable = new L.Draggable(elementToDrag);
5685  * draggable.enable();
5686  * ```
5687  */
5688
5689 var START = touch ? 'touchstart mousedown' : 'mousedown';
5690 var END = {
5691         mousedown: 'mouseup',
5692         touchstart: 'touchend',
5693         pointerdown: 'touchend',
5694         MSPointerDown: 'touchend'
5695 };
5696 var MOVE = {
5697         mousedown: 'mousemove',
5698         touchstart: 'touchmove',
5699         pointerdown: 'touchmove',
5700         MSPointerDown: 'touchmove'
5701 };
5702
5703
5704 var Draggable = Evented.extend({
5705
5706         options: {
5707                 // @section
5708                 // @aka Draggable options
5709                 // @option clickTolerance: Number = 3
5710                 // The max number of pixels a user can shift the mouse pointer during a click
5711                 // for it to be considered a valid click (as opposed to a mouse drag).
5712                 clickTolerance: 3
5713         },
5714
5715         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5716         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5717         initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5718                 setOptions(this, options);
5719
5720                 this._element = element;
5721                 this._dragStartTarget = dragStartTarget || element;
5722                 this._preventOutline = preventOutline$$1;
5723         },
5724
5725         // @method enable()
5726         // Enables the dragging ability
5727         enable: function () {
5728                 if (this._enabled) { return; }
5729
5730                 on(this._dragStartTarget, START, this._onDown, this);
5731
5732                 this._enabled = true;
5733         },
5734
5735         // @method disable()
5736         // Disables the dragging ability
5737         disable: function () {
5738                 if (!this._enabled) { return; }
5739
5740                 // If we're currently dragging this draggable,
5741                 // disabling it counts as first ending the drag.
5742                 if (Draggable._dragging === this) {
5743                         this.finishDrag();
5744                 }
5745
5746                 off(this._dragStartTarget, START, this._onDown, this);
5747
5748                 this._enabled = false;
5749                 this._moved = false;
5750         },
5751
5752         _onDown: function (e) {
5753                 // Ignore simulated events, since we handle both touch and
5754                 // mouse explicitly; otherwise we risk getting duplicates of
5755                 // touch events, see #4315.
5756                 // Also ignore the event if disabled; this happens in IE11
5757                 // under some circumstances, see #3666.
5758                 if (e._simulated || !this._enabled) { return; }
5759
5760                 this._moved = false;
5761
5762                 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5763
5764                 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5765                 Draggable._dragging = this;  // Prevent dragging multiple objects at once.
5766
5767                 if (this._preventOutline) {
5768                         preventOutline(this._element);
5769                 }
5770
5771                 disableImageDrag();
5772                 disableTextSelection();
5773
5774                 if (this._moving) { return; }
5775
5776                 // @event down: Event
5777                 // Fired when a drag is about to start.
5778                 this.fire('down');
5779
5780                 var first = e.touches ? e.touches[0] : e;
5781
5782                 this._startPoint = new Point(first.clientX, first.clientY);
5783
5784                 on(document, MOVE[e.type], this._onMove, this);
5785                 on(document, END[e.type], this._onUp, this);
5786         },
5787
5788         _onMove: function (e) {
5789                 // Ignore simulated events, since we handle both touch and
5790                 // mouse explicitly; otherwise we risk getting duplicates of
5791                 // touch events, see #4315.
5792                 // Also ignore the event if disabled; this happens in IE11
5793                 // under some circumstances, see #3666.
5794                 if (e._simulated || !this._enabled) { return; }
5795
5796                 if (e.touches && e.touches.length > 1) {
5797                         this._moved = true;
5798                         return;
5799                 }
5800
5801                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5802                     newPoint = new Point(first.clientX, first.clientY),
5803                     offset = newPoint.subtract(this._startPoint);
5804
5805                 if (!offset.x && !offset.y) { return; }
5806                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5807
5808                 preventDefault(e);
5809
5810                 if (!this._moved) {
5811                         // @event dragstart: Event
5812                         // Fired when a drag starts
5813                         this.fire('dragstart');
5814
5815                         this._moved = true;
5816                         this._startPos = getPosition(this._element).subtract(offset);
5817
5818                         addClass(document.body, 'leaflet-dragging');
5819
5820                         this._lastTarget = e.target || e.srcElement;
5821                         // IE and Edge do not give the <use> element, so fetch it
5822                         // if necessary
5823                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5824                                 this._lastTarget = this._lastTarget.correspondingUseElement;
5825                         }
5826                         addClass(this._lastTarget, 'leaflet-drag-target');
5827                 }
5828
5829                 this._newPos = this._startPos.add(offset);
5830                 this._moving = true;
5831
5832                 cancelAnimFrame(this._animRequest);
5833                 this._lastEvent = e;
5834                 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5835         },
5836
5837         _updatePosition: function () {
5838                 var e = {originalEvent: this._lastEvent};
5839
5840                 // @event predrag: Event
5841                 // Fired continuously during dragging *before* each corresponding
5842                 // update of the element's position.
5843                 this.fire('predrag', e);
5844                 setPosition(this._element, this._newPos);
5845
5846                 // @event drag: Event
5847                 // Fired continuously during dragging.
5848                 this.fire('drag', e);
5849         },
5850
5851         _onUp: function (e) {
5852                 // Ignore simulated events, since we handle both touch and
5853                 // mouse explicitly; otherwise we risk getting duplicates of
5854                 // touch events, see #4315.
5855                 // Also ignore the event if disabled; this happens in IE11
5856                 // under some circumstances, see #3666.
5857                 if (e._simulated || !this._enabled) { return; }
5858                 this.finishDrag();
5859         },
5860
5861         finishDrag: function () {
5862                 removeClass(document.body, 'leaflet-dragging');
5863
5864                 if (this._lastTarget) {
5865                         removeClass(this._lastTarget, 'leaflet-drag-target');
5866                         this._lastTarget = null;
5867                 }
5868
5869                 for (var i in MOVE) {
5870                         off(document, MOVE[i], this._onMove, this);
5871                         off(document, END[i], this._onUp, this);
5872                 }
5873
5874                 enableImageDrag();
5875                 enableTextSelection();
5876
5877                 if (this._moved && this._moving) {
5878                         // ensure drag is not fired after dragend
5879                         cancelAnimFrame(this._animRequest);
5880
5881                         // @event dragend: DragEndEvent
5882                         // Fired when the drag ends.
5883                         this.fire('dragend', {
5884                                 distance: this._newPos.distanceTo(this._startPos)
5885                         });
5886                 }
5887
5888                 this._moving = false;
5889                 Draggable._dragging = false;
5890         }
5891
5892 });
5893
5894 /*
5895  * @namespace LineUtil
5896  *
5897  * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
5898  */
5899
5900 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5901 // Improves rendering performance dramatically by lessening the number of points to draw.
5902
5903 // @function simplify(points: Point[], tolerance: Number): Point[]
5904 // Dramatically reduces the number of points in a polyline while retaining
5905 // its shape and returns a new array of simplified points, using the
5906 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5907 // Used for a huge performance boost when processing/displaying Leaflet polylines for
5908 // each zoom level and also reducing visual noise. tolerance affects the amount of
5909 // simplification (lesser value means higher quality but slower and with more points).
5910 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5911 function simplify(points, tolerance) {
5912         if (!tolerance || !points.length) {
5913                 return points.slice();
5914         }
5915
5916         var sqTolerance = tolerance * tolerance;
5917
5918             // stage 1: vertex reduction
5919             points = _reducePoints(points, sqTolerance);
5920
5921             // stage 2: Douglas-Peucker simplification
5922             points = _simplifyDP(points, sqTolerance);
5923
5924         return points;
5925 }
5926
5927 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
5928 // Returns the distance between point `p` and segment `p1` to `p2`.
5929 function pointToSegmentDistance(p, p1, p2) {
5930         return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
5931 }
5932
5933 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
5934 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
5935 function closestPointOnSegment(p, p1, p2) {
5936         return _sqClosestPointOnSegment(p, p1, p2);
5937 }
5938
5939 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5940 function _simplifyDP(points, sqTolerance) {
5941
5942         var len = points.length,
5943             ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5944             markers = new ArrayConstructor(len);
5945
5946             markers[0] = markers[len - 1] = 1;
5947
5948         _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5949
5950         var i,
5951             newPoints = [];
5952
5953         for (i = 0; i < len; i++) {
5954                 if (markers[i]) {
5955                         newPoints.push(points[i]);
5956                 }
5957         }
5958
5959         return newPoints;
5960 }
5961
5962 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
5963
5964         var maxSqDist = 0,
5965         index, i, sqDist;
5966
5967         for (i = first + 1; i <= last - 1; i++) {
5968                 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
5969
5970                 if (sqDist > maxSqDist) {
5971                         index = i;
5972                         maxSqDist = sqDist;
5973                 }
5974         }
5975
5976         if (maxSqDist > sqTolerance) {
5977                 markers[index] = 1;
5978
5979                 _simplifyDPStep(points, markers, sqTolerance, first, index);
5980                 _simplifyDPStep(points, markers, sqTolerance, index, last);
5981         }
5982 }
5983
5984 // reduce points that are too close to each other to a single point
5985 function _reducePoints(points, sqTolerance) {
5986         var reducedPoints = [points[0]];
5987
5988         for (var i = 1, prev = 0, len = points.length; i < len; i++) {
5989                 if (_sqDist(points[i], points[prev]) > sqTolerance) {
5990                         reducedPoints.push(points[i]);
5991                         prev = i;
5992                 }
5993         }
5994         if (prev < len - 1) {
5995                 reducedPoints.push(points[len - 1]);
5996         }
5997         return reducedPoints;
5998 }
5999
6000 var _lastCode;
6001
6002 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6003 // Clips the segment a to b by rectangular bounds with the
6004 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6005 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6006 // points that are on the screen or near, increasing performance.
6007 function clipSegment(a, b, bounds, useLastCode, round) {
6008         var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6009             codeB = _getBitCode(b, bounds),
6010
6011             codeOut, p, newCode;
6012
6013             // save 2nd code to avoid calculating it on the next segment
6014             _lastCode = codeB;
6015
6016         while (true) {
6017                 // if a,b is inside the clip window (trivial accept)
6018                 if (!(codeA | codeB)) {
6019                         return [a, b];
6020                 }
6021
6022                 // if a,b is outside the clip window (trivial reject)
6023                 if (codeA & codeB) {
6024                         return false;
6025                 }
6026
6027                 // other cases
6028                 codeOut = codeA || codeB;
6029                 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6030                 newCode = _getBitCode(p, bounds);
6031
6032                 if (codeOut === codeA) {
6033                         a = p;
6034                         codeA = newCode;
6035                 } else {
6036                         b = p;
6037                         codeB = newCode;
6038                 }
6039         }
6040 }
6041
6042 function _getEdgeIntersection(a, b, code, bounds, round) {
6043         var dx = b.x - a.x,
6044             dy = b.y - a.y,
6045             min = bounds.min,
6046             max = bounds.max,
6047             x, y;
6048
6049         if (code & 8) { // top
6050                 x = a.x + dx * (max.y - a.y) / dy;
6051                 y = max.y;
6052
6053         } else if (code & 4) { // bottom
6054                 x = a.x + dx * (min.y - a.y) / dy;
6055                 y = min.y;
6056
6057         } else if (code & 2) { // right
6058                 x = max.x;
6059                 y = a.y + dy * (max.x - a.x) / dx;
6060
6061         } else if (code & 1) { // left
6062                 x = min.x;
6063                 y = a.y + dy * (min.x - a.x) / dx;
6064         }
6065
6066         return new Point(x, y, round);
6067 }
6068
6069 function _getBitCode(p, bounds) {
6070         var code = 0;
6071
6072         if (p.x < bounds.min.x) { // left
6073                 code |= 1;
6074         } else if (p.x > bounds.max.x) { // right
6075                 code |= 2;
6076         }
6077
6078         if (p.y < bounds.min.y) { // bottom
6079                 code |= 4;
6080         } else if (p.y > bounds.max.y) { // top
6081                 code |= 8;
6082         }
6083
6084         return code;
6085 }
6086
6087 // square distance (to avoid unnecessary Math.sqrt calls)
6088 function _sqDist(p1, p2) {
6089         var dx = p2.x - p1.x,
6090             dy = p2.y - p1.y;
6091         return dx * dx + dy * dy;
6092 }
6093
6094 // return closest point on segment or distance to that point
6095 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6096         var x = p1.x,
6097             y = p1.y,
6098             dx = p2.x - x,
6099             dy = p2.y - y,
6100             dot = dx * dx + dy * dy,
6101             t;
6102
6103         if (dot > 0) {
6104                 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6105
6106                 if (t > 1) {
6107                         x = p2.x;
6108                         y = p2.y;
6109                 } else if (t > 0) {
6110                         x += dx * t;
6111                         y += dy * t;
6112                 }
6113         }
6114
6115         dx = p.x - x;
6116         dy = p.y - y;
6117
6118         return sqDist ? dx * dx + dy * dy : new Point(x, y);
6119 }
6120
6121
6122 // @function isFlat(latlngs: LatLng[]): Boolean
6123 // Returns true if `latlngs` is a flat array, false is nested.
6124 function isFlat(latlngs) {
6125         return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6126 }
6127
6128 function _flat(latlngs) {
6129         console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6130         return isFlat(latlngs);
6131 }
6132
6133
6134 var LineUtil = (Object.freeze || Object)({
6135         simplify: simplify,
6136         pointToSegmentDistance: pointToSegmentDistance,
6137         closestPointOnSegment: closestPointOnSegment,
6138         clipSegment: clipSegment,
6139         _getEdgeIntersection: _getEdgeIntersection,
6140         _getBitCode: _getBitCode,
6141         _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6142         isFlat: isFlat,
6143         _flat: _flat
6144 });
6145
6146 /*
6147  * @namespace PolyUtil
6148  * Various utility functions for polygon geometries.
6149  */
6150
6151 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6152  * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
6153  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6154  * performance. Note that polygon points needs different algorithm for clipping
6155  * than polyline, so there's a separate method for it.
6156  */
6157 function clipPolygon(points, bounds, round) {
6158         var clippedPoints,
6159             edges = [1, 4, 2, 8],
6160             i, j, k,
6161             a, b,
6162             len, edge, p;
6163
6164         for (i = 0, len = points.length; i < len; i++) {
6165                 points[i]._code = _getBitCode(points[i], bounds);
6166         }
6167
6168         // for each edge (left, bottom, right, top)
6169         for (k = 0; k < 4; k++) {
6170                 edge = edges[k];
6171                 clippedPoints = [];
6172
6173                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6174                         a = points[i];
6175                         b = points[j];
6176
6177                         // if a is inside the clip window
6178                         if (!(a._code & edge)) {
6179                                 // if b is outside the clip window (a->b goes out of screen)
6180                                 if (b._code & edge) {
6181                                         p = _getEdgeIntersection(b, a, edge, bounds, round);
6182                                         p._code = _getBitCode(p, bounds);
6183                                         clippedPoints.push(p);
6184                                 }
6185                                 clippedPoints.push(a);
6186
6187                         // else if b is inside the clip window (a->b enters the screen)
6188                         } else if (!(b._code & edge)) {
6189                                 p = _getEdgeIntersection(b, a, edge, bounds, round);
6190                                 p._code = _getBitCode(p, bounds);
6191                                 clippedPoints.push(p);
6192                         }
6193                 }
6194                 points = clippedPoints;
6195         }
6196
6197         return points;
6198 }
6199
6200
6201 var PolyUtil = (Object.freeze || Object)({
6202         clipPolygon: clipPolygon
6203 });
6204
6205 /*
6206  * @namespace Projection
6207  * @section
6208  * Leaflet comes with a set of already defined Projections out of the box:
6209  *
6210  * @projection L.Projection.LonLat
6211  *
6212  * Equirectangular, or Plate Carree projection — the most simple projection,
6213  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6214  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6215  * `EPSG:4326` and `Simple` CRS.
6216  */
6217
6218 var LonLat = {
6219         project: function (latlng) {
6220                 return new Point(latlng.lng, latlng.lat);
6221         },
6222
6223         unproject: function (point) {
6224                 return new LatLng(point.y, point.x);
6225         },
6226
6227         bounds: new Bounds([-180, -90], [180, 90])
6228 };
6229
6230 /*
6231  * @namespace Projection
6232  * @projection L.Projection.Mercator
6233  *
6234  * 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.
6235  */
6236
6237 var Mercator = {
6238         R: 6378137,
6239         R_MINOR: 6356752.314245179,
6240
6241         bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6242
6243         project: function (latlng) {
6244                 var d = Math.PI / 180,
6245                     r = this.R,
6246                     y = latlng.lat * d,
6247                     tmp = this.R_MINOR / r,
6248                     e = Math.sqrt(1 - tmp * tmp),
6249                     con = e * Math.sin(y);
6250
6251                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6252                 y = -r * Math.log(Math.max(ts, 1E-10));
6253
6254                 return new Point(latlng.lng * d * r, y);
6255         },
6256
6257         unproject: function (point) {
6258                 var d = 180 / Math.PI,
6259                     r = this.R,
6260                     tmp = this.R_MINOR / r,
6261                     e = Math.sqrt(1 - tmp * tmp),
6262                     ts = Math.exp(-point.y / r),
6263                     phi = Math.PI / 2 - 2 * Math.atan(ts);
6264
6265                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6266                         con = e * Math.sin(phi);
6267                         con = Math.pow((1 - con) / (1 + con), e / 2);
6268                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6269                         phi += dphi;
6270                 }
6271
6272                 return new LatLng(phi * d, point.x * d / r);
6273         }
6274 };
6275
6276 /*
6277  * @class Projection
6278
6279  * An object with methods for projecting geographical coordinates of the world onto
6280  * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6281
6282  * @property bounds: Bounds
6283  * The bounds (specified in CRS units) where the projection is valid
6284
6285  * @method project(latlng: LatLng): Point
6286  * Projects geographical coordinates into a 2D point.
6287  * Only accepts actual `L.LatLng` instances, not arrays.
6288
6289  * @method unproject(point: Point): LatLng
6290  * The inverse of `project`. Projects a 2D point into a geographical location.
6291  * Only accepts actual `L.Point` instances, not arrays.
6292
6293  * Note that the projection instances do not inherit from Leafet's `Class` object,
6294  * and can't be instantiated. Also, new classes can't inherit from them,
6295  * and methods can't be added to them with the `include` function.
6296
6297  */
6298
6299
6300
6301
6302 var index = (Object.freeze || Object)({
6303         LonLat: LonLat,
6304         Mercator: Mercator,
6305         SphericalMercator: SphericalMercator
6306 });
6307
6308 /*
6309  * @namespace CRS
6310  * @crs L.CRS.EPSG3395
6311  *
6312  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6313  */
6314 var EPSG3395 = extend({}, Earth, {
6315         code: 'EPSG:3395',
6316         projection: Mercator,
6317
6318         transformation: (function () {
6319                 var scale = 0.5 / (Math.PI * Mercator.R);
6320                 return toTransformation(scale, 0.5, -scale, 0.5);
6321         }())
6322 });
6323
6324 /*
6325  * @namespace CRS
6326  * @crs L.CRS.EPSG4326
6327  *
6328  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6329  *
6330  * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6331  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
6332  * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6333  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6334  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6335  */
6336
6337 var EPSG4326 = extend({}, Earth, {
6338         code: 'EPSG:4326',
6339         projection: LonLat,
6340         transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6341 });
6342
6343 /*
6344  * @namespace CRS
6345  * @crs L.CRS.Simple
6346  *
6347  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6348  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6349  * axis should still be inverted (going from bottom to top). `distance()` returns
6350  * simple euclidean distance.
6351  */
6352
6353 var Simple = extend({}, CRS, {
6354         projection: LonLat,
6355         transformation: toTransformation(1, 0, -1, 0),
6356
6357         scale: function (zoom) {
6358                 return Math.pow(2, zoom);
6359         },
6360
6361         zoom: function (scale) {
6362                 return Math.log(scale) / Math.LN2;
6363         },
6364
6365         distance: function (latlng1, latlng2) {
6366                 var dx = latlng2.lng - latlng1.lng,
6367                     dy = latlng2.lat - latlng1.lat;
6368
6369                 return Math.sqrt(dx * dx + dy * dy);
6370         },
6371
6372         infinite: true
6373 });
6374
6375 CRS.Earth = Earth;
6376 CRS.EPSG3395 = EPSG3395;
6377 CRS.EPSG3857 = EPSG3857;
6378 CRS.EPSG900913 = EPSG900913;
6379 CRS.EPSG4326 = EPSG4326;
6380 CRS.Simple = Simple;
6381
6382 /*
6383  * @class Layer
6384  * @inherits Evented
6385  * @aka L.Layer
6386  * @aka ILayer
6387  *
6388  * A set of methods from the Layer base class that all Leaflet layers use.
6389  * Inherits all methods, options and events from `L.Evented`.
6390  *
6391  * @example
6392  *
6393  * ```js
6394  * var layer = L.Marker(latlng).addTo(map);
6395  * layer.addTo(map);
6396  * layer.remove();
6397  * ```
6398  *
6399  * @event add: Event
6400  * Fired after the layer is added to a map
6401  *
6402  * @event remove: Event
6403  * Fired after the layer is removed from a map
6404  */
6405
6406
6407 var Layer = Evented.extend({
6408
6409         // Classes extending `L.Layer` will inherit the following options:
6410         options: {
6411                 // @option pane: String = 'overlayPane'
6412                 // 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.
6413                 pane: 'overlayPane',
6414
6415                 // @option attribution: String = null
6416                 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
6417                 attribution: null,
6418
6419                 bubblingMouseEvents: true
6420         },
6421
6422         /* @section
6423          * Classes extending `L.Layer` will inherit the following methods:
6424          *
6425          * @method addTo(map: Map|LayerGroup): this
6426          * Adds the layer to the given map or layer group.
6427          */
6428         addTo: function (map) {
6429                 map.addLayer(this);
6430                 return this;
6431         },
6432
6433         // @method remove: this
6434         // Removes the layer from the map it is currently active on.
6435         remove: function () {
6436                 return this.removeFrom(this._map || this._mapToAdd);
6437         },
6438
6439         // @method removeFrom(map: Map): this
6440         // Removes the layer from the given map
6441         removeFrom: function (obj) {
6442                 if (obj) {
6443                         obj.removeLayer(this);
6444                 }
6445                 return this;
6446         },
6447
6448         // @method getPane(name? : String): HTMLElement
6449         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6450         getPane: function (name) {
6451                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6452         },
6453
6454         addInteractiveTarget: function (targetEl) {
6455                 this._map._targets[stamp(targetEl)] = this;
6456                 return this;
6457         },
6458
6459         removeInteractiveTarget: function (targetEl) {
6460                 delete this._map._targets[stamp(targetEl)];
6461                 return this;
6462         },
6463
6464         // @method getAttribution: String
6465         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6466         getAttribution: function () {
6467                 return this.options.attribution;
6468         },
6469
6470         _layerAdd: function (e) {
6471                 var map = e.target;
6472
6473                 // check in case layer gets added and then removed before the map is ready
6474                 if (!map.hasLayer(this)) { return; }
6475
6476                 this._map = map;
6477                 this._zoomAnimated = map._zoomAnimated;
6478
6479                 if (this.getEvents) {
6480                         var events = this.getEvents();
6481                         map.on(events, this);
6482                         this.once('remove', function () {
6483                                 map.off(events, this);
6484                         }, this);
6485                 }
6486
6487                 this.onAdd(map);
6488
6489                 if (this.getAttribution && map.attributionControl) {
6490                         map.attributionControl.addAttribution(this.getAttribution());
6491                 }
6492
6493                 this.fire('add');
6494                 map.fire('layeradd', {layer: this});
6495         }
6496 });
6497
6498 /* @section Extension methods
6499  * @uninheritable
6500  *
6501  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6502  *
6503  * @method onAdd(map: Map): this
6504  * 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).
6505  *
6506  * @method onRemove(map: Map): this
6507  * 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).
6508  *
6509  * @method getEvents(): Object
6510  * 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.
6511  *
6512  * @method getAttribution(): String
6513  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6514  *
6515  * @method beforeAdd(map: Map): this
6516  * 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.
6517  */
6518
6519
6520 /* @namespace Map
6521  * @section Layer events
6522  *
6523  * @event layeradd: LayerEvent
6524  * Fired when a new layer is added to the map.
6525  *
6526  * @event layerremove: LayerEvent
6527  * Fired when some layer is removed from the map
6528  *
6529  * @section Methods for Layers and Controls
6530  */
6531 Map.include({
6532         // @method addLayer(layer: Layer): this
6533         // Adds the given layer to the map
6534         addLayer: function (layer) {
6535                 if (!layer._layerAdd) {
6536                         throw new Error('The provided object is not a Layer.');
6537                 }
6538
6539                 var id = stamp(layer);
6540                 if (this._layers[id]) { return this; }
6541                 this._layers[id] = layer;
6542
6543                 layer._mapToAdd = this;
6544
6545                 if (layer.beforeAdd) {
6546                         layer.beforeAdd(this);
6547                 }
6548
6549                 this.whenReady(layer._layerAdd, layer);
6550
6551                 return this;
6552         },
6553
6554         // @method removeLayer(layer: Layer): this
6555         // Removes the given layer from the map.
6556         removeLayer: function (layer) {
6557                 var id = stamp(layer);
6558
6559                 if (!this._layers[id]) { return this; }
6560
6561                 if (this._loaded) {
6562                         layer.onRemove(this);
6563                 }
6564
6565                 if (layer.getAttribution && this.attributionControl) {
6566                         this.attributionControl.removeAttribution(layer.getAttribution());
6567                 }
6568
6569                 delete this._layers[id];
6570
6571                 if (this._loaded) {
6572                         this.fire('layerremove', {layer: layer});
6573                         layer.fire('remove');
6574                 }
6575
6576                 layer._map = layer._mapToAdd = null;
6577
6578                 return this;
6579         },
6580
6581         // @method hasLayer(layer: Layer): Boolean
6582         // Returns `true` if the given layer is currently added to the map
6583         hasLayer: function (layer) {
6584                 return !!layer && (stamp(layer) in this._layers);
6585         },
6586
6587         /* @method eachLayer(fn: Function, context?: Object): this
6588          * Iterates over the layers of the map, optionally specifying context of the iterator function.
6589          * ```
6590          * map.eachLayer(function(layer){
6591          *     layer.bindPopup('Hello');
6592          * });
6593          * ```
6594          */
6595         eachLayer: function (method, context) {
6596                 for (var i in this._layers) {
6597                         method.call(context, this._layers[i]);
6598                 }
6599                 return this;
6600         },
6601
6602         _addLayers: function (layers) {
6603                 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6604
6605                 for (var i = 0, len = layers.length; i < len; i++) {
6606                         this.addLayer(layers[i]);
6607                 }
6608         },
6609
6610         _addZoomLimit: function (layer) {
6611                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6612                         this._zoomBoundLayers[stamp(layer)] = layer;
6613                         this._updateZoomLevels();
6614                 }
6615         },
6616
6617         _removeZoomLimit: function (layer) {
6618                 var id = stamp(layer);
6619
6620                 if (this._zoomBoundLayers[id]) {
6621                         delete this._zoomBoundLayers[id];
6622                         this._updateZoomLevels();
6623                 }
6624         },
6625
6626         _updateZoomLevels: function () {
6627                 var minZoom = Infinity,
6628                     maxZoom = -Infinity,
6629                     oldZoomSpan = this._getZoomSpan();
6630
6631                 for (var i in this._zoomBoundLayers) {
6632                         var options = this._zoomBoundLayers[i].options;
6633
6634                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6635                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6636                 }
6637
6638                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6639                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6640
6641                 // @section Map state change events
6642                 // @event zoomlevelschange: Event
6643                 // Fired when the number of zoomlevels on the map is changed due
6644                 // to adding or removing a layer.
6645                 if (oldZoomSpan !== this._getZoomSpan()) {
6646                         this.fire('zoomlevelschange');
6647                 }
6648
6649                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6650                         this.setZoom(this._layersMaxZoom);
6651                 }
6652                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6653                         this.setZoom(this._layersMinZoom);
6654                 }
6655         }
6656 });
6657
6658 /*
6659  * @class LayerGroup
6660  * @aka L.LayerGroup
6661  * @inherits Layer
6662  *
6663  * Used to group several layers and handle them as one. If you add it to the map,
6664  * any layers added or removed from the group will be added/removed on the map as
6665  * well. Extends `Layer`.
6666  *
6667  * @example
6668  *
6669  * ```js
6670  * L.layerGroup([marker1, marker2])
6671  *      .addLayer(polyline)
6672  *      .addTo(map);
6673  * ```
6674  */
6675
6676 var LayerGroup = Layer.extend({
6677
6678         initialize: function (layers, options) {
6679                 setOptions(this, options);
6680
6681                 this._layers = {};
6682
6683                 var i, len;
6684
6685                 if (layers) {
6686                         for (i = 0, len = layers.length; i < len; i++) {
6687                                 this.addLayer(layers[i]);
6688                         }
6689                 }
6690         },
6691
6692         // @method addLayer(layer: Layer): this
6693         // Adds the given layer to the group.
6694         addLayer: function (layer) {
6695                 var id = this.getLayerId(layer);
6696
6697                 this._layers[id] = layer;
6698
6699                 if (this._map) {
6700                         this._map.addLayer(layer);
6701                 }
6702
6703                 return this;
6704         },
6705
6706         // @method removeLayer(layer: Layer): this
6707         // Removes the given layer from the group.
6708         // @alternative
6709         // @method removeLayer(id: Number): this
6710         // Removes the layer with the given internal ID from the group.
6711         removeLayer: function (layer) {
6712                 var id = layer in this._layers ? layer : this.getLayerId(layer);
6713
6714                 if (this._map && this._layers[id]) {
6715                         this._map.removeLayer(this._layers[id]);
6716                 }
6717
6718                 delete this._layers[id];
6719
6720                 return this;
6721         },
6722
6723         // @method hasLayer(layer: Layer): Boolean
6724         // Returns `true` if the given layer is currently added to the group.
6725         // @alternative
6726         // @method hasLayer(id: Number): Boolean
6727         // Returns `true` if the given internal ID is currently added to the group.
6728         hasLayer: function (layer) {
6729                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6730         },
6731
6732         // @method clearLayers(): this
6733         // Removes all the layers from the group.
6734         clearLayers: function () {
6735                 return this.eachLayer(this.removeLayer, this);
6736         },
6737
6738         // @method invoke(methodName: String, …): this
6739         // Calls `methodName` on every layer contained in this group, passing any
6740         // additional parameters. Has no effect if the layers contained do not
6741         // implement `methodName`.
6742         invoke: function (methodName) {
6743                 var args = Array.prototype.slice.call(arguments, 1),
6744                     i, layer;
6745
6746                 for (i in this._layers) {
6747                         layer = this._layers[i];
6748
6749                         if (layer[methodName]) {
6750                                 layer[methodName].apply(layer, args);
6751                         }
6752                 }
6753
6754                 return this;
6755         },
6756
6757         onAdd: function (map) {
6758                 this.eachLayer(map.addLayer, map);
6759         },
6760
6761         onRemove: function (map) {
6762                 this.eachLayer(map.removeLayer, map);
6763         },
6764
6765         // @method eachLayer(fn: Function, context?: Object): this
6766         // Iterates over the layers of the group, optionally specifying context of the iterator function.
6767         // ```js
6768         // group.eachLayer(function (layer) {
6769         //      layer.bindPopup('Hello');
6770         // });
6771         // ```
6772         eachLayer: function (method, context) {
6773                 for (var i in this._layers) {
6774                         method.call(context, this._layers[i]);
6775                 }
6776                 return this;
6777         },
6778
6779         // @method getLayer(id: Number): Layer
6780         // Returns the layer with the given internal ID.
6781         getLayer: function (id) {
6782                 return this._layers[id];
6783         },
6784
6785         // @method getLayers(): Layer[]
6786         // Returns an array of all the layers added to the group.
6787         getLayers: function () {
6788                 var layers = [];
6789                 this.eachLayer(layers.push, layers);
6790                 return layers;
6791         },
6792
6793         // @method setZIndex(zIndex: Number): this
6794         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6795         setZIndex: function (zIndex) {
6796                 return this.invoke('setZIndex', zIndex);
6797         },
6798
6799         // @method getLayerId(layer: Layer): Number
6800         // Returns the internal ID for a layer
6801         getLayerId: function (layer) {
6802                 return stamp(layer);
6803         }
6804 });
6805
6806
6807 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6808 // Create a layer group, optionally given an initial set of layers and an `options` object.
6809 var layerGroup = function (layers, options) {
6810         return new LayerGroup(layers, options);
6811 };
6812
6813 /*
6814  * @class FeatureGroup
6815  * @aka L.FeatureGroup
6816  * @inherits LayerGroup
6817  *
6818  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6819  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6820  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
6821  * handler, it will handle events from any of the layers. This includes mouse events
6822  * and custom events.
6823  *  * Has `layeradd` and `layerremove` events
6824  *
6825  * @example
6826  *
6827  * ```js
6828  * L.featureGroup([marker1, marker2, polyline])
6829  *      .bindPopup('Hello world!')
6830  *      .on('click', function() { alert('Clicked on a member of the group!'); })
6831  *      .addTo(map);
6832  * ```
6833  */
6834
6835 var FeatureGroup = LayerGroup.extend({
6836
6837         addLayer: function (layer) {
6838                 if (this.hasLayer(layer)) {
6839                         return this;
6840                 }
6841
6842                 layer.addEventParent(this);
6843
6844                 LayerGroup.prototype.addLayer.call(this, layer);
6845
6846                 // @event layeradd: LayerEvent
6847                 // Fired when a layer is added to this `FeatureGroup`
6848                 return this.fire('layeradd', {layer: layer});
6849         },
6850
6851         removeLayer: function (layer) {
6852                 if (!this.hasLayer(layer)) {
6853                         return this;
6854                 }
6855                 if (layer in this._layers) {
6856                         layer = this._layers[layer];
6857                 }
6858
6859                 layer.removeEventParent(this);
6860
6861                 LayerGroup.prototype.removeLayer.call(this, layer);
6862
6863                 // @event layerremove: LayerEvent
6864                 // Fired when a layer is removed from this `FeatureGroup`
6865                 return this.fire('layerremove', {layer: layer});
6866         },
6867
6868         // @method setStyle(style: Path options): this
6869         // Sets the given path options to each layer of the group that has a `setStyle` method.
6870         setStyle: function (style) {
6871                 return this.invoke('setStyle', style);
6872         },
6873
6874         // @method bringToFront(): this
6875         // Brings the layer group to the top of all other layers
6876         bringToFront: function () {
6877                 return this.invoke('bringToFront');
6878         },
6879
6880         // @method bringToBack(): this
6881         // Brings the layer group to the back of all other layers
6882         bringToBack: function () {
6883                 return this.invoke('bringToBack');
6884         },
6885
6886         // @method getBounds(): LatLngBounds
6887         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6888         getBounds: function () {
6889                 var bounds = new LatLngBounds();
6890
6891                 for (var id in this._layers) {
6892                         var layer = this._layers[id];
6893                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6894                 }
6895                 return bounds;
6896         }
6897 });
6898
6899 // @factory L.featureGroup(layers: Layer[])
6900 // Create a feature group, optionally given an initial set of layers.
6901 var featureGroup = function (layers) {
6902         return new FeatureGroup(layers);
6903 };
6904
6905 /*
6906  * @class Icon
6907  * @aka L.Icon
6908  *
6909  * Represents an icon to provide when creating a marker.
6910  *
6911  * @example
6912  *
6913  * ```js
6914  * var myIcon = L.icon({
6915  *     iconUrl: 'my-icon.png',
6916  *     iconRetinaUrl: 'my-icon@2x.png',
6917  *     iconSize: [38, 95],
6918  *     iconAnchor: [22, 94],
6919  *     popupAnchor: [-3, -76],
6920  *     shadowUrl: 'my-icon-shadow.png',
6921  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
6922  *     shadowSize: [68, 95],
6923  *     shadowAnchor: [22, 94]
6924  * });
6925  *
6926  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6927  * ```
6928  *
6929  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6930  *
6931  */
6932
6933 var Icon = Class.extend({
6934
6935         /* @section
6936          * @aka Icon options
6937          *
6938          * @option iconUrl: String = null
6939          * **(required)** The URL to the icon image (absolute or relative to your script path).
6940          *
6941          * @option iconRetinaUrl: String = null
6942          * The URL to a retina sized version of the icon image (absolute or relative to your
6943          * script path). Used for Retina screen devices.
6944          *
6945          * @option iconSize: Point = null
6946          * Size of the icon image in pixels.
6947          *
6948          * @option iconAnchor: Point = null
6949          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6950          * will be aligned so that this point is at the marker's geographical location. Centered
6951          * by default if size is specified, also can be set in CSS with negative margins.
6952          *
6953          * @option popupAnchor: Point = [0, 0]
6954          * The coordinates of the point from which popups will "open", relative to the icon anchor.
6955          *
6956          * @option tooltipAnchor: Point = [0, 0]
6957          * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
6958          *
6959          * @option shadowUrl: String = null
6960          * The URL to the icon shadow image. If not specified, no shadow image will be created.
6961          *
6962          * @option shadowRetinaUrl: String = null
6963          *
6964          * @option shadowSize: Point = null
6965          * Size of the shadow image in pixels.
6966          *
6967          * @option shadowAnchor: Point = null
6968          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6969          * as iconAnchor if not specified).
6970          *
6971          * @option className: String = ''
6972          * A custom class name to assign to both icon and shadow images. Empty by default.
6973          */
6974
6975         options: {
6976                 popupAnchor: [0, 0],
6977                 tooltipAnchor: [0, 0],
6978         },
6979
6980         initialize: function (options) {
6981                 setOptions(this, options);
6982         },
6983
6984         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6985         // Called internally when the icon has to be shown, returns a `<img>` HTML element
6986         // styled according to the options.
6987         createIcon: function (oldIcon) {
6988                 return this._createIcon('icon', oldIcon);
6989         },
6990
6991         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6992         // As `createIcon`, but for the shadow beneath it.
6993         createShadow: function (oldIcon) {
6994                 return this._createIcon('shadow', oldIcon);
6995         },
6996
6997         _createIcon: function (name, oldIcon) {
6998                 var src = this._getIconUrl(name);
6999
7000                 if (!src) {
7001                         if (name === 'icon') {
7002                                 throw new Error('iconUrl not set in Icon options (see the docs).');
7003                         }
7004                         return null;
7005                 }
7006
7007                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7008                 this._setIconStyles(img, name);
7009
7010                 return img;
7011         },
7012
7013         _setIconStyles: function (img, name) {
7014                 var options = this.options;
7015                 var sizeOption = options[name + 'Size'];
7016
7017                 if (typeof sizeOption === 'number') {
7018                         sizeOption = [sizeOption, sizeOption];
7019                 }
7020
7021                 var size = toPoint(sizeOption),
7022                     anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7023                             size && size.divideBy(2, true));
7024
7025                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7026
7027                 if (anchor) {
7028                         img.style.marginLeft = (-anchor.x) + 'px';
7029                         img.style.marginTop  = (-anchor.y) + 'px';
7030                 }
7031
7032                 if (size) {
7033                         img.style.width  = size.x + 'px';
7034                         img.style.height = size.y + 'px';
7035                 }
7036         },
7037
7038         _createImg: function (src, el) {
7039                 el = el || document.createElement('img');
7040                 el.src = src;
7041                 return el;
7042         },
7043
7044         _getIconUrl: function (name) {
7045                 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7046         }
7047 });
7048
7049
7050 // @factory L.icon(options: Icon options)
7051 // Creates an icon instance with the given options.
7052 function icon(options) {
7053         return new Icon(options);
7054 }
7055
7056 /*
7057  * @miniclass Icon.Default (Icon)
7058  * @aka L.Icon.Default
7059  * @section
7060  *
7061  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7062  * no icon is specified. Points to the blue marker image distributed with Leaflet
7063  * releases.
7064  *
7065  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7066  * (which is a set of `Icon options`).
7067  *
7068  * If you want to _completely_ replace the default icon, override the
7069  * `L.Marker.prototype.options.icon` with your own icon instead.
7070  */
7071
7072 var IconDefault = Icon.extend({
7073
7074         options: {
7075                 iconUrl:       'marker-icon.png',
7076                 iconRetinaUrl: 'marker-icon-2x.png',
7077                 shadowUrl:     'marker-shadow.png',
7078                 iconSize:    [25, 41],
7079                 iconAnchor:  [12, 41],
7080                 popupAnchor: [1, -34],
7081                 tooltipAnchor: [16, -28],
7082                 shadowSize:  [41, 41]
7083         },
7084
7085         _getIconUrl: function (name) {
7086                 if (!IconDefault.imagePath) {   // Deprecated, backwards-compatibility only
7087                         IconDefault.imagePath = this._detectIconPath();
7088                 }
7089
7090                 // @option imagePath: String
7091                 // `Icon.Default` will try to auto-detect the location of the
7092                 // blue icon images. If you are placing these images in a non-standard
7093                 // way, set this option to point to the right path.
7094                 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7095         },
7096
7097         _detectIconPath: function () {
7098                 var el = create$1('div',  'leaflet-default-icon-path', document.body);
7099                 var path = getStyle(el, 'background-image') ||
7100                            getStyle(el, 'backgroundImage');     // IE8
7101
7102                 document.body.removeChild(el);
7103
7104                 if (path === null || path.indexOf('url') !== 0) {
7105                         path = '';
7106                 } else {
7107                         path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7108                 }
7109
7110                 return path;
7111         }
7112 });
7113
7114 /*
7115  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7116  */
7117
7118
7119 /* @namespace Marker
7120  * @section Interaction handlers
7121  *
7122  * 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:
7123  *
7124  * ```js
7125  * marker.dragging.disable();
7126  * ```
7127  *
7128  * @property dragging: Handler
7129  * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7130  */
7131
7132 var MarkerDrag = Handler.extend({
7133         initialize: function (marker) {
7134                 this._marker = marker;
7135         },
7136
7137         addHooks: function () {
7138                 var icon = this._marker._icon;
7139
7140                 if (!this._draggable) {
7141                         this._draggable = new Draggable(icon, icon, true);
7142                 }
7143
7144                 this._draggable.on({
7145                         dragstart: this._onDragStart,
7146                         predrag: this._onPreDrag,
7147                         drag: this._onDrag,
7148                         dragend: this._onDragEnd
7149                 }, this).enable();
7150
7151                 addClass(icon, 'leaflet-marker-draggable');
7152         },
7153
7154         removeHooks: function () {
7155                 this._draggable.off({
7156                         dragstart: this._onDragStart,
7157                         predrag: this._onPreDrag,
7158                         drag: this._onDrag,
7159                         dragend: this._onDragEnd
7160                 }, this).disable();
7161
7162                 if (this._marker._icon) {
7163                         removeClass(this._marker._icon, 'leaflet-marker-draggable');
7164                 }
7165         },
7166
7167         moved: function () {
7168                 return this._draggable && this._draggable._moved;
7169         },
7170
7171         _adjustPan: function (e) {
7172                 var marker = this._marker,
7173                     map = marker._map,
7174                     speed = this._marker.options.autoPanSpeed,
7175                     padding = this._marker.options.autoPanPadding,
7176                     iconPos = L.DomUtil.getPosition(marker._icon),
7177                     bounds = map.getPixelBounds(),
7178                     origin = map.getPixelOrigin();
7179
7180                 var panBounds = toBounds(
7181                         bounds.min._subtract(origin).add(padding),
7182                         bounds.max._subtract(origin).subtract(padding)
7183                 );
7184
7185                 if (!panBounds.contains(iconPos)) {
7186                         // Compute incremental movement
7187                         var movement = toPoint(
7188                                 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7189                                 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7190
7191                                 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7192                                 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7193                         ).multiplyBy(speed);
7194
7195                         map.panBy(movement, {animate: false});
7196
7197                         this._draggable._newPos._add(movement);
7198                         this._draggable._startPos._add(movement);
7199
7200                         L.DomUtil.setPosition(marker._icon, this._draggable._newPos);
7201                         this._onDrag(e);
7202
7203                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7204                 }
7205         },
7206
7207         _onDragStart: function () {
7208                 // @section Dragging events
7209                 // @event dragstart: Event
7210                 // Fired when the user starts dragging the marker.
7211
7212                 // @event movestart: Event
7213                 // Fired when the marker starts moving (because of dragging).
7214
7215                 this._oldLatLng = this._marker.getLatLng();
7216                 this._marker
7217                     .closePopup()
7218                     .fire('movestart')
7219                     .fire('dragstart');
7220         },
7221
7222         _onPreDrag: function (e) {
7223                 if (this._marker.options.autoPan) {
7224                         cancelAnimFrame(this._panRequest);
7225                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7226                 }
7227         },
7228
7229         _onDrag: function (e) {
7230                 var marker = this._marker,
7231                     shadow = marker._shadow,
7232                 iconPos = getPosition(marker._icon),
7233                     latlng = marker._map.layerPointToLatLng(iconPos);
7234
7235                 // update shadow position
7236                 if (shadow) {
7237                         setPosition(shadow, iconPos);
7238                 }
7239
7240                 marker._latlng = latlng;
7241                 e.latlng = latlng;
7242                 e.oldLatLng = this._oldLatLng;
7243
7244                 // @event drag: Event
7245                 // Fired repeatedly while the user drags the marker.
7246                 marker
7247                     .fire('move', e)
7248                     .fire('drag', e);
7249         },
7250
7251         _onDragEnd: function (e) {
7252                 // @event dragend: DragEndEvent
7253                 // Fired when the user stops dragging the marker.
7254
7255                  cancelAnimFrame(this._panRequest);
7256
7257                 // @event moveend: Event
7258                 // Fired when the marker stops moving (because of dragging).
7259                 delete this._oldLatLng;
7260                 this._marker
7261                     .fire('moveend')
7262                     .fire('dragend', e);
7263         }
7264 });
7265
7266 /*
7267  * @class Marker
7268  * @inherits Interactive layer
7269  * @aka L.Marker
7270  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7271  *
7272  * @example
7273  *
7274  * ```js
7275  * L.marker([50.5, 30.5]).addTo(map);
7276  * ```
7277  */
7278
7279 var Marker = Layer.extend({
7280
7281         // @section
7282         // @aka Marker options
7283         options: {
7284                 // @option icon: Icon = *
7285                 // Icon instance to use for rendering the marker.
7286                 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7287                 // If not specified, a common instance of `L.Icon.Default` is used.
7288                 icon: new IconDefault(),
7289
7290                 // Option inherited from "Interactive layer" abstract class
7291                 interactive: true,
7292
7293                 // @option draggable: Boolean = false
7294                 // Whether the marker is draggable with mouse/touch or not.
7295                 draggable: false,
7296
7297                 // @option autoPan: Boolean = false
7298                 // Set it to `true` if you want the map to do panning animation when marker hits the edges.
7299                 autoPan: false,
7300
7301                 // @option autoPanPadding: Point = Point(50, 50)
7302                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
7303                 autoPanPadding: [50, 50],
7304
7305                 // @option autoPanSpeed: Number = 10
7306                 // Number of pixels the map should move by.
7307                 autoPanSpeed: 10,
7308
7309                 // @option keyboard: Boolean = true
7310                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7311                 keyboard: true,
7312
7313                 // @option title: String = ''
7314                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7315                 title: '',
7316
7317                 // @option alt: String = ''
7318                 // Text for the `alt` attribute of the icon image (useful for accessibility).
7319                 alt: '',
7320
7321                 // @option zIndexOffset: Number = 0
7322                 // 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).
7323                 zIndexOffset: 0,
7324
7325                 // @option opacity: Number = 1.0
7326                 // The opacity of the marker.
7327                 opacity: 1,
7328
7329                 // @option riseOnHover: Boolean = false
7330                 // If `true`, the marker will get on top of others when you hover the mouse over it.
7331                 riseOnHover: false,
7332
7333                 // @option riseOffset: Number = 250
7334                 // The z-index offset used for the `riseOnHover` feature.
7335                 riseOffset: 250,
7336
7337                 // @option pane: String = 'markerPane'
7338                 // `Map pane` where the markers icon will be added.
7339                 pane: 'markerPane',
7340
7341                 // @option bubblingMouseEvents: Boolean = false
7342                 // When `true`, a mouse event on this marker will trigger the same event on the map
7343                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7344                 bubblingMouseEvents: false
7345         },
7346
7347         /* @section
7348          *
7349          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7350          */
7351
7352         initialize: function (latlng, options) {
7353                 setOptions(this, options);
7354                 this._latlng = toLatLng(latlng);
7355         },
7356
7357         onAdd: function (map) {
7358                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7359
7360                 if (this._zoomAnimated) {
7361                         map.on('zoomanim', this._animateZoom, this);
7362                 }
7363
7364                 this._initIcon();
7365                 this.update();
7366         },
7367
7368         onRemove: function (map) {
7369                 if (this.dragging && this.dragging.enabled()) {
7370                         this.options.draggable = true;
7371                         this.dragging.removeHooks();
7372                 }
7373                 delete this.dragging;
7374
7375                 if (this._zoomAnimated) {
7376                         map.off('zoomanim', this._animateZoom, this);
7377                 }
7378
7379                 this._removeIcon();
7380                 this._removeShadow();
7381         },
7382
7383         getEvents: function () {
7384                 return {
7385                         zoom: this.update,
7386                         viewreset: this.update
7387                 };
7388         },
7389
7390         // @method getLatLng: LatLng
7391         // Returns the current geographical position of the marker.
7392         getLatLng: function () {
7393                 return this._latlng;
7394         },
7395
7396         // @method setLatLng(latlng: LatLng): this
7397         // Changes the marker position to the given point.
7398         setLatLng: function (latlng) {
7399                 var oldLatLng = this._latlng;
7400                 this._latlng = toLatLng(latlng);
7401                 this.update();
7402
7403                 // @event move: Event
7404                 // 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`.
7405                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7406         },
7407
7408         // @method setZIndexOffset(offset: Number): this
7409         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7410         setZIndexOffset: function (offset) {
7411                 this.options.zIndexOffset = offset;
7412                 return this.update();
7413         },
7414
7415         // @method setIcon(icon: Icon): this
7416         // Changes the marker icon.
7417         setIcon: function (icon) {
7418
7419                 this.options.icon = icon;
7420
7421                 if (this._map) {
7422                         this._initIcon();
7423                         this.update();
7424                 }
7425
7426                 if (this._popup) {
7427                         this.bindPopup(this._popup, this._popup.options);
7428                 }
7429
7430                 return this;
7431         },
7432
7433         getElement: function () {
7434                 return this._icon;
7435         },
7436
7437         update: function () {
7438
7439                 if (this._icon && this._map) {
7440                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
7441                         this._setPos(pos);
7442                 }
7443
7444                 return this;
7445         },
7446
7447         _initIcon: function () {
7448                 var options = this.options,
7449                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7450
7451                 var icon = options.icon.createIcon(this._icon),
7452                     addIcon = false;
7453
7454                 // if we're not reusing the icon, remove the old one and init new one
7455                 if (icon !== this._icon) {
7456                         if (this._icon) {
7457                                 this._removeIcon();
7458                         }
7459                         addIcon = true;
7460
7461                         if (options.title) {
7462                                 icon.title = options.title;
7463                         }
7464
7465                         if (icon.tagName === 'IMG') {
7466                                 icon.alt = options.alt || '';
7467                         }
7468                 }
7469
7470                 addClass(icon, classToAdd);
7471
7472                 if (options.keyboard) {
7473                         icon.tabIndex = '0';
7474                 }
7475
7476                 this._icon = icon;
7477
7478                 if (options.riseOnHover) {
7479                         this.on({
7480                                 mouseover: this._bringToFront,
7481                                 mouseout: this._resetZIndex
7482                         });
7483                 }
7484
7485                 var newShadow = options.icon.createShadow(this._shadow),
7486                     addShadow = false;
7487
7488                 if (newShadow !== this._shadow) {
7489                         this._removeShadow();
7490                         addShadow = true;
7491                 }
7492
7493                 if (newShadow) {
7494                         addClass(newShadow, classToAdd);
7495                         newShadow.alt = '';
7496                 }
7497                 this._shadow = newShadow;
7498
7499
7500                 if (options.opacity < 1) {
7501                         this._updateOpacity();
7502                 }
7503
7504
7505                 if (addIcon) {
7506                         this.getPane().appendChild(this._icon);
7507                 }
7508                 this._initInteraction();
7509                 if (newShadow && addShadow) {
7510                         this.getPane('shadowPane').appendChild(this._shadow);
7511                 }
7512         },
7513
7514         _removeIcon: function () {
7515                 if (this.options.riseOnHover) {
7516                         this.off({
7517                                 mouseover: this._bringToFront,
7518                                 mouseout: this._resetZIndex
7519                         });
7520                 }
7521
7522                 remove(this._icon);
7523                 this.removeInteractiveTarget(this._icon);
7524
7525                 this._icon = null;
7526         },
7527
7528         _removeShadow: function () {
7529                 if (this._shadow) {
7530                         remove(this._shadow);
7531                 }
7532                 this._shadow = null;
7533         },
7534
7535         _setPos: function (pos) {
7536                 setPosition(this._icon, pos);
7537
7538                 if (this._shadow) {
7539                         setPosition(this._shadow, pos);
7540                 }
7541
7542                 this._zIndex = pos.y + this.options.zIndexOffset;
7543
7544                 this._resetZIndex();
7545         },
7546
7547         _updateZIndex: function (offset) {
7548                 this._icon.style.zIndex = this._zIndex + offset;
7549         },
7550
7551         _animateZoom: function (opt) {
7552                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7553
7554                 this._setPos(pos);
7555         },
7556
7557         _initInteraction: function () {
7558
7559                 if (!this.options.interactive) { return; }
7560
7561                 addClass(this._icon, 'leaflet-interactive');
7562
7563                 this.addInteractiveTarget(this._icon);
7564
7565                 if (MarkerDrag) {
7566                         var draggable = this.options.draggable;
7567                         if (this.dragging) {
7568                                 draggable = this.dragging.enabled();
7569                                 this.dragging.disable();
7570                         }
7571
7572                         this.dragging = new MarkerDrag(this);
7573
7574                         if (draggable) {
7575                                 this.dragging.enable();
7576                         }
7577                 }
7578         },
7579
7580         // @method setOpacity(opacity: Number): this
7581         // Changes the opacity of the marker.
7582         setOpacity: function (opacity) {
7583                 this.options.opacity = opacity;
7584                 if (this._map) {
7585                         this._updateOpacity();
7586                 }
7587
7588                 return this;
7589         },
7590
7591         _updateOpacity: function () {
7592                 var opacity = this.options.opacity;
7593
7594                 setOpacity(this._icon, opacity);
7595
7596                 if (this._shadow) {
7597                         setOpacity(this._shadow, opacity);
7598                 }
7599         },
7600
7601         _bringToFront: function () {
7602                 this._updateZIndex(this.options.riseOffset);
7603         },
7604
7605         _resetZIndex: function () {
7606                 this._updateZIndex(0);
7607         },
7608
7609         _getPopupAnchor: function () {
7610                 return this.options.icon.options.popupAnchor;
7611         },
7612
7613         _getTooltipAnchor: function () {
7614                 return this.options.icon.options.tooltipAnchor;
7615         }
7616 });
7617
7618
7619 // factory L.marker(latlng: LatLng, options? : Marker options)
7620
7621 // @factory L.marker(latlng: LatLng, options? : Marker options)
7622 // Instantiates a Marker object given a geographical point and optionally an options object.
7623 function marker(latlng, options) {
7624         return new Marker(latlng, options);
7625 }
7626
7627 /*
7628  * @class Path
7629  * @aka L.Path
7630  * @inherits Interactive layer
7631  *
7632  * An abstract class that contains options and constants shared between vector
7633  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7634  */
7635
7636 var Path = Layer.extend({
7637
7638         // @section
7639         // @aka Path options
7640         options: {
7641                 // @option stroke: Boolean = true
7642                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7643                 stroke: true,
7644
7645                 // @option color: String = '#3388ff'
7646                 // Stroke color
7647                 color: '#3388ff',
7648
7649                 // @option weight: Number = 3
7650                 // Stroke width in pixels
7651                 weight: 3,
7652
7653                 // @option opacity: Number = 1.0
7654                 // Stroke opacity
7655                 opacity: 1,
7656
7657                 // @option lineCap: String= 'round'
7658                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7659                 lineCap: 'round',
7660
7661                 // @option lineJoin: String = 'round'
7662                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7663                 lineJoin: 'round',
7664
7665                 // @option dashArray: String = null
7666                 // 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).
7667                 dashArray: null,
7668
7669                 // @option dashOffset: String = null
7670                 // 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).
7671                 dashOffset: null,
7672
7673                 // @option fill: Boolean = depends
7674                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7675                 fill: false,
7676
7677                 // @option fillColor: String = *
7678                 // Fill color. Defaults to the value of the [`color`](#path-color) option
7679                 fillColor: null,
7680
7681                 // @option fillOpacity: Number = 0.2
7682                 // Fill opacity.
7683                 fillOpacity: 0.2,
7684
7685                 // @option fillRule: String = 'evenodd'
7686                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7687                 fillRule: 'evenodd',
7688
7689                 // className: '',
7690
7691                 // Option inherited from "Interactive layer" abstract class
7692                 interactive: true,
7693
7694                 // @option bubblingMouseEvents: Boolean = true
7695                 // When `true`, a mouse event on this path will trigger the same event on the map
7696                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7697                 bubblingMouseEvents: true
7698         },
7699
7700         beforeAdd: function (map) {
7701                 // Renderer is set here because we need to call renderer.getEvents
7702                 // before this.getEvents.
7703                 this._renderer = map.getRenderer(this);
7704         },
7705
7706         onAdd: function () {
7707                 this._renderer._initPath(this);
7708                 this._reset();
7709                 this._renderer._addPath(this);
7710         },
7711
7712         onRemove: function () {
7713                 this._renderer._removePath(this);
7714         },
7715
7716         // @method redraw(): this
7717         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7718         redraw: function () {
7719                 if (this._map) {
7720                         this._renderer._updatePath(this);
7721                 }
7722                 return this;
7723         },
7724
7725         // @method setStyle(style: Path options): this
7726         // Changes the appearance of a Path based on the options in the `Path options` object.
7727         setStyle: function (style) {
7728                 setOptions(this, style);
7729                 if (this._renderer) {
7730                         this._renderer._updateStyle(this);
7731                 }
7732                 return this;
7733         },
7734
7735         // @method bringToFront(): this
7736         // Brings the layer to the top of all path layers.
7737         bringToFront: function () {
7738                 if (this._renderer) {
7739                         this._renderer._bringToFront(this);
7740                 }
7741                 return this;
7742         },
7743
7744         // @method bringToBack(): this
7745         // Brings the layer to the bottom of all path layers.
7746         bringToBack: function () {
7747                 if (this._renderer) {
7748                         this._renderer._bringToBack(this);
7749                 }
7750                 return this;
7751         },
7752
7753         getElement: function () {
7754                 return this._path;
7755         },
7756
7757         _reset: function () {
7758                 // defined in child classes
7759                 this._project();
7760                 this._update();
7761         },
7762
7763         _clickTolerance: function () {
7764                 // used when doing hit detection for Canvas layers
7765                 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7766         }
7767 });
7768
7769 /*
7770  * @class CircleMarker
7771  * @aka L.CircleMarker
7772  * @inherits Path
7773  *
7774  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7775  */
7776
7777 var CircleMarker = Path.extend({
7778
7779         // @section
7780         // @aka CircleMarker options
7781         options: {
7782                 fill: true,
7783
7784                 // @option radius: Number = 10
7785                 // Radius of the circle marker, in pixels
7786                 radius: 10
7787         },
7788
7789         initialize: function (latlng, options) {
7790                 setOptions(this, options);
7791                 this._latlng = toLatLng(latlng);
7792                 this._radius = this.options.radius;
7793         },
7794
7795         // @method setLatLng(latLng: LatLng): this
7796         // Sets the position of a circle marker to a new location.
7797         setLatLng: function (latlng) {
7798                 this._latlng = toLatLng(latlng);
7799                 this.redraw();
7800                 return this.fire('move', {latlng: this._latlng});
7801         },
7802
7803         // @method getLatLng(): LatLng
7804         // Returns the current geographical position of the circle marker
7805         getLatLng: function () {
7806                 return this._latlng;
7807         },
7808
7809         // @method setRadius(radius: Number): this
7810         // Sets the radius of a circle marker. Units are in pixels.
7811         setRadius: function (radius) {
7812                 this.options.radius = this._radius = radius;
7813                 return this.redraw();
7814         },
7815
7816         // @method getRadius(): Number
7817         // Returns the current radius of the circle
7818         getRadius: function () {
7819                 return this._radius;
7820         },
7821
7822         setStyle : function (options) {
7823                 var radius = options && options.radius || this._radius;
7824                 Path.prototype.setStyle.call(this, options);
7825                 this.setRadius(radius);
7826                 return this;
7827         },
7828
7829         _project: function () {
7830                 this._point = this._map.latLngToLayerPoint(this._latlng);
7831                 this._updateBounds();
7832         },
7833
7834         _updateBounds: function () {
7835                 var r = this._radius,
7836                     r2 = this._radiusY || r,
7837                     w = this._clickTolerance(),
7838                     p = [r + w, r2 + w];
7839                 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7840         },
7841
7842         _update: function () {
7843                 if (this._map) {
7844                         this._updatePath();
7845                 }
7846         },
7847
7848         _updatePath: function () {
7849                 this._renderer._updateCircle(this);
7850         },
7851
7852         _empty: function () {
7853                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7854         },
7855
7856         // Needed by the `Canvas` renderer for interactivity
7857         _containsPoint: function (p) {
7858                 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7859         }
7860 });
7861
7862
7863 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7864 // Instantiates a circle marker object given a geographical point, and an optional options object.
7865 function circleMarker(latlng, options) {
7866         return new CircleMarker(latlng, options);
7867 }
7868
7869 /*
7870  * @class Circle
7871  * @aka L.Circle
7872  * @inherits CircleMarker
7873  *
7874  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7875  *
7876  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7877  *
7878  * @example
7879  *
7880  * ```js
7881  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7882  * ```
7883  */
7884
7885 var Circle = CircleMarker.extend({
7886
7887         initialize: function (latlng, options, legacyOptions) {
7888                 if (typeof options === 'number') {
7889                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7890                         options = extend({}, legacyOptions, {radius: options});
7891                 }
7892                 setOptions(this, options);
7893                 this._latlng = toLatLng(latlng);
7894
7895                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7896
7897                 // @section
7898                 // @aka Circle options
7899                 // @option radius: Number; Radius of the circle, in meters.
7900                 this._mRadius = this.options.radius;
7901         },
7902
7903         // @method setRadius(radius: Number): this
7904         // Sets the radius of a circle. Units are in meters.
7905         setRadius: function (radius) {
7906                 this._mRadius = radius;
7907                 return this.redraw();
7908         },
7909
7910         // @method getRadius(): Number
7911         // Returns the current radius of a circle. Units are in meters.
7912         getRadius: function () {
7913                 return this._mRadius;
7914         },
7915
7916         // @method getBounds(): LatLngBounds
7917         // Returns the `LatLngBounds` of the path.
7918         getBounds: function () {
7919                 var half = [this._radius, this._radiusY || this._radius];
7920
7921                 return new LatLngBounds(
7922                         this._map.layerPointToLatLng(this._point.subtract(half)),
7923                         this._map.layerPointToLatLng(this._point.add(half)));
7924         },
7925
7926         setStyle: Path.prototype.setStyle,
7927
7928         _project: function () {
7929
7930                 var lng = this._latlng.lng,
7931                     lat = this._latlng.lat,
7932                     map = this._map,
7933                     crs = map.options.crs;
7934
7935                 if (crs.distance === Earth.distance) {
7936                         var d = Math.PI / 180,
7937                             latR = (this._mRadius / Earth.R) / d,
7938                             top = map.project([lat + latR, lng]),
7939                             bottom = map.project([lat - latR, lng]),
7940                             p = top.add(bottom).divideBy(2),
7941                             lat2 = map.unproject(p).lat,
7942                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
7943                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
7944
7945                         if (isNaN(lngR) || lngR === 0) {
7946                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
7947                         }
7948
7949                         this._point = p.subtract(map.getPixelOrigin());
7950                         this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
7951                         this._radiusY = p.y - top.y;
7952
7953                 } else {
7954                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
7955
7956                         this._point = map.latLngToLayerPoint(this._latlng);
7957                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
7958                 }
7959
7960                 this._updateBounds();
7961         }
7962 });
7963
7964 // @factory L.circle(latlng: LatLng, options?: Circle options)
7965 // Instantiates a circle object given a geographical point, and an options object
7966 // which contains the circle radius.
7967 // @alternative
7968 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
7969 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
7970 // Do not use in new applications or plugins.
7971 function circle(latlng, options, legacyOptions) {
7972         return new Circle(latlng, options, legacyOptions);
7973 }
7974
7975 /*
7976  * @class Polyline
7977  * @aka L.Polyline
7978  * @inherits Path
7979  *
7980  * A class for drawing polyline overlays on a map. Extends `Path`.
7981  *
7982  * @example
7983  *
7984  * ```js
7985  * // create a red polyline from an array of LatLng points
7986  * var latlngs = [
7987  *      [45.51, -122.68],
7988  *      [37.77, -122.43],
7989  *      [34.04, -118.2]
7990  * ];
7991  *
7992  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7993  *
7994  * // zoom the map to the polyline
7995  * map.fitBounds(polyline.getBounds());
7996  * ```
7997  *
7998  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7999  *
8000  * ```js
8001  * // create a red polyline from an array of arrays of LatLng points
8002  * var latlngs = [
8003  *      [[45.51, -122.68],
8004  *       [37.77, -122.43],
8005  *       [34.04, -118.2]],
8006  *      [[40.78, -73.91],
8007  *       [41.83, -87.62],
8008  *       [32.76, -96.72]]
8009  * ];
8010  * ```
8011  */
8012
8013
8014 var Polyline = Path.extend({
8015
8016         // @section
8017         // @aka Polyline options
8018         options: {
8019                 // @option smoothFactor: Number = 1.0
8020                 // How much to simplify the polyline on each zoom level. More means
8021                 // better performance and smoother look, and less means more accurate representation.
8022                 smoothFactor: 1.0,
8023
8024                 // @option noClip: Boolean = false
8025                 // Disable polyline clipping.
8026                 noClip: false
8027         },
8028
8029         initialize: function (latlngs, options) {
8030                 setOptions(this, options);
8031                 this._setLatLngs(latlngs);
8032         },
8033
8034         // @method getLatLngs(): LatLng[]
8035         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8036         getLatLngs: function () {
8037                 return this._latlngs;
8038         },
8039
8040         // @method setLatLngs(latlngs: LatLng[]): this
8041         // Replaces all the points in the polyline with the given array of geographical points.
8042         setLatLngs: function (latlngs) {
8043                 this._setLatLngs(latlngs);
8044                 return this.redraw();
8045         },
8046
8047         // @method isEmpty(): Boolean
8048         // Returns `true` if the Polyline has no LatLngs.
8049         isEmpty: function () {
8050                 return !this._latlngs.length;
8051         },
8052
8053         // @method closestLayerPoint: Point
8054         // Returns the point closest to `p` on the Polyline.
8055         closestLayerPoint: function (p) {
8056                 var minDistance = Infinity,
8057                     minPoint = null,
8058                     closest = _sqClosestPointOnSegment,
8059                     p1, p2;
8060
8061                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8062                         var points = this._parts[j];
8063
8064                         for (var i = 1, len = points.length; i < len; i++) {
8065                                 p1 = points[i - 1];
8066                                 p2 = points[i];
8067
8068                                 var sqDist = closest(p, p1, p2, true);
8069
8070                                 if (sqDist < minDistance) {
8071                                         minDistance = sqDist;
8072                                         minPoint = closest(p, p1, p2);
8073                                 }
8074                         }
8075                 }
8076                 if (minPoint) {
8077                         minPoint.distance = Math.sqrt(minDistance);
8078                 }
8079                 return minPoint;
8080         },
8081
8082         // @method getCenter(): LatLng
8083         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8084         getCenter: function () {
8085                 // throws error when not yet added to map as this center calculation requires projected coordinates
8086                 if (!this._map) {
8087                         throw new Error('Must add layer to map before using getCenter()');
8088                 }
8089
8090                 var i, halfDist, segDist, dist, p1, p2, ratio,
8091                     points = this._rings[0],
8092                     len = points.length;
8093
8094                 if (!len) { return null; }
8095
8096                 // polyline centroid algorithm; only uses the first ring if there are multiple
8097
8098                 for (i = 0, halfDist = 0; i < len - 1; i++) {
8099                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
8100                 }
8101
8102                 // The line is so small in the current view that all points are on the same pixel.
8103                 if (halfDist === 0) {
8104                         return this._map.layerPointToLatLng(points[0]);
8105                 }
8106
8107                 for (i = 0, dist = 0; i < len - 1; i++) {
8108                         p1 = points[i];
8109                         p2 = points[i + 1];
8110                         segDist = p1.distanceTo(p2);
8111                         dist += segDist;
8112
8113                         if (dist > halfDist) {
8114                                 ratio = (dist - halfDist) / segDist;
8115                                 return this._map.layerPointToLatLng([
8116                                         p2.x - ratio * (p2.x - p1.x),
8117                                         p2.y - ratio * (p2.y - p1.y)
8118                                 ]);
8119                         }
8120                 }
8121         },
8122
8123         // @method getBounds(): LatLngBounds
8124         // Returns the `LatLngBounds` of the path.
8125         getBounds: function () {
8126                 return this._bounds;
8127         },
8128
8129         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8130         // Adds a given point to the polyline. By default, adds to the first ring of
8131         // the polyline in case of a multi-polyline, but can be overridden by passing
8132         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8133         addLatLng: function (latlng, latlngs) {
8134                 latlngs = latlngs || this._defaultShape();
8135                 latlng = toLatLng(latlng);
8136                 latlngs.push(latlng);
8137                 this._bounds.extend(latlng);
8138                 return this.redraw();
8139         },
8140
8141         _setLatLngs: function (latlngs) {
8142                 this._bounds = new LatLngBounds();
8143                 this._latlngs = this._convertLatLngs(latlngs);
8144         },
8145
8146         _defaultShape: function () {
8147                 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8148         },
8149
8150         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8151         _convertLatLngs: function (latlngs) {
8152                 var result = [],
8153                     flat = isFlat(latlngs);
8154
8155                 for (var i = 0, len = latlngs.length; i < len; i++) {
8156                         if (flat) {
8157                                 result[i] = toLatLng(latlngs[i]);
8158                                 this._bounds.extend(result[i]);
8159                         } else {
8160                                 result[i] = this._convertLatLngs(latlngs[i]);
8161                         }
8162                 }
8163
8164                 return result;
8165         },
8166
8167         _project: function () {
8168                 var pxBounds = new Bounds();
8169                 this._rings = [];
8170                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8171
8172                 var w = this._clickTolerance(),
8173                     p = new Point(w, w);
8174
8175                 if (this._bounds.isValid() && pxBounds.isValid()) {
8176                         pxBounds.min._subtract(p);
8177                         pxBounds.max._add(p);
8178                         this._pxBounds = pxBounds;
8179                 }
8180         },
8181
8182         // recursively turns latlngs into a set of rings with projected coordinates
8183         _projectLatlngs: function (latlngs, result, projectedBounds) {
8184                 var flat = latlngs[0] instanceof LatLng,
8185                     len = latlngs.length,
8186                     i, ring;
8187
8188                 if (flat) {
8189                         ring = [];
8190                         for (i = 0; i < len; i++) {
8191                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8192                                 projectedBounds.extend(ring[i]);
8193                         }
8194                         result.push(ring);
8195                 } else {
8196                         for (i = 0; i < len; i++) {
8197                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8198                         }
8199                 }
8200         },
8201
8202         // clip polyline by renderer bounds so that we have less to render for performance
8203         _clipPoints: function () {
8204                 var bounds = this._renderer._bounds;
8205
8206                 this._parts = [];
8207                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8208                         return;
8209                 }
8210
8211                 if (this.options.noClip) {
8212                         this._parts = this._rings;
8213                         return;
8214                 }
8215
8216                 var parts = this._parts,
8217                     i, j, k, len, len2, segment, points;
8218
8219                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8220                         points = this._rings[i];
8221
8222                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8223                                 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8224
8225                                 if (!segment) { continue; }
8226
8227                                 parts[k] = parts[k] || [];
8228                                 parts[k].push(segment[0]);
8229
8230                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8231                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8232                                         parts[k].push(segment[1]);
8233                                         k++;
8234                                 }
8235                         }
8236                 }
8237         },
8238
8239         // simplify each clipped part of the polyline for performance
8240         _simplifyPoints: function () {
8241                 var parts = this._parts,
8242                     tolerance = this.options.smoothFactor;
8243
8244                 for (var i = 0, len = parts.length; i < len; i++) {
8245                         parts[i] = simplify(parts[i], tolerance);
8246                 }
8247         },
8248
8249         _update: function () {
8250                 if (!this._map) { return; }
8251
8252                 this._clipPoints();
8253                 this._simplifyPoints();
8254                 this._updatePath();
8255         },
8256
8257         _updatePath: function () {
8258                 this._renderer._updatePoly(this);
8259         },
8260
8261         // Needed by the `Canvas` renderer for interactivity
8262         _containsPoint: function (p, closed) {
8263                 var i, j, k, len, len2, part,
8264                     w = this._clickTolerance();
8265
8266                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8267
8268                 // hit detection for polylines
8269                 for (i = 0, len = this._parts.length; i < len; i++) {
8270                         part = this._parts[i];
8271
8272                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8273                                 if (!closed && (j === 0)) { continue; }
8274
8275                                 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8276                                         return true;
8277                                 }
8278                         }
8279                 }
8280                 return false;
8281         }
8282 });
8283
8284 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8285 // Instantiates a polyline object given an array of geographical points and
8286 // optionally an options object. You can create a `Polyline` object with
8287 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8288 // of geographic points.
8289 function polyline(latlngs, options) {
8290         return new Polyline(latlngs, options);
8291 }
8292
8293 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8294 Polyline._flat = _flat;
8295
8296 /*
8297  * @class Polygon
8298  * @aka L.Polygon
8299  * @inherits Polyline
8300  *
8301  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8302  *
8303  * 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.
8304  *
8305  *
8306  * @example
8307  *
8308  * ```js
8309  * // create a red polygon from an array of LatLng points
8310  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8311  *
8312  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8313  *
8314  * // zoom the map to the polygon
8315  * map.fitBounds(polygon.getBounds());
8316  * ```
8317  *
8318  * 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:
8319  *
8320  * ```js
8321  * var latlngs = [
8322  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8323  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8324  * ];
8325  * ```
8326  *
8327  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8328  *
8329  * ```js
8330  * var latlngs = [
8331  *   [ // first polygon
8332  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8333  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8334  *   ],
8335  *   [ // second polygon
8336  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8337  *   ]
8338  * ];
8339  * ```
8340  */
8341
8342 var Polygon = Polyline.extend({
8343
8344         options: {
8345                 fill: true
8346         },
8347
8348         isEmpty: function () {
8349                 return !this._latlngs.length || !this._latlngs[0].length;
8350         },
8351
8352         getCenter: function () {
8353                 // throws error when not yet added to map as this center calculation requires projected coordinates
8354                 if (!this._map) {
8355                         throw new Error('Must add layer to map before using getCenter()');
8356                 }
8357
8358                 var i, j, p1, p2, f, area, x, y, center,
8359                     points = this._rings[0],
8360                     len = points.length;
8361
8362                 if (!len) { return null; }
8363
8364                 // polygon centroid algorithm; only uses the first ring if there are multiple
8365
8366                 area = x = y = 0;
8367
8368                 for (i = 0, j = len - 1; i < len; j = i++) {
8369                         p1 = points[i];
8370                         p2 = points[j];
8371
8372                         f = p1.y * p2.x - p2.y * p1.x;
8373                         x += (p1.x + p2.x) * f;
8374                         y += (p1.y + p2.y) * f;
8375                         area += f * 3;
8376                 }
8377
8378                 if (area === 0) {
8379                         // Polygon is so small that all points are on same pixel.
8380                         center = points[0];
8381                 } else {
8382                         center = [x / area, y / area];
8383                 }
8384                 return this._map.layerPointToLatLng(center);
8385         },
8386
8387         _convertLatLngs: function (latlngs) {
8388                 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8389                     len = result.length;
8390
8391                 // remove last point if it equals first one
8392                 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8393                         result.pop();
8394                 }
8395                 return result;
8396         },
8397
8398         _setLatLngs: function (latlngs) {
8399                 Polyline.prototype._setLatLngs.call(this, latlngs);
8400                 if (isFlat(this._latlngs)) {
8401                         this._latlngs = [this._latlngs];
8402                 }
8403         },
8404
8405         _defaultShape: function () {
8406                 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8407         },
8408
8409         _clipPoints: function () {
8410                 // polygons need a different clipping algorithm so we redefine that
8411
8412                 var bounds = this._renderer._bounds,
8413                     w = this.options.weight,
8414                     p = new Point(w, w);
8415
8416                 // increase clip padding by stroke width to avoid stroke on clip edges
8417                 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8418
8419                 this._parts = [];
8420                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8421                         return;
8422                 }
8423
8424                 if (this.options.noClip) {
8425                         this._parts = this._rings;
8426                         return;
8427                 }
8428
8429                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8430                         clipped = clipPolygon(this._rings[i], bounds, true);
8431                         if (clipped.length) {
8432                                 this._parts.push(clipped);
8433                         }
8434                 }
8435         },
8436
8437         _updatePath: function () {
8438                 this._renderer._updatePoly(this, true);
8439         },
8440
8441         // Needed by the `Canvas` renderer for interactivity
8442         _containsPoint: function (p) {
8443                 var inside = false,
8444                     part, p1, p2, i, j, k, len, len2;
8445
8446                 if (!this._pxBounds.contains(p)) { return false; }
8447
8448                 // ray casting algorithm for detecting if point is in polygon
8449                 for (i = 0, len = this._parts.length; i < len; i++) {
8450                         part = this._parts[i];
8451
8452                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8453                                 p1 = part[j];
8454                                 p2 = part[k];
8455
8456                                 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)) {
8457                                         inside = !inside;
8458                                 }
8459                         }
8460                 }
8461
8462                 // also check if it's on polygon stroke
8463                 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8464         }
8465
8466 });
8467
8468
8469 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8470 function polygon(latlngs, options) {
8471         return new Polygon(latlngs, options);
8472 }
8473
8474 /*
8475  * @class GeoJSON
8476  * @aka L.GeoJSON
8477  * @inherits FeatureGroup
8478  *
8479  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8480  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8481  *
8482  * @example
8483  *
8484  * ```js
8485  * L.geoJSON(data, {
8486  *      style: function (feature) {
8487  *              return {color: feature.properties.color};
8488  *      }
8489  * }).bindPopup(function (layer) {
8490  *      return layer.feature.properties.description;
8491  * }).addTo(map);
8492  * ```
8493  */
8494
8495 var GeoJSON = FeatureGroup.extend({
8496
8497         /* @section
8498          * @aka GeoJSON options
8499          *
8500          * @option pointToLayer: Function = *
8501          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8502          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8503          * The default is to spawn a default `Marker`:
8504          * ```js
8505          * function(geoJsonPoint, latlng) {
8506          *      return L.marker(latlng);
8507          * }
8508          * ```
8509          *
8510          * @option style: Function = *
8511          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8512          * called internally when data is added.
8513          * The default value is to not override any defaults:
8514          * ```js
8515          * function (geoJsonFeature) {
8516          *      return {}
8517          * }
8518          * ```
8519          *
8520          * @option onEachFeature: Function = *
8521          * A `Function` that will be called once for each created `Feature`, after it has
8522          * been created and styled. Useful for attaching events and popups to features.
8523          * The default is to do nothing with the newly created layers:
8524          * ```js
8525          * function (feature, layer) {}
8526          * ```
8527          *
8528          * @option filter: Function = *
8529          * A `Function` that will be used to decide whether to include a feature or not.
8530          * The default is to include all features:
8531          * ```js
8532          * function (geoJsonFeature) {
8533          *      return true;
8534          * }
8535          * ```
8536          * Note: dynamically changing the `filter` option will have effect only on newly
8537          * added data. It will _not_ re-evaluate already included features.
8538          *
8539          * @option coordsToLatLng: Function = *
8540          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8541          * The default is the `coordsToLatLng` static method.
8542          */
8543
8544         initialize: function (geojson, options) {
8545                 setOptions(this, options);
8546
8547                 this._layers = {};
8548
8549                 if (geojson) {
8550                         this.addData(geojson);
8551                 }
8552         },
8553
8554         // @method addData( <GeoJSON> data ): this
8555         // Adds a GeoJSON object to the layer.
8556         addData: function (geojson) {
8557                 var features = isArray(geojson) ? geojson : geojson.features,
8558                     i, len, feature;
8559
8560                 if (features) {
8561                         for (i = 0, len = features.length; i < len; i++) {
8562                                 // only add this if geometry or geometries are set and not null
8563                                 feature = features[i];
8564                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8565                                         this.addData(feature);
8566                                 }
8567                         }
8568                         return this;
8569                 }
8570
8571                 var options = this.options;
8572
8573                 if (options.filter && !options.filter(geojson)) { return this; }
8574
8575                 var layer = geometryToLayer(geojson, options);
8576                 if (!layer) {
8577                         return this;
8578                 }
8579                 layer.feature = asFeature(geojson);
8580
8581                 layer.defaultOptions = layer.options;
8582                 this.resetStyle(layer);
8583
8584                 if (options.onEachFeature) {
8585                         options.onEachFeature(geojson, layer);
8586                 }
8587
8588                 return this.addLayer(layer);
8589         },
8590
8591         // @method resetStyle( <Path> layer ): this
8592         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8593         resetStyle: function (layer) {
8594                 // reset any custom styles
8595                 layer.options = extend({}, layer.defaultOptions);
8596                 this._setLayerStyle(layer, this.options.style);
8597                 return this;
8598         },
8599
8600         // @method setStyle( <Function> style ): this
8601         // Changes styles of GeoJSON vector layers with the given style function.
8602         setStyle: function (style) {
8603                 return this.eachLayer(function (layer) {
8604                         this._setLayerStyle(layer, style);
8605                 }, this);
8606         },
8607
8608         _setLayerStyle: function (layer, style) {
8609                 if (typeof style === 'function') {
8610                         style = style(layer.feature);
8611                 }
8612                 if (layer.setStyle) {
8613                         layer.setStyle(style);
8614                 }
8615         }
8616 });
8617
8618 // @section
8619 // There are several static functions which can be called without instantiating L.GeoJSON:
8620
8621 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8622 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8623 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8624 // functions if provided as options.
8625 function geometryToLayer(geojson, options) {
8626
8627         var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8628             coords = geometry ? geometry.coordinates : null,
8629             layers = [],
8630             pointToLayer = options && options.pointToLayer,
8631             _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8632             latlng, latlngs, i, len;
8633
8634         if (!coords && !geometry) {
8635                 return null;
8636         }
8637
8638         switch (geometry.type) {
8639         case 'Point':
8640                 latlng = _coordsToLatLng(coords);
8641                 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8642
8643         case 'MultiPoint':
8644                 for (i = 0, len = coords.length; i < len; i++) {
8645                         latlng = _coordsToLatLng(coords[i]);
8646                         layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8647                 }
8648                 return new FeatureGroup(layers);
8649
8650         case 'LineString':
8651         case 'MultiLineString':
8652                 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8653                 return new Polyline(latlngs, options);
8654
8655         case 'Polygon':
8656         case 'MultiPolygon':
8657                 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8658                 return new Polygon(latlngs, options);
8659
8660         case 'GeometryCollection':
8661                 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8662                         var layer = geometryToLayer({
8663                                 geometry: geometry.geometries[i],
8664                                 type: 'Feature',
8665                                 properties: geojson.properties
8666                         }, options);
8667
8668                         if (layer) {
8669                                 layers.push(layer);
8670                         }
8671                 }
8672                 return new FeatureGroup(layers);
8673
8674         default:
8675                 throw new Error('Invalid GeoJSON object.');
8676         }
8677 }
8678
8679 // @function coordsToLatLng(coords: Array): LatLng
8680 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8681 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8682 function coordsToLatLng(coords) {
8683         return new LatLng(coords[1], coords[0], coords[2]);
8684 }
8685
8686 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8687 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8688 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8689 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8690 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8691         var latlngs = [];
8692
8693         for (var i = 0, len = coords.length, latlng; i < len; i++) {
8694                 latlng = levelsDeep ?
8695                         coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8696                         (_coordsToLatLng || coordsToLatLng)(coords[i]);
8697
8698                 latlngs.push(latlng);
8699         }
8700
8701         return latlngs;
8702 }
8703
8704 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8705 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8706 function latLngToCoords(latlng, precision) {
8707         precision = typeof precision === 'number' ? precision : 6;
8708         return latlng.alt !== undefined ?
8709                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8710                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8711 }
8712
8713 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8714 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8715 // `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.
8716 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8717         var coords = [];
8718
8719         for (var i = 0, len = latlngs.length; i < len; i++) {
8720                 coords.push(levelsDeep ?
8721                         latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8722                         latLngToCoords(latlngs[i], precision));
8723         }
8724
8725         if (!levelsDeep && closed) {
8726                 coords.push(coords[0]);
8727         }
8728
8729         return coords;
8730 }
8731
8732 function getFeature(layer, newGeometry) {
8733         return layer.feature ?
8734                 extend({}, layer.feature, {geometry: newGeometry}) :
8735                 asFeature(newGeometry);
8736 }
8737
8738 // @function asFeature(geojson: Object): Object
8739 // Normalize GeoJSON geometries/features into GeoJSON features.
8740 function asFeature(geojson) {
8741         if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8742                 return geojson;
8743         }
8744
8745         return {
8746                 type: 'Feature',
8747                 properties: {},
8748                 geometry: geojson
8749         };
8750 }
8751
8752 var PointToGeoJSON = {
8753         toGeoJSON: function (precision) {
8754                 return getFeature(this, {
8755                         type: 'Point',
8756                         coordinates: latLngToCoords(this.getLatLng(), precision)
8757                 });
8758         }
8759 };
8760
8761 // @namespace Marker
8762 // @method toGeoJSON(): Object
8763 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8764 Marker.include(PointToGeoJSON);
8765
8766 // @namespace CircleMarker
8767 // @method toGeoJSON(): Object
8768 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8769 Circle.include(PointToGeoJSON);
8770 CircleMarker.include(PointToGeoJSON);
8771
8772
8773 // @namespace Polyline
8774 // @method toGeoJSON(): Object
8775 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8776 Polyline.include({
8777         toGeoJSON: function (precision) {
8778                 var multi = !isFlat(this._latlngs);
8779
8780                 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8781
8782                 return getFeature(this, {
8783                         type: (multi ? 'Multi' : '') + 'LineString',
8784                         coordinates: coords
8785                 });
8786         }
8787 });
8788
8789 // @namespace Polygon
8790 // @method toGeoJSON(): Object
8791 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8792 Polygon.include({
8793         toGeoJSON: function (precision) {
8794                 var holes = !isFlat(this._latlngs),
8795                     multi = holes && !isFlat(this._latlngs[0]);
8796
8797                 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8798
8799                 if (!holes) {
8800                         coords = [coords];
8801                 }
8802
8803                 return getFeature(this, {
8804                         type: (multi ? 'Multi' : '') + 'Polygon',
8805                         coordinates: coords
8806                 });
8807         }
8808 });
8809
8810
8811 // @namespace LayerGroup
8812 LayerGroup.include({
8813         toMultiPoint: function (precision) {
8814                 var coords = [];
8815
8816                 this.eachLayer(function (layer) {
8817                         coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8818                 });
8819
8820                 return getFeature(this, {
8821                         type: 'MultiPoint',
8822                         coordinates: coords
8823                 });
8824         },
8825
8826         // @method toGeoJSON(): Object
8827         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8828         toGeoJSON: function (precision) {
8829
8830                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8831
8832                 if (type === 'MultiPoint') {
8833                         return this.toMultiPoint(precision);
8834                 }
8835
8836                 var isGeometryCollection = type === 'GeometryCollection',
8837                     jsons = [];
8838
8839                 this.eachLayer(function (layer) {
8840                         if (layer.toGeoJSON) {
8841                                 var json = layer.toGeoJSON(precision);
8842                                 if (isGeometryCollection) {
8843                                         jsons.push(json.geometry);
8844                                 } else {
8845                                         var feature = asFeature(json);
8846                                         // Squash nested feature collections
8847                                         if (feature.type === 'FeatureCollection') {
8848                                                 jsons.push.apply(jsons, feature.features);
8849                                         } else {
8850                                                 jsons.push(feature);
8851                                         }
8852                                 }
8853                         }
8854                 });
8855
8856                 if (isGeometryCollection) {
8857                         return getFeature(this, {
8858                                 geometries: jsons,
8859                                 type: 'GeometryCollection'
8860                         });
8861                 }
8862
8863                 return {
8864                         type: 'FeatureCollection',
8865                         features: jsons
8866                 };
8867         }
8868 });
8869
8870 // @namespace GeoJSON
8871 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8872 // Creates a GeoJSON layer. Optionally accepts an object in
8873 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
8874 // (you can alternatively add it later with `addData` method) and an `options` object.
8875 function geoJSON(geojson, options) {
8876         return new GeoJSON(geojson, options);
8877 }
8878
8879 // Backward compatibility.
8880 var geoJson = geoJSON;
8881
8882 /*
8883  * @class ImageOverlay
8884  * @aka L.ImageOverlay
8885  * @inherits Interactive layer
8886  *
8887  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8888  *
8889  * @example
8890  *
8891  * ```js
8892  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8893  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8894  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8895  * ```
8896  */
8897
8898 var ImageOverlay = Layer.extend({
8899
8900         // @section
8901         // @aka ImageOverlay options
8902         options: {
8903                 // @option opacity: Number = 1.0
8904                 // The opacity of the image overlay.
8905                 opacity: 1,
8906
8907                 // @option alt: String = ''
8908                 // Text for the `alt` attribute of the image (useful for accessibility).
8909                 alt: '',
8910
8911                 // @option interactive: Boolean = false
8912                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
8913                 interactive: false,
8914
8915                 // @option crossOrigin: Boolean = false
8916                 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
8917                 crossOrigin: false,
8918
8919                 // @option errorOverlayUrl: String = ''
8920                 // URL to the overlay image to show in place of the overlay that failed to load.
8921                 errorOverlayUrl: '',
8922
8923                 // @option zIndex: Number = 1
8924                 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.
8925                 zIndex: 1,
8926
8927                 // @option className: String = ''
8928                 // A custom class name to assign to the image. Empty by default.
8929                 className: '',
8930         },
8931
8932         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
8933                 this._url = url;
8934                 this._bounds = toLatLngBounds(bounds);
8935
8936                 setOptions(this, options);
8937         },
8938
8939         onAdd: function () {
8940                 if (!this._image) {
8941                         this._initImage();
8942
8943                         if (this.options.opacity < 1) {
8944                                 this._updateOpacity();
8945                         }
8946                 }
8947
8948                 if (this.options.interactive) {
8949                         addClass(this._image, 'leaflet-interactive');
8950                         this.addInteractiveTarget(this._image);
8951                 }
8952
8953                 this.getPane().appendChild(this._image);
8954                 this._reset();
8955         },
8956
8957         onRemove: function () {
8958                 remove(this._image);
8959                 if (this.options.interactive) {
8960                         this.removeInteractiveTarget(this._image);
8961                 }
8962         },
8963
8964         // @method setOpacity(opacity: Number): this
8965         // Sets the opacity of the overlay.
8966         setOpacity: function (opacity) {
8967                 this.options.opacity = opacity;
8968
8969                 if (this._image) {
8970                         this._updateOpacity();
8971                 }
8972                 return this;
8973         },
8974
8975         setStyle: function (styleOpts) {
8976                 if (styleOpts.opacity) {
8977                         this.setOpacity(styleOpts.opacity);
8978                 }
8979                 return this;
8980         },
8981
8982         // @method bringToFront(): this
8983         // Brings the layer to the top of all overlays.
8984         bringToFront: function () {
8985                 if (this._map) {
8986                         toFront(this._image);
8987                 }
8988                 return this;
8989         },
8990
8991         // @method bringToBack(): this
8992         // Brings the layer to the bottom of all overlays.
8993         bringToBack: function () {
8994                 if (this._map) {
8995                         toBack(this._image);
8996                 }
8997                 return this;
8998         },
8999
9000         // @method setUrl(url: String): this
9001         // Changes the URL of the image.
9002         setUrl: function (url) {
9003                 this._url = url;
9004
9005                 if (this._image) {
9006                         this._image.src = url;
9007                 }
9008                 return this;
9009         },
9010
9011         // @method setBounds(bounds: LatLngBounds): this
9012         // Update the bounds that this ImageOverlay covers
9013         setBounds: function (bounds) {
9014                 this._bounds = toLatLngBounds(bounds);
9015
9016                 if (this._map) {
9017                         this._reset();
9018                 }
9019                 return this;
9020         },
9021
9022         getEvents: function () {
9023                 var events = {
9024                         zoom: this._reset,
9025                         viewreset: this._reset
9026                 };
9027
9028                 if (this._zoomAnimated) {
9029                         events.zoomanim = this._animateZoom;
9030                 }
9031
9032                 return events;
9033         },
9034
9035         // @method: setZIndex(value: Number) : this
9036         // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9037         setZIndex: function (value) {
9038                 this.options.zIndex = value;
9039                 this._updateZIndex();
9040                 return this;
9041         },
9042
9043         // @method getBounds(): LatLngBounds
9044         // Get the bounds that this ImageOverlay covers
9045         getBounds: function () {
9046                 return this._bounds;
9047         },
9048
9049         // @method getElement(): HTMLElement
9050         // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9051         // used by this overlay.
9052         getElement: function () {
9053                 return this._image;
9054         },
9055
9056         _initImage: function () {
9057                 var wasElementSupplied = this._url.tagName === 'IMG';
9058                 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9059
9060                 addClass(img, 'leaflet-image-layer');
9061                 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9062                 if (this.options.className) { addClass(img, this.options.className); }
9063
9064                 img.onselectstart = falseFn;
9065                 img.onmousemove = falseFn;
9066
9067                 // @event load: Event
9068                 // Fired when the ImageOverlay layer has loaded its image
9069                 img.onload = bind(this.fire, this, 'load');
9070                 img.onerror = bind(this._overlayOnError, this, 'error');
9071
9072                 if (this.options.crossOrigin) {
9073                         img.crossOrigin = '';
9074                 }
9075
9076                 if (this.options.zIndex) {
9077                         this._updateZIndex();
9078                 }
9079
9080                 if (wasElementSupplied) {
9081                         this._url = img.src;
9082                         return;
9083                 }
9084
9085                 img.src = this._url;
9086                 img.alt = this.options.alt;
9087         },
9088
9089         _animateZoom: function (e) {
9090                 var scale = this._map.getZoomScale(e.zoom),
9091                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9092
9093                 setTransform(this._image, offset, scale);
9094         },
9095
9096         _reset: function () {
9097                 var image = this._image,
9098                     bounds = new Bounds(
9099                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9100                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9101                     size = bounds.getSize();
9102
9103                 setPosition(image, bounds.min);
9104
9105                 image.style.width  = size.x + 'px';
9106                 image.style.height = size.y + 'px';
9107         },
9108
9109         _updateOpacity: function () {
9110                 setOpacity(this._image, this.options.opacity);
9111         },
9112
9113         _updateZIndex: function () {
9114                 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9115                         this._image.style.zIndex = this.options.zIndex;
9116                 }
9117         },
9118
9119         _overlayOnError: function () {
9120                 // @event error: Event
9121                 // Fired when the ImageOverlay layer has loaded its image
9122                 this.fire('error');
9123
9124                 var errorUrl = this.options.errorOverlayUrl;
9125                 if (errorUrl && this._url !== errorUrl) {
9126                         this._url = errorUrl;
9127                         this._image.src = errorUrl;
9128                 }
9129         }
9130 });
9131
9132 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9133 // Instantiates an image overlay object given the URL of the image and the
9134 // geographical bounds it is tied to.
9135 var imageOverlay = function (url, bounds, options) {
9136         return new ImageOverlay(url, bounds, options);
9137 };
9138
9139 /*
9140  * @class VideoOverlay
9141  * @aka L.VideoOverlay
9142  * @inherits ImageOverlay
9143  *
9144  * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9145  *
9146  * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9147  * HTML5 element.
9148  *
9149  * @example
9150  *
9151  * ```js
9152  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9153  *      videoBounds = [[ 32, -130], [ 13, -100]];
9154  * L.VideoOverlay(videoUrl, videoBounds ).addTo(map);
9155  * ```
9156  */
9157
9158 var VideoOverlay = ImageOverlay.extend({
9159
9160         // @section
9161         // @aka VideoOverlay options
9162         options: {
9163                 // @option autoplay: Boolean = true
9164                 // Whether the video starts playing automatically when loaded.
9165                 autoplay: true,
9166
9167                 // @option loop: Boolean = true
9168                 // Whether the video will loop back to the beginning when played.
9169                 loop: true
9170         },
9171
9172         _initImage: function () {
9173                 var wasElementSupplied = this._url.tagName === 'VIDEO';
9174                 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9175
9176                 addClass(vid, 'leaflet-image-layer');
9177                 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9178
9179                 vid.onselectstart = falseFn;
9180                 vid.onmousemove = falseFn;
9181
9182                 // @event load: Event
9183                 // Fired when the video has finished loading the first frame
9184                 vid.onloadeddata = bind(this.fire, this, 'load');
9185
9186                 if (wasElementSupplied) {
9187                         var sourceElements = vid.getElementsByTagName('source');
9188                         var sources = [];
9189                         for (var j = 0; j < sourceElements.length; j++) {
9190                                 sources.push(sourceElements[j].src);
9191                         }
9192
9193                         this._url = (sourceElements.length > 0) ? sources : [vid.src];
9194                         return;
9195                 }
9196
9197                 if (!isArray(this._url)) { this._url = [this._url]; }
9198
9199                 vid.autoplay = !!this.options.autoplay;
9200                 vid.loop = !!this.options.loop;
9201                 for (var i = 0; i < this._url.length; i++) {
9202                         var source = create$1('source');
9203                         source.src = this._url[i];
9204                         vid.appendChild(source);
9205                 }
9206         }
9207
9208         // @method getElement(): HTMLVideoElement
9209         // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9210         // used by this overlay.
9211 });
9212
9213
9214 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9215 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9216 // geographical bounds it is tied to.
9217
9218 function videoOverlay(video, bounds, options) {
9219         return new VideoOverlay(video, bounds, options);
9220 }
9221
9222 /*
9223  * @class DivOverlay
9224  * @inherits Layer
9225  * @aka L.DivOverlay
9226  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9227  */
9228
9229 // @namespace DivOverlay
9230 var DivOverlay = Layer.extend({
9231
9232         // @section
9233         // @aka DivOverlay options
9234         options: {
9235                 // @option offset: Point = Point(0, 7)
9236                 // The offset of the popup position. Useful to control the anchor
9237                 // of the popup when opening it on some overlays.
9238                 offset: [0, 7],
9239
9240                 // @option className: String = ''
9241                 // A custom CSS class name to assign to the popup.
9242                 className: '',
9243
9244                 // @option pane: String = 'popupPane'
9245                 // `Map pane` where the popup will be added.
9246                 pane: 'popupPane'
9247         },
9248
9249         initialize: function (options, source) {
9250                 setOptions(this, options);
9251
9252                 this._source = source;
9253         },
9254
9255         onAdd: function (map) {
9256                 this._zoomAnimated = map._zoomAnimated;
9257
9258                 if (!this._container) {
9259                         this._initLayout();
9260                 }
9261
9262                 if (map._fadeAnimated) {
9263                         setOpacity(this._container, 0);
9264                 }
9265
9266                 clearTimeout(this._removeTimeout);
9267                 this.getPane().appendChild(this._container);
9268                 this.update();
9269
9270                 if (map._fadeAnimated) {
9271                         setOpacity(this._container, 1);
9272                 }
9273
9274                 this.bringToFront();
9275         },
9276
9277         onRemove: function (map) {
9278                 if (map._fadeAnimated) {
9279                         setOpacity(this._container, 0);
9280                         this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9281                 } else {
9282                         remove(this._container);
9283                 }
9284         },
9285
9286         // @namespace Popup
9287         // @method getLatLng: LatLng
9288         // Returns the geographical point of popup.
9289         getLatLng: function () {
9290                 return this._latlng;
9291         },
9292
9293         // @method setLatLng(latlng: LatLng): this
9294         // Sets the geographical point where the popup will open.
9295         setLatLng: function (latlng) {
9296                 this._latlng = toLatLng(latlng);
9297                 if (this._map) {
9298                         this._updatePosition();
9299                         this._adjustPan();
9300                 }
9301                 return this;
9302         },
9303
9304         // @method getContent: String|HTMLElement
9305         // Returns the content of the popup.
9306         getContent: function () {
9307                 return this._content;
9308         },
9309
9310         // @method setContent(htmlContent: String|HTMLElement|Function): this
9311         // 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.
9312         setContent: function (content) {
9313                 this._content = content;
9314                 this.update();
9315                 return this;
9316         },
9317
9318         // @method getElement: String|HTMLElement
9319         // Alias for [getContent()](#popup-getcontent)
9320         getElement: function () {
9321                 return this._container;
9322         },
9323
9324         // @method update: null
9325         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9326         update: function () {
9327                 if (!this._map) { return; }
9328
9329                 this._container.style.visibility = 'hidden';
9330
9331                 this._updateContent();
9332                 this._updateLayout();
9333                 this._updatePosition();
9334
9335                 this._container.style.visibility = '';
9336
9337                 this._adjustPan();
9338         },
9339
9340         getEvents: function () {
9341                 var events = {
9342                         zoom: this._updatePosition,
9343                         viewreset: this._updatePosition
9344                 };
9345
9346                 if (this._zoomAnimated) {
9347                         events.zoomanim = this._animateZoom;
9348                 }
9349                 return events;
9350         },
9351
9352         // @method isOpen: Boolean
9353         // Returns `true` when the popup is visible on the map.
9354         isOpen: function () {
9355                 return !!this._map && this._map.hasLayer(this);
9356         },
9357
9358         // @method bringToFront: this
9359         // Brings this popup in front of other popups (in the same map pane).
9360         bringToFront: function () {
9361                 if (this._map) {
9362                         toFront(this._container);
9363                 }
9364                 return this;
9365         },
9366
9367         // @method bringToBack: this
9368         // Brings this popup to the back of other popups (in the same map pane).
9369         bringToBack: function () {
9370                 if (this._map) {
9371                         toBack(this._container);
9372                 }
9373                 return this;
9374         },
9375
9376         _updateContent: function () {
9377                 if (!this._content) { return; }
9378
9379                 var node = this._contentNode;
9380                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9381
9382                 if (typeof content === 'string') {
9383                         node.innerHTML = content;
9384                 } else {
9385                         while (node.hasChildNodes()) {
9386                                 node.removeChild(node.firstChild);
9387                         }
9388                         node.appendChild(content);
9389                 }
9390                 this.fire('contentupdate');
9391         },
9392
9393         _updatePosition: function () {
9394                 if (!this._map) { return; }
9395
9396                 var pos = this._map.latLngToLayerPoint(this._latlng),
9397                     offset = toPoint(this.options.offset),
9398                     anchor = this._getAnchor();
9399
9400                 if (this._zoomAnimated) {
9401                         setPosition(this._container, pos.add(anchor));
9402                 } else {
9403                         offset = offset.add(pos).add(anchor);
9404                 }
9405
9406                 var bottom = this._containerBottom = -offset.y,
9407                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9408
9409                 // bottom position the popup in case the height of the popup changes (images loading etc)
9410                 this._container.style.bottom = bottom + 'px';
9411                 this._container.style.left = left + 'px';
9412         },
9413
9414         _getAnchor: function () {
9415                 return [0, 0];
9416         }
9417
9418 });
9419
9420 /*
9421  * @class Popup
9422  * @inherits DivOverlay
9423  * @aka L.Popup
9424  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9425  * open popups while making sure that only one popup is open at one time
9426  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9427  *
9428  * @example
9429  *
9430  * If you want to just bind a popup to marker click and then open it, it's really easy:
9431  *
9432  * ```js
9433  * marker.bindPopup(popupContent).openPopup();
9434  * ```
9435  * Path overlays like polylines also have a `bindPopup` method.
9436  * Here's a more complicated way to open a popup on a map:
9437  *
9438  * ```js
9439  * var popup = L.popup()
9440  *      .setLatLng(latlng)
9441  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9442  *      .openOn(map);
9443  * ```
9444  */
9445
9446
9447 // @namespace Popup
9448 var Popup = DivOverlay.extend({
9449
9450         // @section
9451         // @aka Popup options
9452         options: {
9453                 // @option maxWidth: Number = 300
9454                 // Max width of the popup, in pixels.
9455                 maxWidth: 300,
9456
9457                 // @option minWidth: Number = 50
9458                 // Min width of the popup, in pixels.
9459                 minWidth: 50,
9460
9461                 // @option maxHeight: Number = null
9462                 // If set, creates a scrollable container of the given height
9463                 // inside a popup if its content exceeds it.
9464                 maxHeight: null,
9465
9466                 // @option autoPan: Boolean = true
9467                 // Set it to `false` if you don't want the map to do panning animation
9468                 // to fit the opened popup.
9469                 autoPan: true,
9470
9471                 // @option autoPanPaddingTopLeft: Point = null
9472                 // The margin between the popup and the top left corner of the map
9473                 // view after autopanning was performed.
9474                 autoPanPaddingTopLeft: null,
9475
9476                 // @option autoPanPaddingBottomRight: Point = null
9477                 // The margin between the popup and the bottom right corner of the map
9478                 // view after autopanning was performed.
9479                 autoPanPaddingBottomRight: null,
9480
9481                 // @option autoPanPadding: Point = Point(5, 5)
9482                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9483                 autoPanPadding: [5, 5],
9484
9485                 // @option keepInView: Boolean = false
9486                 // Set it to `true` if you want to prevent users from panning the popup
9487                 // off of the screen while it is open.
9488                 keepInView: false,
9489
9490                 // @option closeButton: Boolean = true
9491                 // Controls the presence of a close button in the popup.
9492                 closeButton: true,
9493
9494                 // @option autoClose: Boolean = true
9495                 // Set it to `false` if you want to override the default behavior of
9496                 // the popup closing when another popup is opened.
9497                 autoClose: true,
9498
9499                 // @option closeOnEscapeKey: Boolean = true
9500                 // Set it to `false` if you want to override the default behavior of
9501                 // the ESC key for closing of the popup.
9502                 closeOnEscapeKey: true,
9503
9504                 // @option closeOnClick: Boolean = *
9505                 // Set it if you want to override the default behavior of the popup closing when user clicks
9506                 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9507
9508                 // @option className: String = ''
9509                 // A custom CSS class name to assign to the popup.
9510                 className: ''
9511         },
9512
9513         // @namespace Popup
9514         // @method openOn(map: Map): this
9515         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9516         openOn: function (map) {
9517                 map.openPopup(this);
9518                 return this;
9519         },
9520
9521         onAdd: function (map) {
9522                 DivOverlay.prototype.onAdd.call(this, map);
9523
9524                 // @namespace Map
9525                 // @section Popup events
9526                 // @event popupopen: PopupEvent
9527                 // Fired when a popup is opened in the map
9528                 map.fire('popupopen', {popup: this});
9529
9530                 if (this._source) {
9531                         // @namespace Layer
9532                         // @section Popup events
9533                         // @event popupopen: PopupEvent
9534                         // Fired when a popup bound to this layer is opened
9535                         this._source.fire('popupopen', {popup: this}, true);
9536                         // For non-path layers, we toggle the popup when clicking
9537                         // again the layer, so prevent the map to reopen it.
9538                         if (!(this._source instanceof Path)) {
9539                                 this._source.on('preclick', stopPropagation);
9540                         }
9541                 }
9542         },
9543
9544         onRemove: function (map) {
9545                 DivOverlay.prototype.onRemove.call(this, map);
9546
9547                 // @namespace Map
9548                 // @section Popup events
9549                 // @event popupclose: PopupEvent
9550                 // Fired when a popup in the map is closed
9551                 map.fire('popupclose', {popup: this});
9552
9553                 if (this._source) {
9554                         // @namespace Layer
9555                         // @section Popup events
9556                         // @event popupclose: PopupEvent
9557                         // Fired when a popup bound to this layer is closed
9558                         this._source.fire('popupclose', {popup: this}, true);
9559                         if (!(this._source instanceof Path)) {
9560                                 this._source.off('preclick', stopPropagation);
9561                         }
9562                 }
9563         },
9564
9565         getEvents: function () {
9566                 var events = DivOverlay.prototype.getEvents.call(this);
9567
9568                 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9569                         events.preclick = this._close;
9570                 }
9571
9572                 if (this.options.keepInView) {
9573                         events.moveend = this._adjustPan;
9574                 }
9575
9576                 return events;
9577         },
9578
9579         _close: function () {
9580                 if (this._map) {
9581                         this._map.closePopup(this);
9582                 }
9583         },
9584
9585         _initLayout: function () {
9586                 var prefix = 'leaflet-popup',
9587                     container = this._container = create$1('div',
9588                         prefix + ' ' + (this.options.className || '') +
9589                         ' leaflet-zoom-animated');
9590
9591                 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9592                 this._contentNode = create$1('div', prefix + '-content', wrapper);
9593
9594                 disableClickPropagation(wrapper);
9595                 disableScrollPropagation(this._contentNode);
9596                 on(wrapper, 'contextmenu', stopPropagation);
9597
9598                 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9599                 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9600
9601                 if (this.options.closeButton) {
9602                         var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9603                         closeButton.href = '#close';
9604                         closeButton.innerHTML = '&#215;';
9605
9606                         on(closeButton, 'click', this._onCloseButtonClick, this);
9607                 }
9608         },
9609
9610         _updateLayout: function () {
9611                 var container = this._contentNode,
9612                     style = container.style;
9613
9614                 style.width = '';
9615                 style.whiteSpace = 'nowrap';
9616
9617                 var width = container.offsetWidth;
9618                 width = Math.min(width, this.options.maxWidth);
9619                 width = Math.max(width, this.options.minWidth);
9620
9621                 style.width = (width + 1) + 'px';
9622                 style.whiteSpace = '';
9623
9624                 style.height = '';
9625
9626                 var height = container.offsetHeight,
9627                     maxHeight = this.options.maxHeight,
9628                     scrolledClass = 'leaflet-popup-scrolled';
9629
9630                 if (maxHeight && height > maxHeight) {
9631                         style.height = maxHeight + 'px';
9632                         addClass(container, scrolledClass);
9633                 } else {
9634                         removeClass(container, scrolledClass);
9635                 }
9636
9637                 this._containerWidth = this._container.offsetWidth;
9638         },
9639
9640         _animateZoom: function (e) {
9641                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9642                     anchor = this._getAnchor();
9643                 setPosition(this._container, pos.add(anchor));
9644         },
9645
9646         _adjustPan: function () {
9647                 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
9648
9649                 var map = this._map,
9650                     marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9651                     containerHeight = this._container.offsetHeight + marginBottom,
9652                     containerWidth = this._containerWidth,
9653                     layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9654
9655                 layerPos._add(getPosition(this._container));
9656
9657                 var containerPos = map.layerPointToContainerPoint(layerPos),
9658                     padding = toPoint(this.options.autoPanPadding),
9659                     paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9660                     paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9661                     size = map.getSize(),
9662                     dx = 0,
9663                     dy = 0;
9664
9665                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9666                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9667                 }
9668                 if (containerPos.x - dx - paddingTL.x < 0) { // left
9669                         dx = containerPos.x - paddingTL.x;
9670                 }
9671                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9672                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9673                 }
9674                 if (containerPos.y - dy - paddingTL.y < 0) { // top
9675                         dy = containerPos.y - paddingTL.y;
9676                 }
9677
9678                 // @namespace Map
9679                 // @section Popup events
9680                 // @event autopanstart: Event
9681                 // Fired when the map starts autopanning when opening a popup.
9682                 if (dx || dy) {
9683                         map
9684                             .fire('autopanstart')
9685                             .panBy([dx, dy]);
9686                 }
9687         },
9688
9689         _onCloseButtonClick: function (e) {
9690                 this._close();
9691                 stop(e);
9692         },
9693
9694         _getAnchor: function () {
9695                 // Where should we anchor the popup on the source layer?
9696                 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9697         }
9698
9699 });
9700
9701 // @namespace Popup
9702 // @factory L.popup(options?: Popup options, source?: Layer)
9703 // 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.
9704 var popup = function (options, source) {
9705         return new Popup(options, source);
9706 };
9707
9708
9709 /* @namespace Map
9710  * @section Interaction Options
9711  * @option closePopupOnClick: Boolean = true
9712  * Set it to `false` if you don't want popups to close when user clicks the map.
9713  */
9714 Map.mergeOptions({
9715         closePopupOnClick: true
9716 });
9717
9718
9719 // @namespace Map
9720 // @section Methods for Layers and Controls
9721 Map.include({
9722         // @method openPopup(popup: Popup): this
9723         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9724         // @alternative
9725         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9726         // Creates a popup with the specified content and options and opens it in the given point on a map.
9727         openPopup: function (popup, latlng, options) {
9728                 if (!(popup instanceof Popup)) {
9729                         popup = new Popup(options).setContent(popup);
9730                 }
9731
9732                 if (latlng) {
9733                         popup.setLatLng(latlng);
9734                 }
9735
9736                 if (this.hasLayer(popup)) {
9737                         return this;
9738                 }
9739
9740                 if (this._popup && this._popup.options.autoClose) {
9741                         this.closePopup();
9742                 }
9743
9744                 this._popup = popup;
9745                 return this.addLayer(popup);
9746         },
9747
9748         // @method closePopup(popup?: Popup): this
9749         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9750         closePopup: function (popup) {
9751                 if (!popup || popup === this._popup) {
9752                         popup = this._popup;
9753                         this._popup = null;
9754                 }
9755                 if (popup) {
9756                         this.removeLayer(popup);
9757                 }
9758                 return this;
9759         }
9760 });
9761
9762 /*
9763  * @namespace Layer
9764  * @section Popup methods example
9765  *
9766  * All layers share a set of methods convenient for binding popups to it.
9767  *
9768  * ```js
9769  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9770  * layer.openPopup();
9771  * layer.closePopup();
9772  * ```
9773  *
9774  * 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.
9775  */
9776
9777 // @section Popup methods
9778 Layer.include({
9779
9780         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9781         // Binds a popup to the layer with the passed `content` and sets up the
9782         // necessary event listeners. If a `Function` is passed it will receive
9783         // the layer as the first argument and should return a `String` or `HTMLElement`.
9784         bindPopup: function (content, options) {
9785
9786                 if (content instanceof Popup) {
9787                         setOptions(content, options);
9788                         this._popup = content;
9789                         content._source = this;
9790                 } else {
9791                         if (!this._popup || options) {
9792                                 this._popup = new Popup(options, this);
9793                         }
9794                         this._popup.setContent(content);
9795                 }
9796
9797                 if (!this._popupHandlersAdded) {
9798                         this.on({
9799                                 click: this._openPopup,
9800                                 keypress: this._onKeyPress,
9801                                 remove: this.closePopup,
9802                                 move: this._movePopup
9803                         });
9804                         this._popupHandlersAdded = true;
9805                 }
9806
9807                 return this;
9808         },
9809
9810         // @method unbindPopup(): this
9811         // Removes the popup previously bound with `bindPopup`.
9812         unbindPopup: function () {
9813                 if (this._popup) {
9814                         this.off({
9815                                 click: this._openPopup,
9816                                 keypress: this._onKeyPress,
9817                                 remove: this.closePopup,
9818                                 move: this._movePopup
9819                         });
9820                         this._popupHandlersAdded = false;
9821                         this._popup = null;
9822                 }
9823                 return this;
9824         },
9825
9826         // @method openPopup(latlng?: LatLng): this
9827         // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
9828         openPopup: function (layer, latlng) {
9829                 if (!(layer instanceof Layer)) {
9830                         latlng = layer;
9831                         layer = this;
9832                 }
9833
9834                 if (layer instanceof FeatureGroup) {
9835                         for (var id in this._layers) {
9836                                 layer = this._layers[id];
9837                                 break;
9838                         }
9839                 }
9840
9841                 if (!latlng) {
9842                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9843                 }
9844
9845                 if (this._popup && this._map) {
9846                         // set popup source to this layer
9847                         this._popup._source = layer;
9848
9849                         // update the popup (content, layout, ect...)
9850                         this._popup.update();
9851
9852                         // open the popup on the map
9853                         this._map.openPopup(this._popup, latlng);
9854                 }
9855
9856                 return this;
9857         },
9858
9859         // @method closePopup(): this
9860         // Closes the popup bound to this layer if it is open.
9861         closePopup: function () {
9862                 if (this._popup) {
9863                         this._popup._close();
9864                 }
9865                 return this;
9866         },
9867
9868         // @method togglePopup(): this
9869         // Opens or closes the popup bound to this layer depending on its current state.
9870         togglePopup: function (target) {
9871                 if (this._popup) {
9872                         if (this._popup._map) {
9873                                 this.closePopup();
9874                         } else {
9875                                 this.openPopup(target);
9876                         }
9877                 }
9878                 return this;
9879         },
9880
9881         // @method isPopupOpen(): boolean
9882         // Returns `true` if the popup bound to this layer is currently open.
9883         isPopupOpen: function () {
9884                 return (this._popup ? this._popup.isOpen() : false);
9885         },
9886
9887         // @method setPopupContent(content: String|HTMLElement|Popup): this
9888         // Sets the content of the popup bound to this layer.
9889         setPopupContent: function (content) {
9890                 if (this._popup) {
9891                         this._popup.setContent(content);
9892                 }
9893                 return this;
9894         },
9895
9896         // @method getPopup(): Popup
9897         // Returns the popup bound to this layer.
9898         getPopup: function () {
9899                 return this._popup;
9900         },
9901
9902         _openPopup: function (e) {
9903                 var layer = e.layer || e.target;
9904
9905                 if (!this._popup) {
9906                         return;
9907                 }
9908
9909                 if (!this._map) {
9910                         return;
9911                 }
9912
9913                 // prevent map click
9914                 stop(e);
9915
9916                 // if this inherits from Path its a vector and we can just
9917                 // open the popup at the new location
9918                 if (layer instanceof Path) {
9919                         this.openPopup(e.layer || e.target, e.latlng);
9920                         return;
9921                 }
9922
9923                 // otherwise treat it like a marker and figure out
9924                 // if we should toggle it open/closed
9925                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
9926                         this.closePopup();
9927                 } else {
9928                         this.openPopup(layer, e.latlng);
9929                 }
9930         },
9931
9932         _movePopup: function (e) {
9933                 this._popup.setLatLng(e.latlng);
9934         },
9935
9936         _onKeyPress: function (e) {
9937                 if (e.originalEvent.keyCode === 13) {
9938                         this._openPopup(e);
9939                 }
9940         }
9941 });
9942
9943 /*
9944  * @class Tooltip
9945  * @inherits DivOverlay
9946  * @aka L.Tooltip
9947  * Used to display small texts on top of map layers.
9948  *
9949  * @example
9950  *
9951  * ```js
9952  * marker.bindTooltip("my tooltip text").openTooltip();
9953  * ```
9954  * Note about tooltip offset. Leaflet takes two options in consideration
9955  * for computing tooltip offsetting:
9956  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
9957  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
9958  *   move it to the bottom. Negatives will move to the left and top.
9959  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
9960  *   should adapt this value if you use a custom icon.
9961  */
9962
9963
9964 // @namespace Tooltip
9965 var Tooltip = DivOverlay.extend({
9966
9967         // @section
9968         // @aka Tooltip options
9969         options: {
9970                 // @option pane: String = 'tooltipPane'
9971                 // `Map pane` where the tooltip will be added.
9972                 pane: 'tooltipPane',
9973
9974                 // @option offset: Point = Point(0, 0)
9975                 // Optional offset of the tooltip position.
9976                 offset: [0, 0],
9977
9978                 // @option direction: String = 'auto'
9979                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
9980                 // `top`, `bottom`, `center`, `auto`.
9981                 // `auto` will dynamically switch between `right` and `left` according to the tooltip
9982                 // position on the map.
9983                 direction: 'auto',
9984
9985                 // @option permanent: Boolean = false
9986                 // Whether to open the tooltip permanently or only on mouseover.
9987                 permanent: false,
9988
9989                 // @option sticky: Boolean = false
9990                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
9991                 sticky: false,
9992
9993                 // @option interactive: Boolean = false
9994                 // If true, the tooltip will listen to the feature events.
9995                 interactive: false,
9996
9997                 // @option opacity: Number = 0.9
9998                 // Tooltip container opacity.
9999                 opacity: 0.9
10000         },
10001
10002         onAdd: function (map) {
10003                 DivOverlay.prototype.onAdd.call(this, map);
10004                 this.setOpacity(this.options.opacity);
10005
10006                 // @namespace Map
10007                 // @section Tooltip events
10008                 // @event tooltipopen: TooltipEvent
10009                 // Fired when a tooltip is opened in the map.
10010                 map.fire('tooltipopen', {tooltip: this});
10011
10012                 if (this._source) {
10013                         // @namespace Layer
10014                         // @section Tooltip events
10015                         // @event tooltipopen: TooltipEvent
10016                         // Fired when a tooltip bound to this layer is opened.
10017                         this._source.fire('tooltipopen', {tooltip: this}, true);
10018                 }
10019         },
10020
10021         onRemove: function (map) {
10022                 DivOverlay.prototype.onRemove.call(this, map);
10023
10024                 // @namespace Map
10025                 // @section Tooltip events
10026                 // @event tooltipclose: TooltipEvent
10027                 // Fired when a tooltip in the map is closed.
10028                 map.fire('tooltipclose', {tooltip: this});
10029
10030                 if (this._source) {
10031                         // @namespace Layer
10032                         // @section Tooltip events
10033                         // @event tooltipclose: TooltipEvent
10034                         // Fired when a tooltip bound to this layer is closed.
10035                         this._source.fire('tooltipclose', {tooltip: this}, true);
10036                 }
10037         },
10038
10039         getEvents: function () {
10040                 var events = DivOverlay.prototype.getEvents.call(this);
10041
10042                 if (touch && !this.options.permanent) {
10043                         events.preclick = this._close;
10044                 }
10045
10046                 return events;
10047         },
10048
10049         _close: function () {
10050                 if (this._map) {
10051                         this._map.closeTooltip(this);
10052                 }
10053         },
10054
10055         _initLayout: function () {
10056                 var prefix = 'leaflet-tooltip',
10057                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10058
10059                 this._contentNode = this._container = create$1('div', className);
10060         },
10061
10062         _updateLayout: function () {},
10063
10064         _adjustPan: function () {},
10065
10066         _setPosition: function (pos) {
10067                 var map = this._map,
10068                     container = this._container,
10069                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
10070                     tooltipPoint = map.layerPointToContainerPoint(pos),
10071                     direction = this.options.direction,
10072                     tooltipWidth = container.offsetWidth,
10073                     tooltipHeight = container.offsetHeight,
10074                     offset = toPoint(this.options.offset),
10075                     anchor = this._getAnchor();
10076
10077                 if (direction === 'top') {
10078                         pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10079                 } else if (direction === 'bottom') {
10080                         pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10081                 } else if (direction === 'center') {
10082                         pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10083                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10084                         direction = 'right';
10085                         pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10086                 } else {
10087                         direction = 'left';
10088                         pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10089                 }
10090
10091                 removeClass(container, 'leaflet-tooltip-right');
10092                 removeClass(container, 'leaflet-tooltip-left');
10093                 removeClass(container, 'leaflet-tooltip-top');
10094                 removeClass(container, 'leaflet-tooltip-bottom');
10095                 addClass(container, 'leaflet-tooltip-' + direction);
10096                 setPosition(container, pos);
10097         },
10098
10099         _updatePosition: function () {
10100                 var pos = this._map.latLngToLayerPoint(this._latlng);
10101                 this._setPosition(pos);
10102         },
10103
10104         setOpacity: function (opacity) {
10105                 this.options.opacity = opacity;
10106
10107                 if (this._container) {
10108                         setOpacity(this._container, opacity);
10109                 }
10110         },
10111
10112         _animateZoom: function (e) {
10113                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10114                 this._setPosition(pos);
10115         },
10116
10117         _getAnchor: function () {
10118                 // Where should we anchor the tooltip on the source layer?
10119                 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10120         }
10121
10122 });
10123
10124 // @namespace Tooltip
10125 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10126 // 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.
10127 var tooltip = function (options, source) {
10128         return new Tooltip(options, source);
10129 };
10130
10131 // @namespace Map
10132 // @section Methods for Layers and Controls
10133 Map.include({
10134
10135         // @method openTooltip(tooltip: Tooltip): this
10136         // Opens the specified tooltip.
10137         // @alternative
10138         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10139         // Creates a tooltip with the specified content and options and open it.
10140         openTooltip: function (tooltip, latlng, options) {
10141                 if (!(tooltip instanceof Tooltip)) {
10142                         tooltip = new Tooltip(options).setContent(tooltip);
10143                 }
10144
10145                 if (latlng) {
10146                         tooltip.setLatLng(latlng);
10147                 }
10148
10149                 if (this.hasLayer(tooltip)) {
10150                         return this;
10151                 }
10152
10153                 return this.addLayer(tooltip);
10154         },
10155
10156         // @method closeTooltip(tooltip?: Tooltip): this
10157         // Closes the tooltip given as parameter.
10158         closeTooltip: function (tooltip) {
10159                 if (tooltip) {
10160                         this.removeLayer(tooltip);
10161                 }
10162                 return this;
10163         }
10164
10165 });
10166
10167 /*
10168  * @namespace Layer
10169  * @section Tooltip methods example
10170  *
10171  * All layers share a set of methods convenient for binding tooltips to it.
10172  *
10173  * ```js
10174  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10175  * layer.openTooltip();
10176  * layer.closeTooltip();
10177  * ```
10178  */
10179
10180 // @section Tooltip methods
10181 Layer.include({
10182
10183         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10184         // Binds a tooltip to the layer with the passed `content` and sets up the
10185         // necessary event listeners. If a `Function` is passed it will receive
10186         // the layer as the first argument and should return a `String` or `HTMLElement`.
10187         bindTooltip: function (content, options) {
10188
10189                 if (content instanceof Tooltip) {
10190                         setOptions(content, options);
10191                         this._tooltip = content;
10192                         content._source = this;
10193                 } else {
10194                         if (!this._tooltip || options) {
10195                                 this._tooltip = new Tooltip(options, this);
10196                         }
10197                         this._tooltip.setContent(content);
10198
10199                 }
10200
10201                 this._initTooltipInteractions();
10202
10203                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10204                         this.openTooltip();
10205                 }
10206
10207                 return this;
10208         },
10209
10210         // @method unbindTooltip(): this
10211         // Removes the tooltip previously bound with `bindTooltip`.
10212         unbindTooltip: function () {
10213                 if (this._tooltip) {
10214                         this._initTooltipInteractions(true);
10215                         this.closeTooltip();
10216                         this._tooltip = null;
10217                 }
10218                 return this;
10219         },
10220
10221         _initTooltipInteractions: function (remove$$1) {
10222                 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10223                 var onOff = remove$$1 ? 'off' : 'on',
10224                     events = {
10225                         remove: this.closeTooltip,
10226                         move: this._moveTooltip
10227                     };
10228                 if (!this._tooltip.options.permanent) {
10229                         events.mouseover = this._openTooltip;
10230                         events.mouseout = this.closeTooltip;
10231                         if (this._tooltip.options.sticky) {
10232                                 events.mousemove = this._moveTooltip;
10233                         }
10234                         if (touch) {
10235                                 events.click = this._openTooltip;
10236                         }
10237                 } else {
10238                         events.add = this._openTooltip;
10239                 }
10240                 this[onOff](events);
10241                 this._tooltipHandlersAdded = !remove$$1;
10242         },
10243
10244         // @method openTooltip(latlng?: LatLng): this
10245         // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10246         openTooltip: function (layer, latlng) {
10247                 if (!(layer instanceof Layer)) {
10248                         latlng = layer;
10249                         layer = this;
10250                 }
10251
10252                 if (layer instanceof FeatureGroup) {
10253                         for (var id in this._layers) {
10254                                 layer = this._layers[id];
10255                                 break;
10256                         }
10257                 }
10258
10259                 if (!latlng) {
10260                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10261                 }
10262
10263                 if (this._tooltip && this._map) {
10264
10265                         // set tooltip source to this layer
10266                         this._tooltip._source = layer;
10267
10268                         // update the tooltip (content, layout, ect...)
10269                         this._tooltip.update();
10270
10271                         // open the tooltip on the map
10272                         this._map.openTooltip(this._tooltip, latlng);
10273
10274                         // Tooltip container may not be defined if not permanent and never
10275                         // opened.
10276                         if (this._tooltip.options.interactive && this._tooltip._container) {
10277                                 addClass(this._tooltip._container, 'leaflet-clickable');
10278                                 this.addInteractiveTarget(this._tooltip._container);
10279                         }
10280                 }
10281
10282                 return this;
10283         },
10284
10285         // @method closeTooltip(): this
10286         // Closes the tooltip bound to this layer if it is open.
10287         closeTooltip: function () {
10288                 if (this._tooltip) {
10289                         this._tooltip._close();
10290                         if (this._tooltip.options.interactive && this._tooltip._container) {
10291                                 removeClass(this._tooltip._container, 'leaflet-clickable');
10292                                 this.removeInteractiveTarget(this._tooltip._container);
10293                         }
10294                 }
10295                 return this;
10296         },
10297
10298         // @method toggleTooltip(): this
10299         // Opens or closes the tooltip bound to this layer depending on its current state.
10300         toggleTooltip: function (target) {
10301                 if (this._tooltip) {
10302                         if (this._tooltip._map) {
10303                                 this.closeTooltip();
10304                         } else {
10305                                 this.openTooltip(target);
10306                         }
10307                 }
10308                 return this;
10309         },
10310
10311         // @method isTooltipOpen(): boolean
10312         // Returns `true` if the tooltip bound to this layer is currently open.
10313         isTooltipOpen: function () {
10314                 return this._tooltip.isOpen();
10315         },
10316
10317         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10318         // Sets the content of the tooltip bound to this layer.
10319         setTooltipContent: function (content) {
10320                 if (this._tooltip) {
10321                         this._tooltip.setContent(content);
10322                 }
10323                 return this;
10324         },
10325
10326         // @method getTooltip(): Tooltip
10327         // Returns the tooltip bound to this layer.
10328         getTooltip: function () {
10329                 return this._tooltip;
10330         },
10331
10332         _openTooltip: function (e) {
10333                 var layer = e.layer || e.target;
10334
10335                 if (!this._tooltip || !this._map) {
10336                         return;
10337                 }
10338                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10339         },
10340
10341         _moveTooltip: function (e) {
10342                 var latlng = e.latlng, containerPoint, layerPoint;
10343                 if (this._tooltip.options.sticky && e.originalEvent) {
10344                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10345                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10346                         latlng = this._map.layerPointToLatLng(layerPoint);
10347                 }
10348                 this._tooltip.setLatLng(latlng);
10349         }
10350 });
10351
10352 /*
10353  * @class DivIcon
10354  * @aka L.DivIcon
10355  * @inherits Icon
10356  *
10357  * Represents a lightweight icon for markers that uses a simple `<div>`
10358  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10359  *
10360  * @example
10361  * ```js
10362  * var myIcon = L.divIcon({className: 'my-div-icon'});
10363  * // you can set .my-div-icon styles in CSS
10364  *
10365  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10366  * ```
10367  *
10368  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10369  */
10370
10371 var DivIcon = Icon.extend({
10372         options: {
10373                 // @section
10374                 // @aka DivIcon options
10375                 iconSize: [12, 12], // also can be set through CSS
10376
10377                 // iconAnchor: (Point),
10378                 // popupAnchor: (Point),
10379
10380                 // @option html: String = ''
10381                 // Custom HTML code to put inside the div element, empty by default.
10382                 html: false,
10383
10384                 // @option bgPos: Point = [0, 0]
10385                 // Optional relative position of the background, in pixels
10386                 bgPos: null,
10387
10388                 className: 'leaflet-div-icon'
10389         },
10390
10391         createIcon: function (oldIcon) {
10392                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10393                     options = this.options;
10394
10395                 div.innerHTML = options.html !== false ? options.html : '';
10396
10397                 if (options.bgPos) {
10398                         var bgPos = toPoint(options.bgPos);
10399                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10400                 }
10401                 this._setIconStyles(div, 'icon');
10402
10403                 return div;
10404         },
10405
10406         createShadow: function () {
10407                 return null;
10408         }
10409 });
10410
10411 // @factory L.divIcon(options: DivIcon options)
10412 // Creates a `DivIcon` instance with the given options.
10413 function divIcon(options) {
10414         return new DivIcon(options);
10415 }
10416
10417 Icon.Default = IconDefault;
10418
10419 /*
10420  * @class GridLayer
10421  * @inherits Layer
10422  * @aka L.GridLayer
10423  *
10424  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10425  * 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.
10426  *
10427  *
10428  * @section Synchronous usage
10429  * @example
10430  *
10431  * 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.
10432  *
10433  * ```js
10434  * var CanvasLayer = L.GridLayer.extend({
10435  *     createTile: function(coords){
10436  *         // create a <canvas> element for drawing
10437  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10438  *
10439  *         // setup tile width and height according to the options
10440  *         var size = this.getTileSize();
10441  *         tile.width = size.x;
10442  *         tile.height = size.y;
10443  *
10444  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10445  *         var ctx = tile.getContext('2d');
10446  *
10447  *         // return the tile so it can be rendered on screen
10448  *         return tile;
10449  *     }
10450  * });
10451  * ```
10452  *
10453  * @section Asynchronous usage
10454  * @example
10455  *
10456  * 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.
10457  *
10458  * ```js
10459  * var CanvasLayer = L.GridLayer.extend({
10460  *     createTile: function(coords, done){
10461  *         var error;
10462  *
10463  *         // create a <canvas> element for drawing
10464  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10465  *
10466  *         // setup tile width and height according to the options
10467  *         var size = this.getTileSize();
10468  *         tile.width = size.x;
10469  *         tile.height = size.y;
10470  *
10471  *         // draw something asynchronously and pass the tile to the done() callback
10472  *         setTimeout(function() {
10473  *             done(error, tile);
10474  *         }, 1000);
10475  *
10476  *         return tile;
10477  *     }
10478  * });
10479  * ```
10480  *
10481  * @section
10482  */
10483
10484
10485 var GridLayer = Layer.extend({
10486
10487         // @section
10488         // @aka GridLayer options
10489         options: {
10490                 // @option tileSize: Number|Point = 256
10491                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10492                 tileSize: 256,
10493
10494                 // @option opacity: Number = 1.0
10495                 // Opacity of the tiles. Can be used in the `createTile()` function.
10496                 opacity: 1,
10497
10498                 // @option updateWhenIdle: Boolean = (depends)
10499                 // Load new tiles only when panning ends.
10500                 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10501                 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10502                 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10503                 updateWhenIdle: mobile,
10504
10505                 // @option updateWhenZooming: Boolean = true
10506                 // 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.
10507                 updateWhenZooming: true,
10508
10509                 // @option updateInterval: Number = 200
10510                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10511                 updateInterval: 200,
10512
10513                 // @option zIndex: Number = 1
10514                 // The explicit zIndex of the tile layer.
10515                 zIndex: 1,
10516
10517                 // @option bounds: LatLngBounds = undefined
10518                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10519                 bounds: null,
10520
10521                 // @option minZoom: Number = 0
10522                 // The minimum zoom level down to which this layer will be displayed (inclusive).
10523                 minZoom: 0,
10524
10525                 // @option maxZoom: Number = undefined
10526                 // The maximum zoom level up to which this layer will be displayed (inclusive).
10527                 maxZoom: undefined,
10528
10529                 // @option maxNativeZoom: Number = undefined
10530                 // Maximum zoom number the tile source has available. If it is specified,
10531                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10532                 // from `maxNativeZoom` level and auto-scaled.
10533                 maxNativeZoom: undefined,
10534
10535                 // @option minNativeZoom: Number = undefined
10536                 // Minimum zoom number the tile source has available. If it is specified,
10537                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10538                 // from `minNativeZoom` level and auto-scaled.
10539                 minNativeZoom: undefined,
10540
10541                 // @option noWrap: Boolean = false
10542                 // Whether the layer is wrapped around the antimeridian. If `true`, the
10543                 // GridLayer will only be displayed once at low zoom levels. Has no
10544                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10545                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10546                 // tiles outside the CRS limits.
10547                 noWrap: false,
10548
10549                 // @option pane: String = 'tilePane'
10550                 // `Map pane` where the grid layer will be added.
10551                 pane: 'tilePane',
10552
10553                 // @option className: String = ''
10554                 // A custom class name to assign to the tile layer. Empty by default.
10555                 className: '',
10556
10557                 // @option keepBuffer: Number = 2
10558                 // When panning the map, keep this many rows and columns of tiles before unloading them.
10559                 keepBuffer: 2
10560         },
10561
10562         initialize: function (options) {
10563                 setOptions(this, options);
10564         },
10565
10566         onAdd: function () {
10567                 this._initContainer();
10568
10569                 this._levels = {};
10570                 this._tiles = {};
10571
10572                 this._resetView();
10573                 this._update();
10574         },
10575
10576         beforeAdd: function (map) {
10577                 map._addZoomLimit(this);
10578         },
10579
10580         onRemove: function (map) {
10581                 this._removeAllTiles();
10582                 remove(this._container);
10583                 map._removeZoomLimit(this);
10584                 this._container = null;
10585                 this._tileZoom = undefined;
10586         },
10587
10588         // @method bringToFront: this
10589         // Brings the tile layer to the top of all tile layers.
10590         bringToFront: function () {
10591                 if (this._map) {
10592                         toFront(this._container);
10593                         this._setAutoZIndex(Math.max);
10594                 }
10595                 return this;
10596         },
10597
10598         // @method bringToBack: this
10599         // Brings the tile layer to the bottom of all tile layers.
10600         bringToBack: function () {
10601                 if (this._map) {
10602                         toBack(this._container);
10603                         this._setAutoZIndex(Math.min);
10604                 }
10605                 return this;
10606         },
10607
10608         // @method getContainer: HTMLElement
10609         // Returns the HTML element that contains the tiles for this layer.
10610         getContainer: function () {
10611                 return this._container;
10612         },
10613
10614         // @method setOpacity(opacity: Number): this
10615         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10616         setOpacity: function (opacity) {
10617                 this.options.opacity = opacity;
10618                 this._updateOpacity();
10619                 return this;
10620         },
10621
10622         // @method setZIndex(zIndex: Number): this
10623         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10624         setZIndex: function (zIndex) {
10625                 this.options.zIndex = zIndex;
10626                 this._updateZIndex();
10627
10628                 return this;
10629         },
10630
10631         // @method isLoading: Boolean
10632         // Returns `true` if any tile in the grid layer has not finished loading.
10633         isLoading: function () {
10634                 return this._loading;
10635         },
10636
10637         // @method redraw: this
10638         // Causes the layer to clear all the tiles and request them again.
10639         redraw: function () {
10640                 if (this._map) {
10641                         this._removeAllTiles();
10642                         this._update();
10643                 }
10644                 return this;
10645         },
10646
10647         getEvents: function () {
10648                 var events = {
10649                         viewprereset: this._invalidateAll,
10650                         viewreset: this._resetView,
10651                         zoom: this._resetView,
10652                         moveend: this._onMoveEnd
10653                 };
10654
10655                 if (!this.options.updateWhenIdle) {
10656                         // update tiles on move, but not more often than once per given interval
10657                         if (!this._onMove) {
10658                                 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10659                         }
10660
10661                         events.move = this._onMove;
10662                 }
10663
10664                 if (this._zoomAnimated) {
10665                         events.zoomanim = this._animateZoom;
10666                 }
10667
10668                 return events;
10669         },
10670
10671         // @section Extension methods
10672         // Layers extending `GridLayer` shall reimplement the following method.
10673         // @method createTile(coords: Object, done?: Function): HTMLElement
10674         // Called only internally, must be overridden by classes extending `GridLayer`.
10675         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10676         // is specified, it must be called when the tile has finished loading and drawing.
10677         createTile: function () {
10678                 return document.createElement('div');
10679         },
10680
10681         // @section
10682         // @method getTileSize: Point
10683         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10684         getTileSize: function () {
10685                 var s = this.options.tileSize;
10686                 return s instanceof Point ? s : new Point(s, s);
10687         },
10688
10689         _updateZIndex: function () {
10690                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10691                         this._container.style.zIndex = this.options.zIndex;
10692                 }
10693         },
10694
10695         _setAutoZIndex: function (compare) {
10696                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10697
10698                 var layers = this.getPane().children,
10699                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10700
10701                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10702
10703                         zIndex = layers[i].style.zIndex;
10704
10705                         if (layers[i] !== this._container && zIndex) {
10706                                 edgeZIndex = compare(edgeZIndex, +zIndex);
10707                         }
10708                 }
10709
10710                 if (isFinite(edgeZIndex)) {
10711                         this.options.zIndex = edgeZIndex + compare(-1, 1);
10712                         this._updateZIndex();
10713                 }
10714         },
10715
10716         _updateOpacity: function () {
10717                 if (!this._map) { return; }
10718
10719                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10720                 if (ielt9) { return; }
10721
10722                 setOpacity(this._container, this.options.opacity);
10723
10724                 var now = +new Date(),
10725                     nextFrame = false,
10726                     willPrune = false;
10727
10728                 for (var key in this._tiles) {
10729                         var tile = this._tiles[key];
10730                         if (!tile.current || !tile.loaded) { continue; }
10731
10732                         var fade = Math.min(1, (now - tile.loaded) / 200);
10733
10734                         setOpacity(tile.el, fade);
10735                         if (fade < 1) {
10736                                 nextFrame = true;
10737                         } else {
10738                                 if (tile.active) {
10739                                         willPrune = true;
10740                                 } else {
10741                                         this._onOpaqueTile(tile);
10742                                 }
10743                                 tile.active = true;
10744                         }
10745                 }
10746
10747                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10748
10749                 if (nextFrame) {
10750                         cancelAnimFrame(this._fadeFrame);
10751                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10752                 }
10753         },
10754
10755         _onOpaqueTile: falseFn,
10756
10757         _initContainer: function () {
10758                 if (this._container) { return; }
10759
10760                 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10761                 this._updateZIndex();
10762
10763                 if (this.options.opacity < 1) {
10764                         this._updateOpacity();
10765                 }
10766
10767                 this.getPane().appendChild(this._container);
10768         },
10769
10770         _updateLevels: function () {
10771
10772                 var zoom = this._tileZoom,
10773                     maxZoom = this.options.maxZoom;
10774
10775                 if (zoom === undefined) { return undefined; }
10776
10777                 for (var z in this._levels) {
10778                         if (this._levels[z].el.children.length || z === zoom) {
10779                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10780                                 this._onUpdateLevel(z);
10781                         } else {
10782                                 remove(this._levels[z].el);
10783                                 this._removeTilesAtZoom(z);
10784                                 this._onRemoveLevel(z);
10785                                 delete this._levels[z];
10786                         }
10787                 }
10788
10789                 var level = this._levels[zoom],
10790                     map = this._map;
10791
10792                 if (!level) {
10793                         level = this._levels[zoom] = {};
10794
10795                         level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10796                         level.el.style.zIndex = maxZoom;
10797
10798                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10799                         level.zoom = zoom;
10800
10801                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
10802
10803                         // force the browser to consider the newly added element for transition
10804                         falseFn(level.el.offsetWidth);
10805
10806                         this._onCreateLevel(level);
10807                 }
10808
10809                 this._level = level;
10810
10811                 return level;
10812         },
10813
10814         _onUpdateLevel: falseFn,
10815
10816         _onRemoveLevel: falseFn,
10817
10818         _onCreateLevel: falseFn,
10819
10820         _pruneTiles: function () {
10821                 if (!this._map) {
10822                         return;
10823                 }
10824
10825                 var key, tile;
10826
10827                 var zoom = this._map.getZoom();
10828                 if (zoom > this.options.maxZoom ||
10829                         zoom < this.options.minZoom) {
10830                         this._removeAllTiles();
10831                         return;
10832                 }
10833
10834                 for (key in this._tiles) {
10835                         tile = this._tiles[key];
10836                         tile.retain = tile.current;
10837                 }
10838
10839                 for (key in this._tiles) {
10840                         tile = this._tiles[key];
10841                         if (tile.current && !tile.active) {
10842                                 var coords = tile.coords;
10843                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10844                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10845                                 }
10846                         }
10847                 }
10848
10849                 for (key in this._tiles) {
10850                         if (!this._tiles[key].retain) {
10851                                 this._removeTile(key);
10852                         }
10853                 }
10854         },
10855
10856         _removeTilesAtZoom: function (zoom) {
10857                 for (var key in this._tiles) {
10858                         if (this._tiles[key].coords.z !== zoom) {
10859                                 continue;
10860                         }
10861                         this._removeTile(key);
10862                 }
10863         },
10864
10865         _removeAllTiles: function () {
10866                 for (var key in this._tiles) {
10867                         this._removeTile(key);
10868                 }
10869         },
10870
10871         _invalidateAll: function () {
10872                 for (var z in this._levels) {
10873                         remove(this._levels[z].el);
10874                         this._onRemoveLevel(z);
10875                         delete this._levels[z];
10876                 }
10877                 this._removeAllTiles();
10878
10879                 this._tileZoom = undefined;
10880         },
10881
10882         _retainParent: function (x, y, z, minZoom) {
10883                 var x2 = Math.floor(x / 2),
10884                     y2 = Math.floor(y / 2),
10885                     z2 = z - 1,
10886                     coords2 = new Point(+x2, +y2);
10887                 coords2.z = +z2;
10888
10889                 var key = this._tileCoordsToKey(coords2),
10890                     tile = this._tiles[key];
10891
10892                 if (tile && tile.active) {
10893                         tile.retain = true;
10894                         return true;
10895
10896                 } else if (tile && tile.loaded) {
10897                         tile.retain = true;
10898                 }
10899
10900                 if (z2 > minZoom) {
10901                         return this._retainParent(x2, y2, z2, minZoom);
10902                 }
10903
10904                 return false;
10905         },
10906
10907         _retainChildren: function (x, y, z, maxZoom) {
10908
10909                 for (var i = 2 * x; i < 2 * x + 2; i++) {
10910                         for (var j = 2 * y; j < 2 * y + 2; j++) {
10911
10912                                 var coords = new Point(i, j);
10913                                 coords.z = z + 1;
10914
10915                                 var key = this._tileCoordsToKey(coords),
10916                                     tile = this._tiles[key];
10917
10918                                 if (tile && tile.active) {
10919                                         tile.retain = true;
10920                                         continue;
10921
10922                                 } else if (tile && tile.loaded) {
10923                                         tile.retain = true;
10924                                 }
10925
10926                                 if (z + 1 < maxZoom) {
10927                                         this._retainChildren(i, j, z + 1, maxZoom);
10928                                 }
10929                         }
10930                 }
10931         },
10932
10933         _resetView: function (e) {
10934                 var animating = e && (e.pinch || e.flyTo);
10935                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
10936         },
10937
10938         _animateZoom: function (e) {
10939                 this._setView(e.center, e.zoom, true, e.noUpdate);
10940         },
10941
10942         _clampZoom: function (zoom) {
10943                 var options = this.options;
10944
10945                 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
10946                         return options.minNativeZoom;
10947                 }
10948
10949                 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
10950                         return options.maxNativeZoom;
10951                 }
10952
10953                 return zoom;
10954         },
10955
10956         _setView: function (center, zoom, noPrune, noUpdate) {
10957                 var tileZoom = this._clampZoom(Math.round(zoom));
10958                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
10959                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
10960                         tileZoom = undefined;
10961                 }
10962
10963                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
10964
10965                 if (!noUpdate || tileZoomChanged) {
10966
10967                         this._tileZoom = tileZoom;
10968
10969                         if (this._abortLoading) {
10970                                 this._abortLoading();
10971                         }
10972
10973                         this._updateLevels();
10974                         this._resetGrid();
10975
10976                         if (tileZoom !== undefined) {
10977                                 this._update(center);
10978                         }
10979
10980                         if (!noPrune) {
10981                                 this._pruneTiles();
10982                         }
10983
10984                         // Flag to prevent _updateOpacity from pruning tiles during
10985                         // a zoom anim or a pinch gesture
10986                         this._noPrune = !!noPrune;
10987                 }
10988
10989                 this._setZoomTransforms(center, zoom);
10990         },
10991
10992         _setZoomTransforms: function (center, zoom) {
10993                 for (var i in this._levels) {
10994                         this._setZoomTransform(this._levels[i], center, zoom);
10995                 }
10996         },
10997
10998         _setZoomTransform: function (level, center, zoom) {
10999                 var scale = this._map.getZoomScale(zoom, level.zoom),
11000                     translate = level.origin.multiplyBy(scale)
11001                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11002
11003                 if (any3d) {
11004                         setTransform(level.el, translate, scale);
11005                 } else {
11006                         setPosition(level.el, translate);
11007                 }
11008         },
11009
11010         _resetGrid: function () {
11011                 var map = this._map,
11012                     crs = map.options.crs,
11013                     tileSize = this._tileSize = this.getTileSize(),
11014                     tileZoom = this._tileZoom;
11015
11016                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11017                 if (bounds) {
11018                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
11019                 }
11020
11021                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11022                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11023                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11024                 ];
11025                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11026                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11027                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11028                 ];
11029         },
11030
11031         _onMoveEnd: function () {
11032                 if (!this._map || this._map._animatingZoom) { return; }
11033
11034                 this._update();
11035         },
11036
11037         _getTiledPixelBounds: function (center) {
11038                 var map = this._map,
11039                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11040                     scale = map.getZoomScale(mapZoom, this._tileZoom),
11041                     pixelCenter = map.project(center, this._tileZoom).floor(),
11042                     halfSize = map.getSize().divideBy(scale * 2);
11043
11044                 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11045         },
11046
11047         // Private method to load tiles in the grid's active zoom level according to map bounds
11048         _update: function (center) {
11049                 var map = this._map;
11050                 if (!map) { return; }
11051                 var zoom = this._clampZoom(map.getZoom());
11052
11053                 if (center === undefined) { center = map.getCenter(); }
11054                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
11055
11056                 var pixelBounds = this._getTiledPixelBounds(center),
11057                     tileRange = this._pxBoundsToTileRange(pixelBounds),
11058                     tileCenter = tileRange.getCenter(),
11059                     queue = [],
11060                     margin = this.options.keepBuffer,
11061                     noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11062                                               tileRange.getTopRight().add([margin, -margin]));
11063
11064                 // Sanity check: panic if the tile range contains Infinity somewhere.
11065                 if (!(isFinite(tileRange.min.x) &&
11066                       isFinite(tileRange.min.y) &&
11067                       isFinite(tileRange.max.x) &&
11068                       isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11069
11070                 for (var key in this._tiles) {
11071                         var c = this._tiles[key].coords;
11072                         if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11073                                 this._tiles[key].current = false;
11074                         }
11075                 }
11076
11077                 // _update just loads more tiles. If the tile zoom level differs too much
11078                 // from the map's, let _setView reset levels and prune old tiles.
11079                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11080
11081                 // create a queue of coordinates to load tiles from
11082                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11083                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11084                                 var coords = new Point(i, j);
11085                                 coords.z = this._tileZoom;
11086
11087                                 if (!this._isValidTile(coords)) { continue; }
11088
11089                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
11090                                 if (tile) {
11091                                         tile.current = true;
11092                                 } else {
11093                                         queue.push(coords);
11094                                 }
11095                         }
11096                 }
11097
11098                 // sort tile queue to load tiles in order of their distance to center
11099                 queue.sort(function (a, b) {
11100                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11101                 });
11102
11103                 if (queue.length !== 0) {
11104                         // if it's the first batch of tiles to load
11105                         if (!this._loading) {
11106                                 this._loading = true;
11107                                 // @event loading: Event
11108                                 // Fired when the grid layer starts loading tiles.
11109                                 this.fire('loading');
11110                         }
11111
11112                         // create DOM fragment to append tiles in one batch
11113                         var fragment = document.createDocumentFragment();
11114
11115                         for (i = 0; i < queue.length; i++) {
11116                                 this._addTile(queue[i], fragment);
11117                         }
11118
11119                         this._level.el.appendChild(fragment);
11120                 }
11121         },
11122
11123         _isValidTile: function (coords) {
11124                 var crs = this._map.options.crs;
11125
11126                 if (!crs.infinite) {
11127                         // don't load tile if it's out of bounds and not wrapped
11128                         var bounds = this._globalTileRange;
11129                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11130                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11131                 }
11132
11133                 if (!this.options.bounds) { return true; }
11134
11135                 // don't load tile if it doesn't intersect the bounds in options
11136                 var tileBounds = this._tileCoordsToBounds(coords);
11137                 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11138         },
11139
11140         _keyToBounds: function (key) {
11141                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11142         },
11143
11144         _tileCoordsToNwSe: function (coords) {
11145                 var map = this._map,
11146                     tileSize = this.getTileSize(),
11147                     nwPoint = coords.scaleBy(tileSize),
11148                     sePoint = nwPoint.add(tileSize),
11149                     nw = map.unproject(nwPoint, coords.z),
11150                     se = map.unproject(sePoint, coords.z);
11151                 return [nw, se];
11152         },
11153
11154         // converts tile coordinates to its geographical bounds
11155         _tileCoordsToBounds: function (coords) {
11156                 var bp = this._tileCoordsToNwSe(coords),
11157                     bounds = new LatLngBounds(bp[0], bp[1]);
11158
11159                 if (!this.options.noWrap) {
11160                         bounds = this._map.wrapLatLngBounds(bounds);
11161                 }
11162                 return bounds;
11163         },
11164         // converts tile coordinates to key for the tile cache
11165         _tileCoordsToKey: function (coords) {
11166                 return coords.x + ':' + coords.y + ':' + coords.z;
11167         },
11168
11169         // converts tile cache key to coordinates
11170         _keyToTileCoords: function (key) {
11171                 var k = key.split(':'),
11172                     coords = new Point(+k[0], +k[1]);
11173                 coords.z = +k[2];
11174                 return coords;
11175         },
11176
11177         _removeTile: function (key) {
11178                 var tile = this._tiles[key];
11179                 if (!tile) { return; }
11180
11181                 // Cancels any pending http requests associated with the tile
11182                 // unless we're on Android's stock browser,
11183                 // see https://github.com/Leaflet/Leaflet/issues/137
11184                 if (!androidStock) {
11185                         tile.el.setAttribute('src', emptyImageUrl);
11186                 }
11187                 remove(tile.el);
11188
11189                 delete this._tiles[key];
11190
11191                 // @event tileunload: TileEvent
11192                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11193                 this.fire('tileunload', {
11194                         tile: tile.el,
11195                         coords: this._keyToTileCoords(key)
11196                 });
11197         },
11198
11199         _initTile: function (tile) {
11200                 addClass(tile, 'leaflet-tile');
11201
11202                 var tileSize = this.getTileSize();
11203                 tile.style.width = tileSize.x + 'px';
11204                 tile.style.height = tileSize.y + 'px';
11205
11206                 tile.onselectstart = falseFn;
11207                 tile.onmousemove = falseFn;
11208
11209                 // update opacity on tiles in IE7-8 because of filter inheritance problems
11210                 if (ielt9 && this.options.opacity < 1) {
11211                         setOpacity(tile, this.options.opacity);
11212                 }
11213
11214                 // without this hack, tiles disappear after zoom on Chrome for Android
11215                 // https://github.com/Leaflet/Leaflet/issues/2078
11216                 if (android && !android23) {
11217                         tile.style.WebkitBackfaceVisibility = 'hidden';
11218                 }
11219         },
11220
11221         _addTile: function (coords, container) {
11222                 var tilePos = this._getTilePos(coords),
11223                     key = this._tileCoordsToKey(coords);
11224
11225                 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11226
11227                 this._initTile(tile);
11228
11229                 // if createTile is defined with a second argument ("done" callback),
11230                 // we know that tile is async and will be ready later; otherwise
11231                 if (this.createTile.length < 2) {
11232                         // mark tile as ready, but delay one frame for opacity animation to happen
11233                         requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11234                 }
11235
11236                 setPosition(tile, tilePos);
11237
11238                 // save tile in cache
11239                 this._tiles[key] = {
11240                         el: tile,
11241                         coords: coords,
11242                         current: true
11243                 };
11244
11245                 container.appendChild(tile);
11246                 // @event tileloadstart: TileEvent
11247                 // Fired when a tile is requested and starts loading.
11248                 this.fire('tileloadstart', {
11249                         tile: tile,
11250                         coords: coords
11251                 });
11252         },
11253
11254         _tileReady: function (coords, err, tile) {
11255                 if (!this._map) { return; }
11256
11257                 if (err) {
11258                         // @event tileerror: TileErrorEvent
11259                         // Fired when there is an error loading a tile.
11260                         this.fire('tileerror', {
11261                                 error: err,
11262                                 tile: tile,
11263                                 coords: coords
11264                         });
11265                 }
11266
11267                 var key = this._tileCoordsToKey(coords);
11268
11269                 tile = this._tiles[key];
11270                 if (!tile) { return; }
11271
11272                 tile.loaded = +new Date();
11273                 if (this._map._fadeAnimated) {
11274                         setOpacity(tile.el, 0);
11275                         cancelAnimFrame(this._fadeFrame);
11276                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11277                 } else {
11278                         tile.active = true;
11279                         this._pruneTiles();
11280                 }
11281
11282                 if (!err) {
11283                         addClass(tile.el, 'leaflet-tile-loaded');
11284
11285                         // @event tileload: TileEvent
11286                         // Fired when a tile loads.
11287                         this.fire('tileload', {
11288                                 tile: tile.el,
11289                                 coords: coords
11290                         });
11291                 }
11292
11293                 if (this._noTilesToLoad()) {
11294                         this._loading = false;
11295                         // @event load: Event
11296                         // Fired when the grid layer loaded all visible tiles.
11297                         this.fire('load');
11298
11299                         if (ielt9 || !this._map._fadeAnimated) {
11300                                 requestAnimFrame(this._pruneTiles, this);
11301                         } else {
11302                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11303                                 // to trigger a pruning.
11304                                 setTimeout(bind(this._pruneTiles, this), 250);
11305                         }
11306                 }
11307         },
11308
11309         _getTilePos: function (coords) {
11310                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11311         },
11312
11313         _wrapCoords: function (coords) {
11314                 var newCoords = new Point(
11315                         this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11316                         this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11317                 newCoords.z = coords.z;
11318                 return newCoords;
11319         },
11320
11321         _pxBoundsToTileRange: function (bounds) {
11322                 var tileSize = this.getTileSize();
11323                 return new Bounds(
11324                         bounds.min.unscaleBy(tileSize).floor(),
11325                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11326         },
11327
11328         _noTilesToLoad: function () {
11329                 for (var key in this._tiles) {
11330                         if (!this._tiles[key].loaded) { return false; }
11331                 }
11332                 return true;
11333         }
11334 });
11335
11336 // @factory L.gridLayer(options?: GridLayer options)
11337 // Creates a new instance of GridLayer with the supplied options.
11338 function gridLayer(options) {
11339         return new GridLayer(options);
11340 }
11341
11342 /*
11343  * @class TileLayer
11344  * @inherits GridLayer
11345  * @aka L.TileLayer
11346  * Used to load and display tile layers on the map. Extends `GridLayer`.
11347  *
11348  * @example
11349  *
11350  * ```js
11351  * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
11352  * ```
11353  *
11354  * @section URL template
11355  * @example
11356  *
11357  * A string of the following form:
11358  *
11359  * ```
11360  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11361  * ```
11362  *
11363  * `{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 "&commat;2x" to the URL to load retina tiles.
11364  *
11365  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11366  *
11367  * ```
11368  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11369  * ```
11370  */
11371
11372
11373 var TileLayer = GridLayer.extend({
11374
11375         // @section
11376         // @aka TileLayer options
11377         options: {
11378                 // @option minZoom: Number = 0
11379                 // The minimum zoom level down to which this layer will be displayed (inclusive).
11380                 minZoom: 0,
11381
11382                 // @option maxZoom: Number = 18
11383                 // The maximum zoom level up to which this layer will be displayed (inclusive).
11384                 maxZoom: 18,
11385
11386                 // @option subdomains: String|String[] = 'abc'
11387                 // 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.
11388                 subdomains: 'abc',
11389
11390                 // @option errorTileUrl: String = ''
11391                 // URL to the tile image to show in place of the tile that failed to load.
11392                 errorTileUrl: '',
11393
11394                 // @option zoomOffset: Number = 0
11395                 // The zoom number used in tile URLs will be offset with this value.
11396                 zoomOffset: 0,
11397
11398                 // @option tms: Boolean = false
11399                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11400                 tms: false,
11401
11402                 // @option zoomReverse: Boolean = false
11403                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11404                 zoomReverse: false,
11405
11406                 // @option detectRetina: Boolean = false
11407                 // 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.
11408                 detectRetina: false,
11409
11410                 // @option crossOrigin: Boolean = false
11411                 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
11412                 crossOrigin: false
11413         },
11414
11415         initialize: function (url, options) {
11416
11417                 this._url = url;
11418
11419                 options = setOptions(this, options);
11420
11421                 // detecting retina displays, adjusting tileSize and zoom levels
11422                 if (options.detectRetina && retina && options.maxZoom > 0) {
11423
11424                         options.tileSize = Math.floor(options.tileSize / 2);
11425
11426                         if (!options.zoomReverse) {
11427                                 options.zoomOffset++;
11428                                 options.maxZoom--;
11429                         } else {
11430                                 options.zoomOffset--;
11431                                 options.minZoom++;
11432                         }
11433
11434                         options.minZoom = Math.max(0, options.minZoom);
11435                 }
11436
11437                 if (typeof options.subdomains === 'string') {
11438                         options.subdomains = options.subdomains.split('');
11439                 }
11440
11441                 // for https://github.com/Leaflet/Leaflet/issues/137
11442                 if (!android) {
11443                         this.on('tileunload', this._onTileRemove);
11444                 }
11445         },
11446
11447         // @method setUrl(url: String, noRedraw?: Boolean): this
11448         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11449         setUrl: function (url, noRedraw) {
11450                 this._url = url;
11451
11452                 if (!noRedraw) {
11453                         this.redraw();
11454                 }
11455                 return this;
11456         },
11457
11458         // @method createTile(coords: Object, done?: Function): HTMLElement
11459         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11460         // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11461         // callback is called when the tile has been loaded.
11462         createTile: function (coords, done) {
11463                 var tile = document.createElement('img');
11464
11465                 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11466                 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11467
11468                 if (this.options.crossOrigin) {
11469                         tile.crossOrigin = '';
11470                 }
11471
11472                 /*
11473                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11474                  http://www.w3.org/TR/WCAG20-TECHS/H67
11475                 */
11476                 tile.alt = '';
11477
11478                 /*
11479                  Set role="presentation" to force screen readers to ignore this
11480                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11481                 */
11482                 tile.setAttribute('role', 'presentation');
11483
11484                 tile.src = this.getTileUrl(coords);
11485
11486                 return tile;
11487         },
11488
11489         // @section Extension methods
11490         // @uninheritable
11491         // Layers extending `TileLayer` might reimplement the following method.
11492         // @method getTileUrl(coords: Object): String
11493         // Called only internally, returns the URL for a tile given its coordinates.
11494         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11495         getTileUrl: function (coords) {
11496                 var data = {
11497                         r: retina ? '@2x' : '',
11498                         s: this._getSubdomain(coords),
11499                         x: coords.x,
11500                         y: coords.y,
11501                         z: this._getZoomForUrl()
11502                 };
11503                 if (this._map && !this._map.options.crs.infinite) {
11504                         var invertedY = this._globalTileRange.max.y - coords.y;
11505                         if (this.options.tms) {
11506                                 data['y'] = invertedY;
11507                         }
11508                         data['-y'] = invertedY;
11509                 }
11510
11511                 return template(this._url, extend(data, this.options));
11512         },
11513
11514         _tileOnLoad: function (done, tile) {
11515                 // For https://github.com/Leaflet/Leaflet/issues/3332
11516                 if (ielt9) {
11517                         setTimeout(bind(done, this, null, tile), 0);
11518                 } else {
11519                         done(null, tile);
11520                 }
11521         },
11522
11523         _tileOnError: function (done, tile, e) {
11524                 var errorUrl = this.options.errorTileUrl;
11525                 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11526                         tile.src = errorUrl;
11527                 }
11528                 done(e, tile);
11529         },
11530
11531         _onTileRemove: function (e) {
11532                 e.tile.onload = null;
11533         },
11534
11535         _getZoomForUrl: function () {
11536                 var zoom = this._tileZoom,
11537                 maxZoom = this.options.maxZoom,
11538                 zoomReverse = this.options.zoomReverse,
11539                 zoomOffset = this.options.zoomOffset;
11540
11541                 if (zoomReverse) {
11542                         zoom = maxZoom - zoom;
11543                 }
11544
11545                 return zoom + zoomOffset;
11546         },
11547
11548         _getSubdomain: function (tilePoint) {
11549                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11550                 return this.options.subdomains[index];
11551         },
11552
11553         // stops loading all tiles in the background layer
11554         _abortLoading: function () {
11555                 var i, tile;
11556                 for (i in this._tiles) {
11557                         if (this._tiles[i].coords.z !== this._tileZoom) {
11558                                 tile = this._tiles[i].el;
11559
11560                                 tile.onload = falseFn;
11561                                 tile.onerror = falseFn;
11562
11563                                 if (!tile.complete) {
11564                                         tile.src = emptyImageUrl;
11565                                         remove(tile);
11566                                         delete this._tiles[i];
11567                                 }
11568                         }
11569                 }
11570         }
11571 });
11572
11573
11574 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11575 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11576
11577 function tileLayer(url, options) {
11578         return new TileLayer(url, options);
11579 }
11580
11581 /*
11582  * @class TileLayer.WMS
11583  * @inherits TileLayer
11584  * @aka L.TileLayer.WMS
11585  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11586  *
11587  * @example
11588  *
11589  * ```js
11590  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11591  *      layers: 'nexrad-n0r-900913',
11592  *      format: 'image/png',
11593  *      transparent: true,
11594  *      attribution: "Weather data © 2012 IEM Nexrad"
11595  * });
11596  * ```
11597  */
11598
11599 var TileLayerWMS = TileLayer.extend({
11600
11601         // @section
11602         // @aka TileLayer.WMS options
11603         // If any custom options not documented here are used, they will be sent to the
11604         // WMS server as extra parameters in each request URL. This can be useful for
11605         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11606         defaultWmsParams: {
11607                 service: 'WMS',
11608                 request: 'GetMap',
11609
11610                 // @option layers: String = ''
11611                 // **(required)** Comma-separated list of WMS layers to show.
11612                 layers: '',
11613
11614                 // @option styles: String = ''
11615                 // Comma-separated list of WMS styles.
11616                 styles: '',
11617
11618                 // @option format: String = 'image/jpeg'
11619                 // WMS image format (use `'image/png'` for layers with transparency).
11620                 format: 'image/jpeg',
11621
11622                 // @option transparent: Boolean = false
11623                 // If `true`, the WMS service will return images with transparency.
11624                 transparent: false,
11625
11626                 // @option version: String = '1.1.1'
11627                 // Version of the WMS service to use
11628                 version: '1.1.1'
11629         },
11630
11631         options: {
11632                 // @option crs: CRS = null
11633                 // Coordinate Reference System to use for the WMS requests, defaults to
11634                 // map CRS. Don't change this if you're not sure what it means.
11635                 crs: null,
11636
11637                 // @option uppercase: Boolean = false
11638                 // If `true`, WMS request parameter keys will be uppercase.
11639                 uppercase: false
11640         },
11641
11642         initialize: function (url, options) {
11643
11644                 this._url = url;
11645
11646                 var wmsParams = extend({}, this.defaultWmsParams);
11647
11648                 // all keys that are not TileLayer options go to WMS params
11649                 for (var i in options) {
11650                         if (!(i in this.options)) {
11651                                 wmsParams[i] = options[i];
11652                         }
11653                 }
11654
11655                 options = setOptions(this, options);
11656
11657                 var realRetina = options.detectRetina && retina ? 2 : 1;
11658                 var tileSize = this.getTileSize();
11659                 wmsParams.width = tileSize.x * realRetina;
11660                 wmsParams.height = tileSize.y * realRetina;
11661
11662                 this.wmsParams = wmsParams;
11663         },
11664
11665         onAdd: function (map) {
11666
11667                 this._crs = this.options.crs || map.options.crs;
11668                 this._wmsVersion = parseFloat(this.wmsParams.version);
11669
11670                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11671                 this.wmsParams[projectionKey] = this._crs.code;
11672
11673                 TileLayer.prototype.onAdd.call(this, map);
11674         },
11675
11676         getTileUrl: function (coords) {
11677
11678                 var tileBounds = this._tileCoordsToNwSe(coords),
11679                     crs = this._crs,
11680                     bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11681                     min = bounds.min,
11682                     max = bounds.max,
11683                     bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11684                     [min.y, min.x, max.y, max.x] :
11685                     [min.x, min.y, max.x, max.y]).join(','),
11686                 url = L.TileLayer.prototype.getTileUrl.call(this, coords);
11687                 return url +
11688                         getParamString(this.wmsParams, url, this.options.uppercase) +
11689                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11690         },
11691
11692         // @method setParams(params: Object, noRedraw?: Boolean): this
11693         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11694         setParams: function (params, noRedraw) {
11695
11696                 extend(this.wmsParams, params);
11697
11698                 if (!noRedraw) {
11699                         this.redraw();
11700                 }
11701
11702                 return this;
11703         }
11704 });
11705
11706
11707 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11708 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11709 function tileLayerWMS(url, options) {
11710         return new TileLayerWMS(url, options);
11711 }
11712
11713 TileLayer.WMS = TileLayerWMS;
11714 tileLayer.wms = tileLayerWMS;
11715
11716 /*
11717  * @class Renderer
11718  * @inherits Layer
11719  * @aka L.Renderer
11720  *
11721  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11722  * DOM container of the renderer, its bounds, and its zoom animation.
11723  *
11724  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11725  * itself can be added or removed to the map. All paths use a renderer, which can
11726  * be implicit (the map will decide the type of renderer and use it automatically)
11727  * or explicit (using the [`renderer`](#path-renderer) option of the path).
11728  *
11729  * Do not use this class directly, use `SVG` and `Canvas` instead.
11730  *
11731  * @event update: Event
11732  * Fired when the renderer updates its bounds, center and zoom, for example when
11733  * its map has moved
11734  */
11735
11736 var Renderer = Layer.extend({
11737
11738         // @section
11739         // @aka Renderer options
11740         options: {
11741                 // @option padding: Number = 0.1
11742                 // How much to extend the clip area around the map view (relative to its size)
11743                 // e.g. 0.1 would be 10% of map view in each direction
11744                 padding: 0.1,
11745
11746                 // @option tolerance: Number = 0
11747                 // How much to extend click tolerance round a path/object on the map
11748                 tolerance : 0
11749         },
11750
11751         initialize: function (options) {
11752                 setOptions(this, options);
11753                 stamp(this);
11754                 this._layers = this._layers || {};
11755         },
11756
11757         onAdd: function () {
11758                 if (!this._container) {
11759                         this._initContainer(); // defined by renderer implementations
11760
11761                         if (this._zoomAnimated) {
11762                                 addClass(this._container, 'leaflet-zoom-animated');
11763                         }
11764                 }
11765
11766                 this.getPane().appendChild(this._container);
11767                 this._update();
11768                 this.on('update', this._updatePaths, this);
11769         },
11770
11771         onRemove: function () {
11772                 this.off('update', this._updatePaths, this);
11773                 this._destroyContainer();
11774         },
11775
11776         getEvents: function () {
11777                 var events = {
11778                         viewreset: this._reset,
11779                         zoom: this._onZoom,
11780                         moveend: this._update,
11781                         zoomend: this._onZoomEnd
11782                 };
11783                 if (this._zoomAnimated) {
11784                         events.zoomanim = this._onAnimZoom;
11785                 }
11786                 return events;
11787         },
11788
11789         _onAnimZoom: function (ev) {
11790                 this._updateTransform(ev.center, ev.zoom);
11791         },
11792
11793         _onZoom: function () {
11794                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11795         },
11796
11797         _updateTransform: function (center, zoom) {
11798                 var scale = this._map.getZoomScale(zoom, this._zoom),
11799                     position = getPosition(this._container),
11800                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11801                     currentCenterPoint = this._map.project(this._center, zoom),
11802                     destCenterPoint = this._map.project(center, zoom),
11803                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
11804
11805                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11806
11807                 if (any3d) {
11808                         setTransform(this._container, topLeftOffset, scale);
11809                 } else {
11810                         setPosition(this._container, topLeftOffset);
11811                 }
11812         },
11813
11814         _reset: function () {
11815                 this._update();
11816                 this._updateTransform(this._center, this._zoom);
11817
11818                 for (var id in this._layers) {
11819                         this._layers[id]._reset();
11820                 }
11821         },
11822
11823         _onZoomEnd: function () {
11824                 for (var id in this._layers) {
11825                         this._layers[id]._project();
11826                 }
11827         },
11828
11829         _updatePaths: function () {
11830                 for (var id in this._layers) {
11831                         this._layers[id]._update();
11832                 }
11833         },
11834
11835         _update: function () {
11836                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11837                 // Subclasses are responsible of firing the 'update' event.
11838                 var p = this.options.padding,
11839                     size = this._map.getSize(),
11840                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11841
11842                 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11843
11844                 this._center = this._map.getCenter();
11845                 this._zoom = this._map.getZoom();
11846         }
11847 });
11848
11849 /*
11850  * @class Canvas
11851  * @inherits Renderer
11852  * @aka L.Canvas
11853  *
11854  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11855  * Inherits `Renderer`.
11856  *
11857  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11858  * available in all web browsers, notably IE8, and overlapping geometries might
11859  * not display properly in some edge cases.
11860  *
11861  * @example
11862  *
11863  * Use Canvas by default for all paths in the map:
11864  *
11865  * ```js
11866  * var map = L.map('map', {
11867  *      renderer: L.canvas()
11868  * });
11869  * ```
11870  *
11871  * Use a Canvas renderer with extra padding for specific vector geometries:
11872  *
11873  * ```js
11874  * var map = L.map('map');
11875  * var myRenderer = L.canvas({ padding: 0.5 });
11876  * var line = L.polyline( coordinates, { renderer: myRenderer } );
11877  * var circle = L.circle( center, { renderer: myRenderer } );
11878  * ```
11879  */
11880
11881 var Canvas = Renderer.extend({
11882         getEvents: function () {
11883                 var events = Renderer.prototype.getEvents.call(this);
11884                 events.viewprereset = this._onViewPreReset;
11885                 return events;
11886         },
11887
11888         _onViewPreReset: function () {
11889                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
11890                 this._postponeUpdatePaths = true;
11891         },
11892
11893         onAdd: function () {
11894                 Renderer.prototype.onAdd.call(this);
11895
11896                 // Redraw vectors since canvas is cleared upon removal,
11897                 // in case of removing the renderer itself from the map.
11898                 this._draw();
11899         },
11900
11901         _initContainer: function () {
11902                 var container = this._container = document.createElement('canvas');
11903
11904                 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
11905                 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
11906                 on(container, 'mouseout', this._handleMouseOut, this);
11907
11908                 this._ctx = container.getContext('2d');
11909         },
11910
11911         _destroyContainer: function () {
11912                 delete this._ctx;
11913                 remove(this._container);
11914                 off(this._container);
11915                 delete this._container;
11916         },
11917
11918         _updatePaths: function () {
11919                 if (this._postponeUpdatePaths) { return; }
11920
11921                 var layer;
11922                 this._redrawBounds = null;
11923                 for (var id in this._layers) {
11924                         layer = this._layers[id];
11925                         layer._update();
11926                 }
11927                 this._redraw();
11928         },
11929
11930         _update: function () {
11931                 if (this._map._animatingZoom && this._bounds) { return; }
11932
11933                 this._drawnLayers = {};
11934
11935                 Renderer.prototype._update.call(this);
11936
11937                 var b = this._bounds,
11938                     container = this._container,
11939                     size = b.getSize(),
11940                     m = retina ? 2 : 1;
11941
11942                 setPosition(container, b.min);
11943
11944                 // set canvas size (also clearing it); use double size on retina
11945                 container.width = m * size.x;
11946                 container.height = m * size.y;
11947                 container.style.width = size.x + 'px';
11948                 container.style.height = size.y + 'px';
11949
11950                 if (retina) {
11951                         this._ctx.scale(2, 2);
11952                 }
11953
11954                 // translate so we use the same path coordinates after canvas element moves
11955                 this._ctx.translate(-b.min.x, -b.min.y);
11956
11957                 // Tell paths to redraw themselves
11958                 this.fire('update');
11959         },
11960
11961         _reset: function () {
11962                 Renderer.prototype._reset.call(this);
11963
11964                 if (this._postponeUpdatePaths) {
11965                         this._postponeUpdatePaths = false;
11966                         this._updatePaths();
11967                 }
11968         },
11969
11970         _initPath: function (layer) {
11971                 this._updateDashArray(layer);
11972                 this._layers[stamp(layer)] = layer;
11973
11974                 var order = layer._order = {
11975                         layer: layer,
11976                         prev: this._drawLast,
11977                         next: null
11978                 };
11979                 if (this._drawLast) { this._drawLast.next = order; }
11980                 this._drawLast = order;
11981                 this._drawFirst = this._drawFirst || this._drawLast;
11982         },
11983
11984         _addPath: function (layer) {
11985                 this._requestRedraw(layer);
11986         },
11987
11988         _removePath: function (layer) {
11989                 var order = layer._order;
11990                 var next = order.next;
11991                 var prev = order.prev;
11992
11993                 if (next) {
11994                         next.prev = prev;
11995                 } else {
11996                         this._drawLast = prev;
11997                 }
11998                 if (prev) {
11999                         prev.next = next;
12000                 } else {
12001                         this._drawFirst = next;
12002                 }
12003
12004                 delete layer._order;
12005
12006                 delete this._layers[L.stamp(layer)];
12007
12008                 this._requestRedraw(layer);
12009         },
12010
12011         _updatePath: function (layer) {
12012                 // Redraw the union of the layer's old pixel
12013                 // bounds and the new pixel bounds.
12014                 this._extendRedrawBounds(layer);
12015                 layer._project();
12016                 layer._update();
12017                 // The redraw will extend the redraw bounds
12018                 // with the new pixel bounds.
12019                 this._requestRedraw(layer);
12020         },
12021
12022         _updateStyle: function (layer) {
12023                 this._updateDashArray(layer);
12024                 this._requestRedraw(layer);
12025         },
12026
12027         _updateDashArray: function (layer) {
12028                 if (layer.options.dashArray) {
12029                         var parts = layer.options.dashArray.split(','),
12030                             dashArray = [],
12031                             i;
12032                         for (i = 0; i < parts.length; i++) {
12033                                 dashArray.push(Number(parts[i]));
12034                         }
12035                         layer.options._dashArray = dashArray;
12036                 }
12037         },
12038
12039         _requestRedraw: function (layer) {
12040                 if (!this._map) { return; }
12041
12042                 this._extendRedrawBounds(layer);
12043                 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12044         },
12045
12046         _extendRedrawBounds: function (layer) {
12047                 if (layer._pxBounds) {
12048                         var padding = (layer.options.weight || 0) + 1;
12049                         this._redrawBounds = this._redrawBounds || new Bounds();
12050                         this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12051                         this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12052                 }
12053         },
12054
12055         _redraw: function () {
12056                 this._redrawRequest = null;
12057
12058                 if (this._redrawBounds) {
12059                         this._redrawBounds.min._floor();
12060                         this._redrawBounds.max._ceil();
12061                 }
12062
12063                 this._clear(); // clear layers in redraw bounds
12064                 this._draw(); // draw layers
12065
12066                 this._redrawBounds = null;
12067         },
12068
12069         _clear: function () {
12070                 var bounds = this._redrawBounds;
12071                 if (bounds) {
12072                         var size = bounds.getSize();
12073                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12074                 } else {
12075                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12076                 }
12077         },
12078
12079         _draw: function () {
12080                 var layer, bounds = this._redrawBounds;
12081                 this._ctx.save();
12082                 if (bounds) {
12083                         var size = bounds.getSize();
12084                         this._ctx.beginPath();
12085                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12086                         this._ctx.clip();
12087                 }
12088
12089                 this._drawing = true;
12090
12091                 for (var order = this._drawFirst; order; order = order.next) {
12092                         layer = order.layer;
12093                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12094                                 layer._updatePath();
12095                         }
12096                 }
12097
12098                 this._drawing = false;
12099
12100                 this._ctx.restore();  // Restore state before clipping.
12101         },
12102
12103         _updatePoly: function (layer, closed) {
12104                 if (!this._drawing) { return; }
12105
12106                 var i, j, len2, p,
12107                     parts = layer._parts,
12108                     len = parts.length,
12109                     ctx = this._ctx;
12110
12111                 if (!len) { return; }
12112
12113                 this._drawnLayers[layer._leaflet_id] = layer;
12114
12115                 ctx.beginPath();
12116
12117                 for (i = 0; i < len; i++) {
12118                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
12119                                 p = parts[i][j];
12120                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12121                         }
12122                         if (closed) {
12123                                 ctx.closePath();
12124                         }
12125                 }
12126
12127                 this._fillStroke(ctx, layer);
12128
12129                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12130         },
12131
12132         _updateCircle: function (layer) {
12133
12134                 if (!this._drawing || layer._empty()) { return; }
12135
12136                 var p = layer._point,
12137                     ctx = this._ctx,
12138                     r = Math.max(Math.round(layer._radius), 1),
12139                     s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12140
12141                 this._drawnLayers[layer._leaflet_id] = layer;
12142
12143                 if (s !== 1) {
12144                         ctx.save();
12145                         ctx.scale(1, s);
12146                 }
12147
12148                 ctx.beginPath();
12149                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12150
12151                 if (s !== 1) {
12152                         ctx.restore();
12153                 }
12154
12155                 this._fillStroke(ctx, layer);
12156         },
12157
12158         _fillStroke: function (ctx, layer) {
12159                 var options = layer.options;
12160
12161                 if (options.fill) {
12162                         ctx.globalAlpha = options.fillOpacity;
12163                         ctx.fillStyle = options.fillColor || options.color;
12164                         ctx.fill(options.fillRule || 'evenodd');
12165                 }
12166
12167                 if (options.stroke && options.weight !== 0) {
12168                         if (ctx.setLineDash) {
12169                                 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12170                         }
12171                         ctx.globalAlpha = options.opacity;
12172                         ctx.lineWidth = options.weight;
12173                         ctx.strokeStyle = options.color;
12174                         ctx.lineCap = options.lineCap;
12175                         ctx.lineJoin = options.lineJoin;
12176                         ctx.stroke();
12177                 }
12178         },
12179
12180         // Canvas obviously doesn't have mouse events for individual drawn objects,
12181         // so we emulate that by calculating what's under the mouse on mousemove/click manually
12182
12183         _onClick: function (e) {
12184                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12185
12186                 for (var order = this._drawFirst; order; order = order.next) {
12187                         layer = order.layer;
12188                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12189                                 clickedLayer = layer;
12190                         }
12191                 }
12192                 if (clickedLayer)  {
12193                         fakeStop(e);
12194                         this._fireEvent([clickedLayer], e);
12195                 }
12196         },
12197
12198         _onMouseMove: function (e) {
12199                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12200
12201                 var point = this._map.mouseEventToLayerPoint(e);
12202                 this._handleMouseHover(e, point);
12203         },
12204
12205
12206         _handleMouseOut: function (e) {
12207                 var layer = this._hoveredLayer;
12208                 if (layer) {
12209                         // if we're leaving the layer, fire mouseout
12210                         removeClass(this._container, 'leaflet-interactive');
12211                         this._fireEvent([layer], e, 'mouseout');
12212                         this._hoveredLayer = null;
12213                 }
12214         },
12215
12216         _handleMouseHover: function (e, point) {
12217                 var layer, candidateHoveredLayer;
12218
12219                 for (var order = this._drawFirst; order; order = order.next) {
12220                         layer = order.layer;
12221                         if (layer.options.interactive && layer._containsPoint(point)) {
12222                                 candidateHoveredLayer = layer;
12223                         }
12224                 }
12225
12226                 if (candidateHoveredLayer !== this._hoveredLayer) {
12227                         this._handleMouseOut(e);
12228
12229                         if (candidateHoveredLayer) {
12230                                 addClass(this._container, 'leaflet-interactive'); // change cursor
12231                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12232                                 this._hoveredLayer = candidateHoveredLayer;
12233                         }
12234                 }
12235
12236                 if (this._hoveredLayer) {
12237                         this._fireEvent([this._hoveredLayer], e);
12238                 }
12239         },
12240
12241         _fireEvent: function (layers, e, type) {
12242                 this._map._fireDOMEvent(e, type || e.type, layers);
12243         },
12244
12245         _bringToFront: function (layer) {
12246                 var order = layer._order;
12247                 var next = order.next;
12248                 var prev = order.prev;
12249
12250                 if (next) {
12251                         next.prev = prev;
12252                 } else {
12253                         // Already last
12254                         return;
12255                 }
12256                 if (prev) {
12257                         prev.next = next;
12258                 } else if (next) {
12259                         // Update first entry unless this is the
12260                         // single entry
12261                         this._drawFirst = next;
12262                 }
12263
12264                 order.prev = this._drawLast;
12265                 this._drawLast.next = order;
12266
12267                 order.next = null;
12268                 this._drawLast = order;
12269
12270                 this._requestRedraw(layer);
12271         },
12272
12273         _bringToBack: function (layer) {
12274                 var order = layer._order;
12275                 var next = order.next;
12276                 var prev = order.prev;
12277
12278                 if (prev) {
12279                         prev.next = next;
12280                 } else {
12281                         // Already first
12282                         return;
12283                 }
12284                 if (next) {
12285                         next.prev = prev;
12286                 } else if (prev) {
12287                         // Update last entry unless this is the
12288                         // single entry
12289                         this._drawLast = prev;
12290                 }
12291
12292                 order.prev = null;
12293
12294                 order.next = this._drawFirst;
12295                 this._drawFirst.prev = order;
12296                 this._drawFirst = order;
12297
12298                 this._requestRedraw(layer);
12299         }
12300 });
12301
12302 // @factory L.canvas(options?: Renderer options)
12303 // Creates a Canvas renderer with the given options.
12304 function canvas$1(options) {
12305         return canvas ? new Canvas(options) : null;
12306 }
12307
12308 /*
12309  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12310  */
12311
12312
12313 var vmlCreate = (function () {
12314         try {
12315                 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12316                 return function (name) {
12317                         return document.createElement('<lvml:' + name + ' class="lvml">');
12318                 };
12319         } catch (e) {
12320                 return function (name) {
12321                         return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12322                 };
12323         }
12324 })();
12325
12326
12327 /*
12328  * @class SVG
12329  *
12330  * 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.
12331  *
12332  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12333  * with old versions of Internet Explorer.
12334  */
12335
12336 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12337 var vmlMixin = {
12338
12339         _initContainer: function () {
12340                 this._container = create$1('div', 'leaflet-vml-container');
12341         },
12342
12343         _update: function () {
12344                 if (this._map._animatingZoom) { return; }
12345                 Renderer.prototype._update.call(this);
12346                 this.fire('update');
12347         },
12348
12349         _initPath: function (layer) {
12350                 var container = layer._container = vmlCreate('shape');
12351
12352                 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12353
12354                 container.coordsize = '1 1';
12355
12356                 layer._path = vmlCreate('path');
12357                 container.appendChild(layer._path);
12358
12359                 this._updateStyle(layer);
12360                 this._layers[stamp(layer)] = layer;
12361         },
12362
12363         _addPath: function (layer) {
12364                 var container = layer._container;
12365                 this._container.appendChild(container);
12366
12367                 if (layer.options.interactive) {
12368                         layer.addInteractiveTarget(container);
12369                 }
12370         },
12371
12372         _removePath: function (layer) {
12373                 var container = layer._container;
12374                 remove(container);
12375                 layer.removeInteractiveTarget(container);
12376                 delete this._layers[stamp(layer)];
12377         },
12378
12379         _updateStyle: function (layer) {
12380                 var stroke = layer._stroke,
12381                     fill = layer._fill,
12382                     options = layer.options,
12383                     container = layer._container;
12384
12385                 container.stroked = !!options.stroke;
12386                 container.filled = !!options.fill;
12387
12388                 if (options.stroke) {
12389                         if (!stroke) {
12390                                 stroke = layer._stroke = vmlCreate('stroke');
12391                         }
12392                         container.appendChild(stroke);
12393                         stroke.weight = options.weight + 'px';
12394                         stroke.color = options.color;
12395                         stroke.opacity = options.opacity;
12396
12397                         if (options.dashArray) {
12398                                 stroke.dashStyle = isArray(options.dashArray) ?
12399                                     options.dashArray.join(' ') :
12400                                     options.dashArray.replace(/( *, *)/g, ' ');
12401                         } else {
12402                                 stroke.dashStyle = '';
12403                         }
12404                         stroke.endcap = options.lineCap.replace('butt', 'flat');
12405                         stroke.joinstyle = options.lineJoin;
12406
12407                 } else if (stroke) {
12408                         container.removeChild(stroke);
12409                         layer._stroke = null;
12410                 }
12411
12412                 if (options.fill) {
12413                         if (!fill) {
12414                                 fill = layer._fill = vmlCreate('fill');
12415                         }
12416                         container.appendChild(fill);
12417                         fill.color = options.fillColor || options.color;
12418                         fill.opacity = options.fillOpacity;
12419
12420                 } else if (fill) {
12421                         container.removeChild(fill);
12422                         layer._fill = null;
12423                 }
12424         },
12425
12426         _updateCircle: function (layer) {
12427                 var p = layer._point.round(),
12428                     r = Math.round(layer._radius),
12429                     r2 = Math.round(layer._radiusY || r);
12430
12431                 this._setPath(layer, layer._empty() ? 'M0 0' :
12432                         'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12433         },
12434
12435         _setPath: function (layer, path) {
12436                 layer._path.v = path;
12437         },
12438
12439         _bringToFront: function (layer) {
12440                 toFront(layer._container);
12441         },
12442
12443         _bringToBack: function (layer) {
12444                 toBack(layer._container);
12445         }
12446 };
12447
12448 var create$2 = vml ? vmlCreate : svgCreate;
12449
12450 /*
12451  * @class SVG
12452  * @inherits Renderer
12453  * @aka L.SVG
12454  *
12455  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12456  * Inherits `Renderer`.
12457  *
12458  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12459  * available in all web browsers, notably Android 2.x and 3.x.
12460  *
12461  * Although SVG is not available on IE7 and IE8, these browsers support
12462  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12463  * (a now deprecated technology), and the SVG renderer will fall back to VML in
12464  * this case.
12465  *
12466  * @example
12467  *
12468  * Use SVG by default for all paths in the map:
12469  *
12470  * ```js
12471  * var map = L.map('map', {
12472  *      renderer: L.svg()
12473  * });
12474  * ```
12475  *
12476  * Use a SVG renderer with extra padding for specific vector geometries:
12477  *
12478  * ```js
12479  * var map = L.map('map');
12480  * var myRenderer = L.svg({ padding: 0.5 });
12481  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12482  * var circle = L.circle( center, { renderer: myRenderer } );
12483  * ```
12484  */
12485
12486 var SVG = Renderer.extend({
12487
12488         getEvents: function () {
12489                 var events = Renderer.prototype.getEvents.call(this);
12490                 events.zoomstart = this._onZoomStart;
12491                 return events;
12492         },
12493
12494         _initContainer: function () {
12495                 this._container = create$2('svg');
12496
12497                 // makes it possible to click through svg root; we'll reset it back in individual paths
12498                 this._container.setAttribute('pointer-events', 'none');
12499
12500                 this._rootGroup = create$2('g');
12501                 this._container.appendChild(this._rootGroup);
12502         },
12503
12504         _destroyContainer: function () {
12505                 remove(this._container);
12506                 off(this._container);
12507                 delete this._container;
12508                 delete this._rootGroup;
12509                 delete this._svgSize;
12510         },
12511
12512         _onZoomStart: function () {
12513                 // Drag-then-pinch interactions might mess up the center and zoom.
12514                 // In this case, the easiest way to prevent this is re-do the renderer
12515                 //   bounds and padding when the zooming starts.
12516                 this._update();
12517         },
12518
12519         _update: function () {
12520                 if (this._map._animatingZoom && this._bounds) { return; }
12521
12522                 Renderer.prototype._update.call(this);
12523
12524                 var b = this._bounds,
12525                     size = b.getSize(),
12526                     container = this._container;
12527
12528                 // set size of svg-container if changed
12529                 if (!this._svgSize || !this._svgSize.equals(size)) {
12530                         this._svgSize = size;
12531                         container.setAttribute('width', size.x);
12532                         container.setAttribute('height', size.y);
12533                 }
12534
12535                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12536                 setPosition(container, b.min);
12537                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12538
12539                 this.fire('update');
12540         },
12541
12542         // methods below are called by vector layers implementations
12543
12544         _initPath: function (layer) {
12545                 var path = layer._path = create$2('path');
12546
12547                 // @namespace Path
12548                 // @option className: String = null
12549                 // Custom class name set on an element. Only for SVG renderer.
12550                 if (layer.options.className) {
12551                         addClass(path, layer.options.className);
12552                 }
12553
12554                 if (layer.options.interactive) {
12555                         addClass(path, 'leaflet-interactive');
12556                 }
12557
12558                 this._updateStyle(layer);
12559                 this._layers[stamp(layer)] = layer;
12560         },
12561
12562         _addPath: function (layer) {
12563                 if (!this._rootGroup) { this._initContainer(); }
12564                 this._rootGroup.appendChild(layer._path);
12565                 layer.addInteractiveTarget(layer._path);
12566         },
12567
12568         _removePath: function (layer) {
12569                 remove(layer._path);
12570                 layer.removeInteractiveTarget(layer._path);
12571                 delete this._layers[stamp(layer)];
12572         },
12573
12574         _updatePath: function (layer) {
12575                 layer._project();
12576                 layer._update();
12577         },
12578
12579         _updateStyle: function (layer) {
12580                 var path = layer._path,
12581                     options = layer.options;
12582
12583                 if (!path) { return; }
12584
12585                 if (options.stroke) {
12586                         path.setAttribute('stroke', options.color);
12587                         path.setAttribute('stroke-opacity', options.opacity);
12588                         path.setAttribute('stroke-width', options.weight);
12589                         path.setAttribute('stroke-linecap', options.lineCap);
12590                         path.setAttribute('stroke-linejoin', options.lineJoin);
12591
12592                         if (options.dashArray) {
12593                                 path.setAttribute('stroke-dasharray', options.dashArray);
12594                         } else {
12595                                 path.removeAttribute('stroke-dasharray');
12596                         }
12597
12598                         if (options.dashOffset) {
12599                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
12600                         } else {
12601                                 path.removeAttribute('stroke-dashoffset');
12602                         }
12603                 } else {
12604                         path.setAttribute('stroke', 'none');
12605                 }
12606
12607                 if (options.fill) {
12608                         path.setAttribute('fill', options.fillColor || options.color);
12609                         path.setAttribute('fill-opacity', options.fillOpacity);
12610                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12611                 } else {
12612                         path.setAttribute('fill', 'none');
12613                 }
12614         },
12615
12616         _updatePoly: function (layer, closed) {
12617                 this._setPath(layer, pointsToPath(layer._parts, closed));
12618         },
12619
12620         _updateCircle: function (layer) {
12621                 var p = layer._point,
12622                     r = Math.max(Math.round(layer._radius), 1),
12623                     r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12624                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12625
12626                 // drawing a circle with two half-arcs
12627                 var d = layer._empty() ? 'M0 0' :
12628                         'M' + (p.x - r) + ',' + p.y +
12629                         arc + (r * 2) + ',0 ' +
12630                         arc + (-r * 2) + ',0 ';
12631
12632                 this._setPath(layer, d);
12633         },
12634
12635         _setPath: function (layer, path) {
12636                 layer._path.setAttribute('d', path);
12637         },
12638
12639         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12640         _bringToFront: function (layer) {
12641                 toFront(layer._path);
12642         },
12643
12644         _bringToBack: function (layer) {
12645                 toBack(layer._path);
12646         }
12647 });
12648
12649 if (vml) {
12650         SVG.include(vmlMixin);
12651 }
12652
12653 // @namespace SVG
12654 // @factory L.svg(options?: Renderer options)
12655 // Creates a SVG renderer with the given options.
12656 function svg$1(options) {
12657         return svg || vml ? new SVG(options) : null;
12658 }
12659
12660 Map.include({
12661         // @namespace Map; @method getRenderer(layer: Path): Renderer
12662         // Returns the instance of `Renderer` that should be used to render the given
12663         // `Path`. It will ensure that the `renderer` options of the map and paths
12664         // are respected, and that the renderers do exist on the map.
12665         getRenderer: function (layer) {
12666                 // @namespace Path; @option renderer: Renderer
12667                 // Use this specific instance of `Renderer` for this path. Takes
12668                 // precedence over the map's [default renderer](#map-renderer).
12669                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12670
12671                 if (!renderer) {
12672                         // @namespace Map; @option preferCanvas: Boolean = false
12673                         // Whether `Path`s should be rendered on a `Canvas` renderer.
12674                         // By default, all `Path`s are rendered in a `SVG` renderer.
12675                         renderer = this._renderer = (this.options.preferCanvas && canvas$1()) || svg$1();
12676                 }
12677
12678                 if (!this.hasLayer(renderer)) {
12679                         this.addLayer(renderer);
12680                 }
12681                 return renderer;
12682         },
12683
12684         _getPaneRenderer: function (name) {
12685                 if (name === 'overlayPane' || name === undefined) {
12686                         return false;
12687                 }
12688
12689                 var renderer = this._paneRenderers[name];
12690                 if (renderer === undefined) {
12691                         renderer = (SVG && svg$1({pane: name})) || (Canvas && canvas$1({pane: name}));
12692                         this._paneRenderers[name] = renderer;
12693                 }
12694                 return renderer;
12695         }
12696 });
12697
12698 /*
12699  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12700  */
12701
12702 /*
12703  * @class Rectangle
12704  * @aka L.Rectangle
12705  * @inherits Polygon
12706  *
12707  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12708  *
12709  * @example
12710  *
12711  * ```js
12712  * // define rectangle geographical bounds
12713  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12714  *
12715  * // create an orange rectangle
12716  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12717  *
12718  * // zoom the map to the rectangle bounds
12719  * map.fitBounds(bounds);
12720  * ```
12721  *
12722  */
12723
12724
12725 var Rectangle = Polygon.extend({
12726         initialize: function (latLngBounds, options) {
12727                 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12728         },
12729
12730         // @method setBounds(latLngBounds: LatLngBounds): this
12731         // Redraws the rectangle with the passed bounds.
12732         setBounds: function (latLngBounds) {
12733                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12734         },
12735
12736         _boundsToLatLngs: function (latLngBounds) {
12737                 latLngBounds = toLatLngBounds(latLngBounds);
12738                 return [
12739                         latLngBounds.getSouthWest(),
12740                         latLngBounds.getNorthWest(),
12741                         latLngBounds.getNorthEast(),
12742                         latLngBounds.getSouthEast()
12743                 ];
12744         }
12745 });
12746
12747
12748 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12749 function rectangle(latLngBounds, options) {
12750         return new Rectangle(latLngBounds, options);
12751 }
12752
12753 SVG.create = create$2;
12754 SVG.pointsToPath = pointsToPath;
12755
12756 GeoJSON.geometryToLayer = geometryToLayer;
12757 GeoJSON.coordsToLatLng = coordsToLatLng;
12758 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12759 GeoJSON.latLngToCoords = latLngToCoords;
12760 GeoJSON.latLngsToCoords = latLngsToCoords;
12761 GeoJSON.getFeature = getFeature;
12762 GeoJSON.asFeature = asFeature;
12763
12764 /*
12765  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12766  * (zoom to a selected bounding box), enabled by default.
12767  */
12768
12769 // @namespace Map
12770 // @section Interaction Options
12771 Map.mergeOptions({
12772         // @option boxZoom: Boolean = true
12773         // Whether the map can be zoomed to a rectangular area specified by
12774         // dragging the mouse while pressing the shift key.
12775         boxZoom: true
12776 });
12777
12778 var BoxZoom = Handler.extend({
12779         initialize: function (map) {
12780                 this._map = map;
12781                 this._container = map._container;
12782                 this._pane = map._panes.overlayPane;
12783                 this._resetStateTimeout = 0;
12784                 map.on('unload', this._destroy, this);
12785         },
12786
12787         addHooks: function () {
12788                 on(this._container, 'mousedown', this._onMouseDown, this);
12789         },
12790
12791         removeHooks: function () {
12792                 off(this._container, 'mousedown', this._onMouseDown, this);
12793         },
12794
12795         moved: function () {
12796                 return this._moved;
12797         },
12798
12799         _destroy: function () {
12800                 remove(this._pane);
12801                 delete this._pane;
12802         },
12803
12804         _resetState: function () {
12805                 this._resetStateTimeout = 0;
12806                 this._moved = false;
12807         },
12808
12809         _clearDeferredResetState: function () {
12810                 if (this._resetStateTimeout !== 0) {
12811                         clearTimeout(this._resetStateTimeout);
12812                         this._resetStateTimeout = 0;
12813                 }
12814         },
12815
12816         _onMouseDown: function (e) {
12817                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12818
12819                 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12820                 // will interrupt the interaction and orphan a box element in the container.
12821                 this._clearDeferredResetState();
12822                 this._resetState();
12823
12824                 disableTextSelection();
12825                 disableImageDrag();
12826
12827                 this._startPoint = this._map.mouseEventToContainerPoint(e);
12828
12829                 on(document, {
12830                         contextmenu: stop,
12831                         mousemove: this._onMouseMove,
12832                         mouseup: this._onMouseUp,
12833                         keydown: this._onKeyDown
12834                 }, this);
12835         },
12836
12837         _onMouseMove: function (e) {
12838                 if (!this._moved) {
12839                         this._moved = true;
12840
12841                         this._box = create$1('div', 'leaflet-zoom-box', this._container);
12842                         addClass(this._container, 'leaflet-crosshair');
12843
12844                         this._map.fire('boxzoomstart');
12845                 }
12846
12847                 this._point = this._map.mouseEventToContainerPoint(e);
12848
12849                 var bounds = new Bounds(this._point, this._startPoint),
12850                     size = bounds.getSize();
12851
12852                 setPosition(this._box, bounds.min);
12853
12854                 this._box.style.width  = size.x + 'px';
12855                 this._box.style.height = size.y + 'px';
12856         },
12857
12858         _finish: function () {
12859                 if (this._moved) {
12860                         remove(this._box);
12861                         removeClass(this._container, 'leaflet-crosshair');
12862                 }
12863
12864                 enableTextSelection();
12865                 enableImageDrag();
12866
12867                 off(document, {
12868                         contextmenu: stop,
12869                         mousemove: this._onMouseMove,
12870                         mouseup: this._onMouseUp,
12871                         keydown: this._onKeyDown
12872                 }, this);
12873         },
12874
12875         _onMouseUp: function (e) {
12876                 if ((e.which !== 1) && (e.button !== 1)) { return; }
12877
12878                 this._finish();
12879
12880                 if (!this._moved) { return; }
12881                 // Postpone to next JS tick so internal click event handling
12882                 // still see it as "moved".
12883                 this._clearDeferredResetState();
12884                 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
12885
12886                 var bounds = new LatLngBounds(
12887                         this._map.containerPointToLatLng(this._startPoint),
12888                         this._map.containerPointToLatLng(this._point));
12889
12890                 this._map
12891                         .fitBounds(bounds)
12892                         .fire('boxzoomend', {boxZoomBounds: bounds});
12893         },
12894
12895         _onKeyDown: function (e) {
12896                 if (e.keyCode === 27) {
12897                         this._finish();
12898                 }
12899         }
12900 });
12901
12902 // @section Handlers
12903 // @property boxZoom: Handler
12904 // Box (shift-drag with mouse) zoom handler.
12905 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
12906
12907 /*
12908  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
12909  */
12910
12911 // @namespace Map
12912 // @section Interaction Options
12913
12914 Map.mergeOptions({
12915         // @option doubleClickZoom: Boolean|String = true
12916         // Whether the map can be zoomed in by double clicking on it and
12917         // zoomed out by double clicking while holding shift. If passed
12918         // `'center'`, double-click zoom will zoom to the center of the
12919         //  view regardless of where the mouse was.
12920         doubleClickZoom: true
12921 });
12922
12923 var DoubleClickZoom = Handler.extend({
12924         addHooks: function () {
12925                 this._map.on('dblclick', this._onDoubleClick, this);
12926         },
12927
12928         removeHooks: function () {
12929                 this._map.off('dblclick', this._onDoubleClick, this);
12930         },
12931
12932         _onDoubleClick: function (e) {
12933                 var map = this._map,
12934                     oldZoom = map.getZoom(),
12935                     delta = map.options.zoomDelta,
12936                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
12937
12938                 if (map.options.doubleClickZoom === 'center') {
12939                         map.setZoom(zoom);
12940                 } else {
12941                         map.setZoomAround(e.containerPoint, zoom);
12942                 }
12943         }
12944 });
12945
12946 // @section Handlers
12947 //
12948 // Map properties include interaction handlers that allow you to control
12949 // interaction behavior in runtime, enabling or disabling certain features such
12950 // as dragging or touch zoom (see `Handler` methods). For example:
12951 //
12952 // ```js
12953 // map.doubleClickZoom.disable();
12954 // ```
12955 //
12956 // @property doubleClickZoom: Handler
12957 // Double click zoom handler.
12958 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
12959
12960 /*
12961  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
12962  */
12963
12964 // @namespace Map
12965 // @section Interaction Options
12966 Map.mergeOptions({
12967         // @option dragging: Boolean = true
12968         // Whether the map be draggable with mouse/touch or not.
12969         dragging: true,
12970
12971         // @section Panning Inertia Options
12972         // @option inertia: Boolean = *
12973         // If enabled, panning of the map will have an inertia effect where
12974         // the map builds momentum while dragging and continues moving in
12975         // the same direction for some time. Feels especially nice on touch
12976         // devices. Enabled by default unless running on old Android devices.
12977         inertia: !android23,
12978
12979         // @option inertiaDeceleration: Number = 3000
12980         // The rate with which the inertial movement slows down, in pixels/second².
12981         inertiaDeceleration: 3400, // px/s^2
12982
12983         // @option inertiaMaxSpeed: Number = Infinity
12984         // Max speed of the inertial movement, in pixels/second.
12985         inertiaMaxSpeed: Infinity, // px/s
12986
12987         // @option easeLinearity: Number = 0.2
12988         easeLinearity: 0.2,
12989
12990         // TODO refactor, move to CRS
12991         // @option worldCopyJump: Boolean = false
12992         // With this option enabled, the map tracks when you pan to another "copy"
12993         // of the world and seamlessly jumps to the original one so that all overlays
12994         // like markers and vector layers are still visible.
12995         worldCopyJump: false,
12996
12997         // @option maxBoundsViscosity: Number = 0.0
12998         // If `maxBounds` is set, this option will control how solid the bounds
12999         // are when dragging the map around. The default value of `0.0` allows the
13000         // user to drag outside the bounds at normal speed, higher values will
13001         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13002         // solid, preventing the user from dragging outside the bounds.
13003         maxBoundsViscosity: 0.0
13004 });
13005
13006 var Drag = Handler.extend({
13007         addHooks: function () {
13008                 if (!this._draggable) {
13009                         var map = this._map;
13010
13011                         this._draggable = new Draggable(map._mapPane, map._container);
13012
13013                         this._draggable.on({
13014                                 dragstart: this._onDragStart,
13015                                 drag: this._onDrag,
13016                                 dragend: this._onDragEnd
13017                         }, this);
13018
13019                         this._draggable.on('predrag', this._onPreDragLimit, this);
13020                         if (map.options.worldCopyJump) {
13021                                 this._draggable.on('predrag', this._onPreDragWrap, this);
13022                                 map.on('zoomend', this._onZoomEnd, this);
13023
13024                                 map.whenReady(this._onZoomEnd, this);
13025                         }
13026                 }
13027                 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13028                 this._draggable.enable();
13029                 this._positions = [];
13030                 this._times = [];
13031         },
13032
13033         removeHooks: function () {
13034                 removeClass(this._map._container, 'leaflet-grab');
13035                 removeClass(this._map._container, 'leaflet-touch-drag');
13036                 this._draggable.disable();
13037         },
13038
13039         moved: function () {
13040                 return this._draggable && this._draggable._moved;
13041         },
13042
13043         moving: function () {
13044                 return this._draggable && this._draggable._moving;
13045         },
13046
13047         _onDragStart: function () {
13048                 var map = this._map;
13049
13050                 map._stop();
13051                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13052                         var bounds = toLatLngBounds(this._map.options.maxBounds);
13053
13054                         this._offsetLimit = toBounds(
13055                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13056                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13057                                         .add(this._map.getSize()));
13058
13059                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13060                 } else {
13061                         this._offsetLimit = null;
13062                 }
13063
13064                 map
13065                     .fire('movestart')
13066                     .fire('dragstart');
13067
13068                 if (map.options.inertia) {
13069                         this._positions = [];
13070                         this._times = [];
13071                 }
13072         },
13073
13074         _onDrag: function (e) {
13075                 if (this._map.options.inertia) {
13076                         var time = this._lastTime = +new Date(),
13077                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13078
13079                         this._positions.push(pos);
13080                         this._times.push(time);
13081
13082                         this._prunePositions(time);
13083                 }
13084
13085                 this._map
13086                     .fire('move', e)
13087                     .fire('drag', e);
13088         },
13089
13090         _prunePositions: function (time) {
13091                 while (this._positions.length > 1 && time - this._times[0] > 50) {
13092                         this._positions.shift();
13093                         this._times.shift();
13094                 }
13095         },
13096
13097         _onZoomEnd: function () {
13098                 var pxCenter = this._map.getSize().divideBy(2),
13099                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13100
13101                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13102                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13103         },
13104
13105         _viscousLimit: function (value, threshold) {
13106                 return value - (value - threshold) * this._viscosity;
13107         },
13108
13109         _onPreDragLimit: function () {
13110                 if (!this._viscosity || !this._offsetLimit) { return; }
13111
13112                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13113
13114                 var limit = this._offsetLimit;
13115                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13116                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13117                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13118                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13119
13120                 this._draggable._newPos = this._draggable._startPos.add(offset);
13121         },
13122
13123         _onPreDragWrap: function () {
13124                 // TODO refactor to be able to adjust map pane position after zoom
13125                 var worldWidth = this._worldWidth,
13126                     halfWidth = Math.round(worldWidth / 2),
13127                     dx = this._initialWorldOffset,
13128                     x = this._draggable._newPos.x,
13129                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13130                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13131                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13132
13133                 this._draggable._absPos = this._draggable._newPos.clone();
13134                 this._draggable._newPos.x = newX;
13135         },
13136
13137         _onDragEnd: function (e) {
13138                 var map = this._map,
13139                     options = map.options,
13140
13141                     noInertia = !options.inertia || this._times.length < 2;
13142
13143                 map.fire('dragend', e);
13144
13145                 if (noInertia) {
13146                         map.fire('moveend');
13147
13148                 } else {
13149                         this._prunePositions(+new Date());
13150
13151                         var direction = this._lastPos.subtract(this._positions[0]),
13152                             duration = (this._lastTime - this._times[0]) / 1000,
13153                             ease = options.easeLinearity,
13154
13155                             speedVector = direction.multiplyBy(ease / duration),
13156                             speed = speedVector.distanceTo([0, 0]),
13157
13158                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13159                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13160
13161                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13162                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13163
13164                         if (!offset.x && !offset.y) {
13165                                 map.fire('moveend');
13166
13167                         } else {
13168                                 offset = map._limitOffset(offset, map.options.maxBounds);
13169
13170                                 requestAnimFrame(function () {
13171                                         map.panBy(offset, {
13172                                                 duration: decelerationDuration,
13173                                                 easeLinearity: ease,
13174                                                 noMoveStart: true,
13175                                                 animate: true
13176                                         });
13177                                 });
13178                         }
13179                 }
13180         }
13181 });
13182
13183 // @section Handlers
13184 // @property dragging: Handler
13185 // Map dragging handler (by both mouse and touch).
13186 Map.addInitHook('addHandler', 'dragging', Drag);
13187
13188 /*
13189  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13190  */
13191
13192 // @namespace Map
13193 // @section Keyboard Navigation Options
13194 Map.mergeOptions({
13195         // @option keyboard: Boolean = true
13196         // Makes the map focusable and allows users to navigate the map with keyboard
13197         // arrows and `+`/`-` keys.
13198         keyboard: true,
13199
13200         // @option keyboardPanDelta: Number = 80
13201         // Amount of pixels to pan when pressing an arrow key.
13202         keyboardPanDelta: 80
13203 });
13204
13205 var Keyboard = Handler.extend({
13206
13207         keyCodes: {
13208                 left:    [37],
13209                 right:   [39],
13210                 down:    [40],
13211                 up:      [38],
13212                 zoomIn:  [187, 107, 61, 171],
13213                 zoomOut: [189, 109, 54, 173]
13214         },
13215
13216         initialize: function (map) {
13217                 this._map = map;
13218
13219                 this._setPanDelta(map.options.keyboardPanDelta);
13220                 this._setZoomDelta(map.options.zoomDelta);
13221         },
13222
13223         addHooks: function () {
13224                 var container = this._map._container;
13225
13226                 // make the container focusable by tabbing
13227                 if (container.tabIndex <= 0) {
13228                         container.tabIndex = '0';
13229                 }
13230
13231                 on(container, {
13232                         focus: this._onFocus,
13233                         blur: this._onBlur,
13234                         mousedown: this._onMouseDown
13235                 }, this);
13236
13237                 this._map.on({
13238                         focus: this._addHooks,
13239                         blur: this._removeHooks
13240                 }, this);
13241         },
13242
13243         removeHooks: function () {
13244                 this._removeHooks();
13245
13246                 off(this._map._container, {
13247                         focus: this._onFocus,
13248                         blur: this._onBlur,
13249                         mousedown: this._onMouseDown
13250                 }, this);
13251
13252                 this._map.off({
13253                         focus: this._addHooks,
13254                         blur: this._removeHooks
13255                 }, this);
13256         },
13257
13258         _onMouseDown: function () {
13259                 if (this._focused) { return; }
13260
13261                 var body = document.body,
13262                     docEl = document.documentElement,
13263                     top = body.scrollTop || docEl.scrollTop,
13264                     left = body.scrollLeft || docEl.scrollLeft;
13265
13266                 this._map._container.focus();
13267
13268                 window.scrollTo(left, top);
13269         },
13270
13271         _onFocus: function () {
13272                 this._focused = true;
13273                 this._map.fire('focus');
13274         },
13275
13276         _onBlur: function () {
13277                 this._focused = false;
13278                 this._map.fire('blur');
13279         },
13280
13281         _setPanDelta: function (panDelta) {
13282                 var keys = this._panKeys = {},
13283                     codes = this.keyCodes,
13284                     i, len;
13285
13286                 for (i = 0, len = codes.left.length; i < len; i++) {
13287                         keys[codes.left[i]] = [-1 * panDelta, 0];
13288                 }
13289                 for (i = 0, len = codes.right.length; i < len; i++) {
13290                         keys[codes.right[i]] = [panDelta, 0];
13291                 }
13292                 for (i = 0, len = codes.down.length; i < len; i++) {
13293                         keys[codes.down[i]] = [0, panDelta];
13294                 }
13295                 for (i = 0, len = codes.up.length; i < len; i++) {
13296                         keys[codes.up[i]] = [0, -1 * panDelta];
13297                 }
13298         },
13299
13300         _setZoomDelta: function (zoomDelta) {
13301                 var keys = this._zoomKeys = {},
13302                     codes = this.keyCodes,
13303                     i, len;
13304
13305                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13306                         keys[codes.zoomIn[i]] = zoomDelta;
13307                 }
13308                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13309                         keys[codes.zoomOut[i]] = -zoomDelta;
13310                 }
13311         },
13312
13313         _addHooks: function () {
13314                 on(document, 'keydown', this._onKeyDown, this);
13315         },
13316
13317         _removeHooks: function () {
13318                 off(document, 'keydown', this._onKeyDown, this);
13319         },
13320
13321         _onKeyDown: function (e) {
13322                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13323
13324                 var key = e.keyCode,
13325                     map = this._map,
13326                     offset;
13327
13328                 if (key in this._panKeys) {
13329
13330                         if (map._panAnim && map._panAnim._inProgress) { return; }
13331
13332                         offset = this._panKeys[key];
13333                         if (e.shiftKey) {
13334                                 offset = toPoint(offset).multiplyBy(3);
13335                         }
13336
13337                         map.panBy(offset);
13338
13339                         if (map.options.maxBounds) {
13340                                 map.panInsideBounds(map.options.maxBounds);
13341                         }
13342
13343                 } else if (key in this._zoomKeys) {
13344                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13345
13346                 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13347                         map.closePopup();
13348
13349                 } else {
13350                         return;
13351                 }
13352
13353                 stop(e);
13354         }
13355 });
13356
13357 // @section Handlers
13358 // @section Handlers
13359 // @property keyboard: Handler
13360 // Keyboard navigation handler.
13361 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13362
13363 /*
13364  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13365  */
13366
13367 // @namespace Map
13368 // @section Interaction Options
13369 Map.mergeOptions({
13370         // @section Mousewheel options
13371         // @option scrollWheelZoom: Boolean|String = true
13372         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13373         // it will zoom to the center of the view regardless of where the mouse was.
13374         scrollWheelZoom: true,
13375
13376         // @option wheelDebounceTime: Number = 40
13377         // Limits the rate at which a wheel can fire (in milliseconds). By default
13378         // user can't zoom via wheel more often than once per 40 ms.
13379         wheelDebounceTime: 40,
13380
13381         // @option wheelPxPerZoomLevel: Number = 60
13382         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13383         // mean a change of one full zoom level. Smaller values will make wheel-zooming
13384         // faster (and vice versa).
13385         wheelPxPerZoomLevel: 60
13386 });
13387
13388 var ScrollWheelZoom = Handler.extend({
13389         addHooks: function () {
13390                 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13391
13392                 this._delta = 0;
13393         },
13394
13395         removeHooks: function () {
13396                 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13397         },
13398
13399         _onWheelScroll: function (e) {
13400                 var delta = getWheelDelta(e);
13401
13402                 var debounce = this._map.options.wheelDebounceTime;
13403
13404                 this._delta += delta;
13405                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13406
13407                 if (!this._startTime) {
13408                         this._startTime = +new Date();
13409                 }
13410
13411                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13412
13413                 clearTimeout(this._timer);
13414                 this._timer = setTimeout(bind(this._performZoom, this), left);
13415
13416                 stop(e);
13417         },
13418
13419         _performZoom: function () {
13420                 var map = this._map,
13421                     zoom = map.getZoom(),
13422                     snap = this._map.options.zoomSnap || 0;
13423
13424                 map._stop(); // stop panning and fly animations if any
13425
13426                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13427                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13428                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13429                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13430                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13431
13432                 this._delta = 0;
13433                 this._startTime = null;
13434
13435                 if (!delta) { return; }
13436
13437                 if (map.options.scrollWheelZoom === 'center') {
13438                         map.setZoom(zoom + delta);
13439                 } else {
13440                         map.setZoomAround(this._lastMousePos, zoom + delta);
13441                 }
13442         }
13443 });
13444
13445 // @section Handlers
13446 // @property scrollWheelZoom: Handler
13447 // Scroll wheel zoom handler.
13448 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13449
13450 /*
13451  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13452  */
13453
13454 // @namespace Map
13455 // @section Interaction Options
13456 Map.mergeOptions({
13457         // @section Touch interaction options
13458         // @option tap: Boolean = true
13459         // Enables mobile hacks for supporting instant taps (fixing 200ms click
13460         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13461         tap: true,
13462
13463         // @option tapTolerance: Number = 15
13464         // The max number of pixels a user can shift his finger during touch
13465         // for it to be considered a valid tap.
13466         tapTolerance: 15
13467 });
13468
13469 var Tap = Handler.extend({
13470         addHooks: function () {
13471                 on(this._map._container, 'touchstart', this._onDown, this);
13472         },
13473
13474         removeHooks: function () {
13475                 off(this._map._container, 'touchstart', this._onDown, this);
13476         },
13477
13478         _onDown: function (e) {
13479                 if (!e.touches) { return; }
13480
13481                 preventDefault(e);
13482
13483                 this._fireClick = true;
13484
13485                 // don't simulate click or track longpress if more than 1 touch
13486                 if (e.touches.length > 1) {
13487                         this._fireClick = false;
13488                         clearTimeout(this._holdTimeout);
13489                         return;
13490                 }
13491
13492                 var first = e.touches[0],
13493                     el = first.target;
13494
13495                 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13496
13497                 // if touching a link, highlight it
13498                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13499                         addClass(el, 'leaflet-active');
13500                 }
13501
13502                 // simulate long hold but setting a timeout
13503                 this._holdTimeout = setTimeout(bind(function () {
13504                         if (this._isTapValid()) {
13505                                 this._fireClick = false;
13506                                 this._onUp();
13507                                 this._simulateEvent('contextmenu', first);
13508                         }
13509                 }, this), 1000);
13510
13511                 this._simulateEvent('mousedown', first);
13512
13513                 on(document, {
13514                         touchmove: this._onMove,
13515                         touchend: this._onUp
13516                 }, this);
13517         },
13518
13519         _onUp: function (e) {
13520                 clearTimeout(this._holdTimeout);
13521
13522                 off(document, {
13523                         touchmove: this._onMove,
13524                         touchend: this._onUp
13525                 }, this);
13526
13527                 if (this._fireClick && e && e.changedTouches) {
13528
13529                         var first = e.changedTouches[0],
13530                             el = first.target;
13531
13532                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13533                                 removeClass(el, 'leaflet-active');
13534                         }
13535
13536                         this._simulateEvent('mouseup', first);
13537
13538                         // simulate click if the touch didn't move too much
13539                         if (this._isTapValid()) {
13540                                 this._simulateEvent('click', first);
13541                         }
13542                 }
13543         },
13544
13545         _isTapValid: function () {
13546                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13547         },
13548
13549         _onMove: function (e) {
13550                 var first = e.touches[0];
13551                 this._newPos = new Point(first.clientX, first.clientY);
13552                 this._simulateEvent('mousemove', first);
13553         },
13554
13555         _simulateEvent: function (type, e) {
13556                 var simulatedEvent = document.createEvent('MouseEvents');
13557
13558                 simulatedEvent._simulated = true;
13559                 e.target._simulatedClick = true;
13560
13561                 simulatedEvent.initMouseEvent(
13562                         type, true, true, window, 1,
13563                         e.screenX, e.screenY,
13564                         e.clientX, e.clientY,
13565                         false, false, false, false, 0, null);
13566
13567                 e.target.dispatchEvent(simulatedEvent);
13568         }
13569 });
13570
13571 // @section Handlers
13572 // @property tap: Handler
13573 // Mobile touch hacks (quick tap and touch hold) handler.
13574 if (touch && !pointer) {
13575         Map.addInitHook('addHandler', 'tap', Tap);
13576 }
13577
13578 /*
13579  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13580  */
13581
13582 // @namespace Map
13583 // @section Interaction Options
13584 Map.mergeOptions({
13585         // @section Touch interaction options
13586         // @option touchZoom: Boolean|String = *
13587         // Whether the map can be zoomed by touch-dragging with two fingers. If
13588         // passed `'center'`, it will zoom to the center of the view regardless of
13589         // where the touch events (fingers) were. Enabled for touch-capable web
13590         // browsers except for old Androids.
13591         touchZoom: touch && !android23,
13592
13593         // @option bounceAtZoomLimits: Boolean = true
13594         // Set it to false if you don't want the map to zoom beyond min/max zoom
13595         // and then bounce back when pinch-zooming.
13596         bounceAtZoomLimits: true
13597 });
13598
13599 var TouchZoom = Handler.extend({
13600         addHooks: function () {
13601                 addClass(this._map._container, 'leaflet-touch-zoom');
13602                 on(this._map._container, 'touchstart', this._onTouchStart, this);
13603         },
13604
13605         removeHooks: function () {
13606                 removeClass(this._map._container, 'leaflet-touch-zoom');
13607                 off(this._map._container, 'touchstart', this._onTouchStart, this);
13608         },
13609
13610         _onTouchStart: function (e) {
13611                 var map = this._map;
13612                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13613
13614                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13615                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
13616
13617                 this._centerPoint = map.getSize()._divideBy(2);
13618                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13619                 if (map.options.touchZoom !== 'center') {
13620                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13621                 }
13622
13623                 this._startDist = p1.distanceTo(p2);
13624                 this._startZoom = map.getZoom();
13625
13626                 this._moved = false;
13627                 this._zooming = true;
13628
13629                 map._stop();
13630
13631                 on(document, 'touchmove', this._onTouchMove, this);
13632                 on(document, 'touchend', this._onTouchEnd, this);
13633
13634                 preventDefault(e);
13635         },
13636
13637         _onTouchMove: function (e) {
13638                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13639
13640                 var map = this._map,
13641                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
13642                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
13643                     scale = p1.distanceTo(p2) / this._startDist;
13644
13645                 this._zoom = map.getScaleZoom(scale, this._startZoom);
13646
13647                 if (!map.options.bounceAtZoomLimits && (
13648                         (this._zoom < map.getMinZoom() && scale < 1) ||
13649                         (this._zoom > map.getMaxZoom() && scale > 1))) {
13650                         this._zoom = map._limitZoom(this._zoom);
13651                 }
13652
13653                 if (map.options.touchZoom === 'center') {
13654                         this._center = this._startLatLng;
13655                         if (scale === 1) { return; }
13656                 } else {
13657                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13658                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13659                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13660                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13661                 }
13662
13663                 if (!this._moved) {
13664                         map._moveStart(true, false);
13665                         this._moved = true;
13666                 }
13667
13668                 cancelAnimFrame(this._animRequest);
13669
13670                 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13671                 this._animRequest = requestAnimFrame(moveFn, this, true);
13672
13673                 preventDefault(e);
13674         },
13675
13676         _onTouchEnd: function () {
13677                 if (!this._moved || !this._zooming) {
13678                         this._zooming = false;
13679                         return;
13680                 }
13681
13682                 this._zooming = false;
13683                 cancelAnimFrame(this._animRequest);
13684
13685                 off(document, 'touchmove', this._onTouchMove);
13686                 off(document, 'touchend', this._onTouchEnd);
13687
13688                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13689                 if (this._map.options.zoomAnimation) {
13690                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13691                 } else {
13692                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13693                 }
13694         }
13695 });
13696
13697 // @section Handlers
13698 // @property touchZoom: Handler
13699 // Touch zoom handler.
13700 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13701
13702 Map.BoxZoom = BoxZoom;
13703 Map.DoubleClickZoom = DoubleClickZoom;
13704 Map.Drag = Drag;
13705 Map.Keyboard = Keyboard;
13706 Map.ScrollWheelZoom = ScrollWheelZoom;
13707 Map.Tap = Tap;
13708 Map.TouchZoom = TouchZoom;
13709
13710 // misc
13711
13712 var oldL = window.L;
13713 function noConflict() {
13714         window.L = oldL;
13715         return this;
13716 }
13717
13718 // Always export us to window global (see #2364)
13719 window.L = exports;
13720
13721 Object.freeze = freeze;
13722
13723 exports.version = version;
13724 exports.noConflict = noConflict;
13725 exports.Control = Control;
13726 exports.control = control;
13727 exports.Browser = Browser;
13728 exports.Evented = Evented;
13729 exports.Mixin = Mixin;
13730 exports.Util = Util;
13731 exports.Class = Class;
13732 exports.Handler = Handler;
13733 exports.extend = extend;
13734 exports.bind = bind;
13735 exports.stamp = stamp;
13736 exports.setOptions = setOptions;
13737 exports.DomEvent = DomEvent;
13738 exports.DomUtil = DomUtil;
13739 exports.PosAnimation = PosAnimation;
13740 exports.Draggable = Draggable;
13741 exports.LineUtil = LineUtil;
13742 exports.PolyUtil = PolyUtil;
13743 exports.Point = Point;
13744 exports.point = toPoint;
13745 exports.Bounds = Bounds;
13746 exports.bounds = toBounds;
13747 exports.Transformation = Transformation;
13748 exports.transformation = toTransformation;
13749 exports.Projection = index;
13750 exports.LatLng = LatLng;
13751 exports.latLng = toLatLng;
13752 exports.LatLngBounds = LatLngBounds;
13753 exports.latLngBounds = toLatLngBounds;
13754 exports.CRS = CRS;
13755 exports.GeoJSON = GeoJSON;
13756 exports.geoJSON = geoJSON;
13757 exports.geoJson = geoJson;
13758 exports.Layer = Layer;
13759 exports.LayerGroup = LayerGroup;
13760 exports.layerGroup = layerGroup;
13761 exports.FeatureGroup = FeatureGroup;
13762 exports.featureGroup = featureGroup;
13763 exports.ImageOverlay = ImageOverlay;
13764 exports.imageOverlay = imageOverlay;
13765 exports.VideoOverlay = VideoOverlay;
13766 exports.videoOverlay = videoOverlay;
13767 exports.DivOverlay = DivOverlay;
13768 exports.Popup = Popup;
13769 exports.popup = popup;
13770 exports.Tooltip = Tooltip;
13771 exports.tooltip = tooltip;
13772 exports.Icon = Icon;
13773 exports.icon = icon;
13774 exports.DivIcon = DivIcon;
13775 exports.divIcon = divIcon;
13776 exports.Marker = Marker;
13777 exports.marker = marker;
13778 exports.TileLayer = TileLayer;
13779 exports.tileLayer = tileLayer;
13780 exports.GridLayer = GridLayer;
13781 exports.gridLayer = gridLayer;
13782 exports.SVG = SVG;
13783 exports.svg = svg$1;
13784 exports.Renderer = Renderer;
13785 exports.Canvas = Canvas;
13786 exports.canvas = canvas$1;
13787 exports.Path = Path;
13788 exports.CircleMarker = CircleMarker;
13789 exports.circleMarker = circleMarker;
13790 exports.Circle = Circle;
13791 exports.circle = circle;
13792 exports.Polyline = Polyline;
13793 exports.polyline = polyline;
13794 exports.Polygon = Polygon;
13795 exports.polygon = polygon;
13796 exports.Rectangle = Rectangle;
13797 exports.rectangle = rectangle;
13798 exports.Map = Map;
13799 exports.map = createMap;
13800
13801 })));
13802 //# sourceMappingURL=leaflet-src.js.map