]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
Move application.yml check to config initializer
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /* @preserve
2  * Leaflet 1.4.0, a JS library for interactive maps. http://leafletjs.com
3  * (c) 2010-2018 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.4.0";
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 Leaflet'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 or on any screen when browser's display zoom is more than 100%.
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 DomUtil
2211  *
2212  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2213  * tree, used by Leaflet internally.
2214  *
2215  * Most functions expecting or returning a `HTMLElement` also work for
2216  * SVG elements. The only difference is that classes refer to CSS classes
2217  * in HTML and SVG classes in SVG.
2218  */
2219
2220
2221 // @property TRANSFORM: String
2222 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2223 var TRANSFORM = testProp(
2224         ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2225
2226 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2227 // the same for the transitionend event, in particular the Android 4.1 stock browser
2228
2229 // @property TRANSITION: String
2230 // Vendor-prefixed transition style name.
2231 var TRANSITION = testProp(
2232         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2233
2234 // @property TRANSITION_END: String
2235 // Vendor-prefixed transitionend event name.
2236 var TRANSITION_END =
2237         TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2238
2239
2240 // @function get(id: String|HTMLElement): HTMLElement
2241 // Returns an element given its DOM id, or returns the element itself
2242 // if it was passed directly.
2243 function get(id) {
2244         return typeof id === 'string' ? document.getElementById(id) : id;
2245 }
2246
2247 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2248 // Returns the value for a certain style attribute on an element,
2249 // including computed values or values set through CSS.
2250 function getStyle(el, style) {
2251         var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2252
2253         if ((!value || value === 'auto') && document.defaultView) {
2254                 var css = document.defaultView.getComputedStyle(el, null);
2255                 value = css ? css[style] : null;
2256         }
2257         return value === 'auto' ? null : value;
2258 }
2259
2260 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2261 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2262 function create$1(tagName, className, container) {
2263         var el = document.createElement(tagName);
2264         el.className = className || '';
2265
2266         if (container) {
2267                 container.appendChild(el);
2268         }
2269         return el;
2270 }
2271
2272 // @function remove(el: HTMLElement)
2273 // Removes `el` from its parent element
2274 function remove(el) {
2275         var parent = el.parentNode;
2276         if (parent) {
2277                 parent.removeChild(el);
2278         }
2279 }
2280
2281 // @function empty(el: HTMLElement)
2282 // Removes all of `el`'s children elements from `el`
2283 function empty(el) {
2284         while (el.firstChild) {
2285                 el.removeChild(el.firstChild);
2286         }
2287 }
2288
2289 // @function toFront(el: HTMLElement)
2290 // Makes `el` the last child of its parent, so it renders in front of the other children.
2291 function toFront(el) {
2292         var parent = el.parentNode;
2293         if (parent && parent.lastChild !== el) {
2294                 parent.appendChild(el);
2295         }
2296 }
2297
2298 // @function toBack(el: HTMLElement)
2299 // Makes `el` the first child of its parent, so it renders behind the other children.
2300 function toBack(el) {
2301         var parent = el.parentNode;
2302         if (parent && parent.firstChild !== el) {
2303                 parent.insertBefore(el, parent.firstChild);
2304         }
2305 }
2306
2307 // @function hasClass(el: HTMLElement, name: String): Boolean
2308 // Returns `true` if the element's class attribute contains `name`.
2309 function hasClass(el, name) {
2310         if (el.classList !== undefined) {
2311                 return el.classList.contains(name);
2312         }
2313         var className = getClass(el);
2314         return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2315 }
2316
2317 // @function addClass(el: HTMLElement, name: String)
2318 // Adds `name` to the element's class attribute.
2319 function addClass(el, name) {
2320         if (el.classList !== undefined) {
2321                 var classes = splitWords(name);
2322                 for (var i = 0, len = classes.length; i < len; i++) {
2323                         el.classList.add(classes[i]);
2324                 }
2325         } else if (!hasClass(el, name)) {
2326                 var className = getClass(el);
2327                 setClass(el, (className ? className + ' ' : '') + name);
2328         }
2329 }
2330
2331 // @function removeClass(el: HTMLElement, name: String)
2332 // Removes `name` from the element's class attribute.
2333 function removeClass(el, name) {
2334         if (el.classList !== undefined) {
2335                 el.classList.remove(name);
2336         } else {
2337                 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2338         }
2339 }
2340
2341 // @function setClass(el: HTMLElement, name: String)
2342 // Sets the element's class.
2343 function setClass(el, name) {
2344         if (el.className.baseVal === undefined) {
2345                 el.className = name;
2346         } else {
2347                 // in case of SVG element
2348                 el.className.baseVal = name;
2349         }
2350 }
2351
2352 // @function getClass(el: HTMLElement): String
2353 // Returns the element's class.
2354 function getClass(el) {
2355         // Check if the element is an SVGElementInstance and use the correspondingElement instead
2356         // (Required for linked SVG elements in IE11.)
2357         if (el.correspondingElement) {
2358                 el = el.correspondingElement;
2359         }
2360         return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2361 }
2362
2363 // @function setOpacity(el: HTMLElement, opacity: Number)
2364 // Set the opacity of an element (including old IE support).
2365 // `opacity` must be a number from `0` to `1`.
2366 function setOpacity(el, value) {
2367         if ('opacity' in el.style) {
2368                 el.style.opacity = value;
2369         } else if ('filter' in el.style) {
2370                 _setOpacityIE(el, value);
2371         }
2372 }
2373
2374 function _setOpacityIE(el, value) {
2375         var filter = false,
2376             filterName = 'DXImageTransform.Microsoft.Alpha';
2377
2378         // filters collection throws an error if we try to retrieve a filter that doesn't exist
2379         try {
2380                 filter = el.filters.item(filterName);
2381         } catch (e) {
2382                 // don't set opacity to 1 if we haven't already set an opacity,
2383                 // it isn't needed and breaks transparent pngs.
2384                 if (value === 1) { return; }
2385         }
2386
2387         value = Math.round(value * 100);
2388
2389         if (filter) {
2390                 filter.Enabled = (value !== 100);
2391                 filter.Opacity = value;
2392         } else {
2393                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2394         }
2395 }
2396
2397 // @function testProp(props: String[]): String|false
2398 // Goes through the array of style names and returns the first name
2399 // that is a valid style name for an element. If no such name is found,
2400 // it returns false. Useful for vendor-prefixed styles like `transform`.
2401 function testProp(props) {
2402         var style = document.documentElement.style;
2403
2404         for (var i = 0; i < props.length; i++) {
2405                 if (props[i] in style) {
2406                         return props[i];
2407                 }
2408         }
2409         return false;
2410 }
2411
2412 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2413 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2414 // and optionally scaled by `scale`. Does not have an effect if the
2415 // browser doesn't support 3D CSS transforms.
2416 function setTransform(el, offset, scale) {
2417         var pos = offset || new Point(0, 0);
2418
2419         el.style[TRANSFORM] =
2420                 (ie3d ?
2421                         'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2422                         'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2423                 (scale ? ' scale(' + scale + ')' : '');
2424 }
2425
2426 // @function setPosition(el: HTMLElement, position: Point)
2427 // Sets the position of `el` to coordinates specified by `position`,
2428 // using CSS translate or top/left positioning depending on the browser
2429 // (used by Leaflet internally to position its layers).
2430 function setPosition(el, point) {
2431
2432         /*eslint-disable */
2433         el._leaflet_pos = point;
2434         /* eslint-enable */
2435
2436         if (any3d) {
2437                 setTransform(el, point);
2438         } else {
2439                 el.style.left = point.x + 'px';
2440                 el.style.top = point.y + 'px';
2441         }
2442 }
2443
2444 // @function getPosition(el: HTMLElement): Point
2445 // Returns the coordinates of an element previously positioned with setPosition.
2446 function getPosition(el) {
2447         // this method is only used for elements previously positioned using setPosition,
2448         // so it's safe to cache the position for performance
2449
2450         return el._leaflet_pos || new Point(0, 0);
2451 }
2452
2453 // @function disableTextSelection()
2454 // Prevents the user from generating `selectstart` DOM events, usually generated
2455 // when the user drags the mouse through a page with text. Used internally
2456 // by Leaflet to override the behaviour of any click-and-drag interaction on
2457 // the map. Affects drag interactions on the whole document.
2458
2459 // @function enableTextSelection()
2460 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2461 var disableTextSelection;
2462 var enableTextSelection;
2463 var _userSelect;
2464 if ('onselectstart' in document) {
2465         disableTextSelection = function () {
2466                 on(window, 'selectstart', preventDefault);
2467         };
2468         enableTextSelection = function () {
2469                 off(window, 'selectstart', preventDefault);
2470         };
2471 } else {
2472         var userSelectProperty = testProp(
2473                 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2474
2475         disableTextSelection = function () {
2476                 if (userSelectProperty) {
2477                         var style = document.documentElement.style;
2478                         _userSelect = style[userSelectProperty];
2479                         style[userSelectProperty] = 'none';
2480                 }
2481         };
2482         enableTextSelection = function () {
2483                 if (userSelectProperty) {
2484                         document.documentElement.style[userSelectProperty] = _userSelect;
2485                         _userSelect = undefined;
2486                 }
2487         };
2488 }
2489
2490 // @function disableImageDrag()
2491 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2492 // for `dragstart` DOM events, usually generated when the user drags an image.
2493 function disableImageDrag() {
2494         on(window, 'dragstart', preventDefault);
2495 }
2496
2497 // @function enableImageDrag()
2498 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2499 function enableImageDrag() {
2500         off(window, 'dragstart', preventDefault);
2501 }
2502
2503 var _outlineElement;
2504 var _outlineStyle;
2505 // @function preventOutline(el: HTMLElement)
2506 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2507 // of the element `el` invisible. Used internally by Leaflet to prevent
2508 // focusable elements from displaying an outline when the user performs a
2509 // drag interaction on them.
2510 function preventOutline(element) {
2511         while (element.tabIndex === -1) {
2512                 element = element.parentNode;
2513         }
2514         if (!element.style) { return; }
2515         restoreOutline();
2516         _outlineElement = element;
2517         _outlineStyle = element.style.outline;
2518         element.style.outline = 'none';
2519         on(window, 'keydown', restoreOutline);
2520 }
2521
2522 // @function restoreOutline()
2523 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2524 function restoreOutline() {
2525         if (!_outlineElement) { return; }
2526         _outlineElement.style.outline = _outlineStyle;
2527         _outlineElement = undefined;
2528         _outlineStyle = undefined;
2529         off(window, 'keydown', restoreOutline);
2530 }
2531
2532 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2533 // Finds the closest parent node which size (width and height) is not null.
2534 function getSizedParentNode(element) {
2535         do {
2536                 element = element.parentNode;
2537         } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2538         return element;
2539 }
2540
2541 // @function getScale(el: HTMLElement): Object
2542 // Computes the CSS scale currently applied on the element.
2543 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2544 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2545 function getScale(element) {
2546         var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2547
2548         return {
2549                 x: rect.width / element.offsetWidth || 1,
2550                 y: rect.height / element.offsetHeight || 1,
2551                 boundingClientRect: rect
2552         };
2553 }
2554
2555
2556 var DomUtil = (Object.freeze || Object)({
2557         TRANSFORM: TRANSFORM,
2558         TRANSITION: TRANSITION,
2559         TRANSITION_END: TRANSITION_END,
2560         get: get,
2561         getStyle: getStyle,
2562         create: create$1,
2563         remove: remove,
2564         empty: empty,
2565         toFront: toFront,
2566         toBack: toBack,
2567         hasClass: hasClass,
2568         addClass: addClass,
2569         removeClass: removeClass,
2570         setClass: setClass,
2571         getClass: getClass,
2572         setOpacity: setOpacity,
2573         testProp: testProp,
2574         setTransform: setTransform,
2575         setPosition: setPosition,
2576         getPosition: getPosition,
2577         disableTextSelection: disableTextSelection,
2578         enableTextSelection: enableTextSelection,
2579         disableImageDrag: disableImageDrag,
2580         enableImageDrag: enableImageDrag,
2581         preventOutline: preventOutline,
2582         restoreOutline: restoreOutline,
2583         getSizedParentNode: getSizedParentNode,
2584         getScale: getScale
2585 });
2586
2587 /*
2588  * @namespace DomEvent
2589  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2590  */
2591
2592 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2593
2594 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2595 // Adds a listener function (`fn`) to a particular DOM event type of the
2596 // element `el`. You can optionally specify the context of the listener
2597 // (object the `this` keyword will point to). You can also pass several
2598 // space-separated types (e.g. `'click dblclick'`).
2599
2600 // @alternative
2601 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2602 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2603 function on(obj, types, fn, context) {
2604
2605         if (typeof types === 'object') {
2606                 for (var type in types) {
2607                         addOne(obj, type, types[type], fn);
2608                 }
2609         } else {
2610                 types = splitWords(types);
2611
2612                 for (var i = 0, len = types.length; i < len; i++) {
2613                         addOne(obj, types[i], fn, context);
2614                 }
2615         }
2616
2617         return this;
2618 }
2619
2620 var eventsKey = '_leaflet_events';
2621
2622 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2623 // Removes a previously added listener function.
2624 // Note that if you passed a custom context to on, you must pass the same
2625 // context to `off` in order to remove the listener.
2626
2627 // @alternative
2628 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2629 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2630 function off(obj, types, fn, context) {
2631
2632         if (typeof types === 'object') {
2633                 for (var type in types) {
2634                         removeOne(obj, type, types[type], fn);
2635                 }
2636         } else if (types) {
2637                 types = splitWords(types);
2638
2639                 for (var i = 0, len = types.length; i < len; i++) {
2640                         removeOne(obj, types[i], fn, context);
2641                 }
2642         } else {
2643                 for (var j in obj[eventsKey]) {
2644                         removeOne(obj, j, obj[eventsKey][j]);
2645                 }
2646                 delete obj[eventsKey];
2647         }
2648
2649         return this;
2650 }
2651
2652 function addOne(obj, type, fn, context) {
2653         var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2654
2655         if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2656
2657         var handler = function (e) {
2658                 return fn.call(context || obj, e || window.event);
2659         };
2660
2661         var originalHandler = handler;
2662
2663         if (pointer && type.indexOf('touch') === 0) {
2664                 // Needs DomEvent.Pointer.js
2665                 addPointerListener(obj, type, handler, id);
2666
2667         } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2668                    !(pointer && chrome)) {
2669                 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2670                 // See #5180
2671                 addDoubleTapListener(obj, handler, id);
2672
2673         } else if ('addEventListener' in obj) {
2674
2675                 if (type === 'mousewheel') {
2676                         obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2677
2678                 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2679                         handler = function (e) {
2680                                 e = e || window.event;
2681                                 if (isExternalTarget(obj, e)) {
2682                                         originalHandler(e);
2683                                 }
2684                         };
2685                         obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2686
2687                 } else {
2688                         if (type === 'click' && android) {
2689                                 handler = function (e) {
2690                                         filterClick(e, originalHandler);
2691                                 };
2692                         }
2693                         obj.addEventListener(type, handler, false);
2694                 }
2695
2696         } else if ('attachEvent' in obj) {
2697                 obj.attachEvent('on' + type, handler);
2698         }
2699
2700         obj[eventsKey] = obj[eventsKey] || {};
2701         obj[eventsKey][id] = handler;
2702 }
2703
2704 function removeOne(obj, type, fn, context) {
2705
2706         var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2707             handler = obj[eventsKey] && obj[eventsKey][id];
2708
2709         if (!handler) { return this; }
2710
2711         if (pointer && type.indexOf('touch') === 0) {
2712                 removePointerListener(obj, type, id);
2713
2714         } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2715                    !(pointer && chrome)) {
2716                 removeDoubleTapListener(obj, id);
2717
2718         } else if ('removeEventListener' in obj) {
2719
2720                 if (type === 'mousewheel') {
2721                         obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2722
2723                 } else {
2724                         obj.removeEventListener(
2725                                 type === 'mouseenter' ? 'mouseover' :
2726                                 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2727                 }
2728
2729         } else if ('detachEvent' in obj) {
2730                 obj.detachEvent('on' + type, handler);
2731         }
2732
2733         obj[eventsKey][id] = null;
2734 }
2735
2736 // @function stopPropagation(ev: DOMEvent): this
2737 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2738 // ```js
2739 // L.DomEvent.on(div, 'click', function (ev) {
2740 //      L.DomEvent.stopPropagation(ev);
2741 // });
2742 // ```
2743 function stopPropagation(e) {
2744
2745         if (e.stopPropagation) {
2746                 e.stopPropagation();
2747         } else if (e.originalEvent) {  // In case of Leaflet event.
2748                 e.originalEvent._stopped = true;
2749         } else {
2750                 e.cancelBubble = true;
2751         }
2752         skipped(e);
2753
2754         return this;
2755 }
2756
2757 // @function disableScrollPropagation(el: HTMLElement): this
2758 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2759 function disableScrollPropagation(el) {
2760         addOne(el, 'mousewheel', stopPropagation);
2761         return this;
2762 }
2763
2764 // @function disableClickPropagation(el: HTMLElement): this
2765 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2766 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2767 function disableClickPropagation(el) {
2768         on(el, 'mousedown touchstart dblclick', stopPropagation);
2769         addOne(el, 'click', fakeStop);
2770         return this;
2771 }
2772
2773 // @function preventDefault(ev: DOMEvent): this
2774 // Prevents the default action of the DOM Event `ev` from happening (such as
2775 // following a link in the href of the a element, or doing a POST request
2776 // with page reload when a `<form>` is submitted).
2777 // Use it inside listener functions.
2778 function preventDefault(e) {
2779         if (e.preventDefault) {
2780                 e.preventDefault();
2781         } else {
2782                 e.returnValue = false;
2783         }
2784         return this;
2785 }
2786
2787 // @function stop(ev: DOMEvent): this
2788 // Does `stopPropagation` and `preventDefault` at the same time.
2789 function stop(e) {
2790         preventDefault(e);
2791         stopPropagation(e);
2792         return this;
2793 }
2794
2795 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2796 // Gets normalized mouse position from a DOM event relative to the
2797 // `container` (border excluded) or to the whole page if not specified.
2798 function getMousePosition(e, container) {
2799         if (!container) {
2800                 return new Point(e.clientX, e.clientY);
2801         }
2802
2803         var scale = getScale(container),
2804             offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)
2805
2806         return new Point(
2807                 // offset.left/top values are in page scale (like clientX/Y),
2808                 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2809                 (e.clientX - offset.left) / scale.x - container.clientLeft,
2810                 (e.clientY - offset.top) / scale.y - container.clientTop
2811         );
2812 }
2813
2814 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2815 // and Firefox scrolls device pixels, not CSS pixels
2816 var wheelPxFactor =
2817         (win && chrome) ? 2 * window.devicePixelRatio :
2818         gecko ? window.devicePixelRatio : 1;
2819
2820 // @function getWheelDelta(ev: DOMEvent): Number
2821 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2822 // pixels scrolled (negative if scrolling down).
2823 // Events from pointing devices without precise scrolling are mapped to
2824 // a best guess of 60 pixels.
2825 function getWheelDelta(e) {
2826         return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2827                (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2828                (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2829                (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2830                (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
2831                e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2832                (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2833                e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2834                0;
2835 }
2836
2837 var skipEvents = {};
2838
2839 function fakeStop(e) {
2840         // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2841         skipEvents[e.type] = true;
2842 }
2843
2844 function skipped(e) {
2845         var events = skipEvents[e.type];
2846         // reset when checking, as it's only used in map container and propagates outside of the map
2847         skipEvents[e.type] = false;
2848         return events;
2849 }
2850
2851 // check if element really left/entered the event target (for mouseenter/mouseleave)
2852 function isExternalTarget(el, e) {
2853
2854         var related = e.relatedTarget;
2855
2856         if (!related) { return true; }
2857
2858         try {
2859                 while (related && (related !== el)) {
2860                         related = related.parentNode;
2861                 }
2862         } catch (err) {
2863                 return false;
2864         }
2865         return (related !== el);
2866 }
2867
2868 var lastClick;
2869
2870 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2871 function filterClick(e, handler) {
2872         var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2873             elapsed = lastClick && (timeStamp - lastClick);
2874
2875         // are they closer together than 500ms yet more than 100ms?
2876         // Android typically triggers them ~300ms apart while multiple listeners
2877         // on the same event should be triggered far faster;
2878         // or check if click is simulated on the element, and if it is, reject any non-simulated events
2879
2880         if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2881                 stop(e);
2882                 return;
2883         }
2884         lastClick = timeStamp;
2885
2886         handler(e);
2887 }
2888
2889
2890
2891
2892 var DomEvent = (Object.freeze || Object)({
2893         on: on,
2894         off: off,
2895         stopPropagation: stopPropagation,
2896         disableScrollPropagation: disableScrollPropagation,
2897         disableClickPropagation: disableClickPropagation,
2898         preventDefault: preventDefault,
2899         stop: stop,
2900         getMousePosition: getMousePosition,
2901         getWheelDelta: getWheelDelta,
2902         fakeStop: fakeStop,
2903         skipped: skipped,
2904         isExternalTarget: isExternalTarget,
2905         addListener: on,
2906         removeListener: off
2907 });
2908
2909 /*
2910  * @class PosAnimation
2911  * @aka L.PosAnimation
2912  * @inherits Evented
2913  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2914  *
2915  * @example
2916  * ```js
2917  * var fx = new L.PosAnimation();
2918  * fx.run(el, [300, 500], 0.5);
2919  * ```
2920  *
2921  * @constructor L.PosAnimation()
2922  * Creates a `PosAnimation` object.
2923  *
2924  */
2925
2926 var PosAnimation = Evented.extend({
2927
2928         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2929         // Run an animation of a given element to a new position, optionally setting
2930         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2931         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2932         // `0.5` by default).
2933         run: function (el, newPos, duration, easeLinearity) {
2934                 this.stop();
2935
2936                 this._el = el;
2937                 this._inProgress = true;
2938                 this._duration = duration || 0.25;
2939                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2940
2941                 this._startPos = getPosition(el);
2942                 this._offset = newPos.subtract(this._startPos);
2943                 this._startTime = +new Date();
2944
2945                 // @event start: Event
2946                 // Fired when the animation starts
2947                 this.fire('start');
2948
2949                 this._animate();
2950         },
2951
2952         // @method stop()
2953         // Stops the animation (if currently running).
2954         stop: function () {
2955                 if (!this._inProgress) { return; }
2956
2957                 this._step(true);
2958                 this._complete();
2959         },
2960
2961         _animate: function () {
2962                 // animation loop
2963                 this._animId = requestAnimFrame(this._animate, this);
2964                 this._step();
2965         },
2966
2967         _step: function (round) {
2968                 var elapsed = (+new Date()) - this._startTime,
2969                     duration = this._duration * 1000;
2970
2971                 if (elapsed < duration) {
2972                         this._runFrame(this._easeOut(elapsed / duration), round);
2973                 } else {
2974                         this._runFrame(1);
2975                         this._complete();
2976                 }
2977         },
2978
2979         _runFrame: function (progress, round) {
2980                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2981                 if (round) {
2982                         pos._round();
2983                 }
2984                 setPosition(this._el, pos);
2985
2986                 // @event step: Event
2987                 // Fired continuously during the animation.
2988                 this.fire('step');
2989         },
2990
2991         _complete: function () {
2992                 cancelAnimFrame(this._animId);
2993
2994                 this._inProgress = false;
2995                 // @event end: Event
2996                 // Fired when the animation ends.
2997                 this.fire('end');
2998         },
2999
3000         _easeOut: function (t) {
3001                 return 1 - Math.pow(1 - t, this._easeOutPower);
3002         }
3003 });
3004
3005 /*
3006  * @class Map
3007  * @aka L.Map
3008  * @inherits Evented
3009  *
3010  * The central class of the API — it is used to create a map on a page and manipulate it.
3011  *
3012  * @example
3013  *
3014  * ```js
3015  * // initialize the map on the "map" div with a given center and zoom
3016  * var map = L.map('map', {
3017  *      center: [51.505, -0.09],
3018  *      zoom: 13
3019  * });
3020  * ```
3021  *
3022  */
3023
3024 var Map = Evented.extend({
3025
3026         options: {
3027                 // @section Map State Options
3028                 // @option crs: CRS = L.CRS.EPSG3857
3029                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3030                 // sure what it means.
3031                 crs: EPSG3857,
3032
3033                 // @option center: LatLng = undefined
3034                 // Initial geographic center of the map
3035                 center: undefined,
3036
3037                 // @option zoom: Number = undefined
3038                 // Initial map zoom level
3039                 zoom: undefined,
3040
3041                 // @option minZoom: Number = *
3042                 // Minimum zoom level of the map.
3043                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3044                 // the lowest of their `minZoom` options will be used instead.
3045                 minZoom: undefined,
3046
3047                 // @option maxZoom: Number = *
3048                 // Maximum zoom level of the map.
3049                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3050                 // the highest of their `maxZoom` options will be used instead.
3051                 maxZoom: undefined,
3052
3053                 // @option layers: Layer[] = []
3054                 // Array of layers that will be added to the map initially
3055                 layers: [],
3056
3057                 // @option maxBounds: LatLngBounds = null
3058                 // When this option is set, the map restricts the view to the given
3059                 // geographical bounds, bouncing the user back if the user tries to pan
3060                 // outside the view. To set the restriction dynamically, use
3061                 // [`setMaxBounds`](#map-setmaxbounds) method.
3062                 maxBounds: undefined,
3063
3064                 // @option renderer: Renderer = *
3065                 // The default method for drawing vector layers on the map. `L.SVG`
3066                 // or `L.Canvas` by default depending on browser support.
3067                 renderer: undefined,
3068
3069
3070                 // @section Animation Options
3071                 // @option zoomAnimation: Boolean = true
3072                 // Whether the map zoom animation is enabled. By default it's enabled
3073                 // in all browsers that support CSS3 Transitions except Android.
3074                 zoomAnimation: true,
3075
3076                 // @option zoomAnimationThreshold: Number = 4
3077                 // Won't animate zoom if the zoom difference exceeds this value.
3078                 zoomAnimationThreshold: 4,
3079
3080                 // @option fadeAnimation: Boolean = true
3081                 // Whether the tile fade animation is enabled. By default it's enabled
3082                 // in all browsers that support CSS3 Transitions except Android.
3083                 fadeAnimation: true,
3084
3085                 // @option markerZoomAnimation: Boolean = true
3086                 // Whether markers animate their zoom with the zoom animation, if disabled
3087                 // they will disappear for the length of the animation. By default it's
3088                 // enabled in all browsers that support CSS3 Transitions except Android.
3089                 markerZoomAnimation: true,
3090
3091                 // @option transform3DLimit: Number = 2^23
3092                 // Defines the maximum size of a CSS translation transform. The default
3093                 // value should not be changed unless a web browser positions layers in
3094                 // the wrong place after doing a large `panBy`.
3095                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3096
3097                 // @section Interaction Options
3098                 // @option zoomSnap: Number = 1
3099                 // Forces the map's zoom level to always be a multiple of this, particularly
3100                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3101                 // By default, the zoom level snaps to the nearest integer; lower values
3102                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3103                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3104                 zoomSnap: 1,
3105
3106                 // @option zoomDelta: Number = 1
3107                 // Controls how much the map's zoom level will change after a
3108                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3109                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3110                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3111                 zoomDelta: 1,
3112
3113                 // @option trackResize: Boolean = true
3114                 // Whether the map automatically handles browser window resize to update itself.
3115                 trackResize: true
3116         },
3117
3118         initialize: function (id, options) { // (HTMLElement or String, Object)
3119                 options = setOptions(this, options);
3120
3121                 // Make sure to assign internal flags at the beginning,
3122                 // to avoid inconsistent state in some edge cases.
3123                 this._handlers = [];
3124                 this._layers = {};
3125                 this._zoomBoundLayers = {};
3126                 this._sizeChanged = true;
3127
3128                 this._initContainer(id);
3129                 this._initLayout();
3130
3131                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3132                 this._onResize = bind(this._onResize, this);
3133
3134                 this._initEvents();
3135
3136                 if (options.maxBounds) {
3137                         this.setMaxBounds(options.maxBounds);
3138                 }
3139
3140                 if (options.zoom !== undefined) {
3141                         this._zoom = this._limitZoom(options.zoom);
3142                 }
3143
3144                 if (options.center && options.zoom !== undefined) {
3145                         this.setView(toLatLng(options.center), options.zoom, {reset: true});
3146                 }
3147
3148                 this.callInitHooks();
3149
3150                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3151                 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3152                                 this.options.zoomAnimation;
3153
3154                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3155                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3156                 if (this._zoomAnimated) {
3157                         this._createAnimProxy();
3158                         on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3159                 }
3160
3161                 this._addLayers(this.options.layers);
3162         },
3163
3164
3165         // @section Methods for modifying map state
3166
3167         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3168         // Sets the view of the map (geographical center and zoom) with the given
3169         // animation options.
3170         setView: function (center, zoom, options) {
3171
3172                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3173                 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3174                 options = options || {};
3175
3176                 this._stop();
3177
3178                 if (this._loaded && !options.reset && options !== true) {
3179
3180                         if (options.animate !== undefined) {
3181                                 options.zoom = extend({animate: options.animate}, options.zoom);
3182                                 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3183                         }
3184
3185                         // try animating pan or zoom
3186                         var moved = (this._zoom !== zoom) ?
3187                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3188                                 this._tryAnimatedPan(center, options.pan);
3189
3190                         if (moved) {
3191                                 // prevent resize handler call, the view will refresh after animation anyway
3192                                 clearTimeout(this._sizeTimer);
3193                                 return this;
3194                         }
3195                 }
3196
3197                 // animation didn't start, just reset the map view
3198                 this._resetView(center, zoom);
3199
3200                 return this;
3201         },
3202
3203         // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3204         // Sets the zoom of the map.
3205         setZoom: function (zoom, options) {
3206                 if (!this._loaded) {
3207                         this._zoom = zoom;
3208                         return this;
3209                 }
3210                 return this.setView(this.getCenter(), zoom, {zoom: options});
3211         },
3212
3213         // @method zoomIn(delta?: Number, options?: Zoom options): this
3214         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3215         zoomIn: function (delta, options) {
3216                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3217                 return this.setZoom(this._zoom + delta, options);
3218         },
3219
3220         // @method zoomOut(delta?: Number, options?: Zoom options): this
3221         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3222         zoomOut: function (delta, options) {
3223                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3224                 return this.setZoom(this._zoom - delta, options);
3225         },
3226
3227         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3228         // Zooms the map while keeping a specified geographical point on the map
3229         // stationary (e.g. used internally for scroll zoom and double-click zoom).
3230         // @alternative
3231         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3232         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3233         setZoomAround: function (latlng, zoom, options) {
3234                 var scale = this.getZoomScale(zoom),
3235                     viewHalf = this.getSize().divideBy(2),
3236                     containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3237
3238                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3239                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3240
3241                 return this.setView(newCenter, zoom, {zoom: options});
3242         },
3243
3244         _getBoundsCenterZoom: function (bounds, options) {
3245
3246                 options = options || {};
3247                 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3248
3249                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3250                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3251
3252                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3253
3254                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3255
3256                 if (zoom === Infinity) {
3257                         return {
3258                                 center: bounds.getCenter(),
3259                                 zoom: zoom
3260                         };
3261                 }
3262
3263                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3264
3265                     swPoint = this.project(bounds.getSouthWest(), zoom),
3266                     nePoint = this.project(bounds.getNorthEast(), zoom),
3267                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3268
3269                 return {
3270                         center: center,
3271                         zoom: zoom
3272                 };
3273         },
3274
3275         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3276         // Sets a map view that contains the given geographical bounds with the
3277         // maximum zoom level possible.
3278         fitBounds: function (bounds, options) {
3279
3280                 bounds = toLatLngBounds(bounds);
3281
3282                 if (!bounds.isValid()) {
3283                         throw new Error('Bounds are not valid.');
3284                 }
3285
3286                 var target = this._getBoundsCenterZoom(bounds, options);
3287                 return this.setView(target.center, target.zoom, options);
3288         },
3289
3290         // @method fitWorld(options?: fitBounds options): this
3291         // Sets a map view that mostly contains the whole world with the maximum
3292         // zoom level possible.
3293         fitWorld: function (options) {
3294                 return this.fitBounds([[-90, -180], [90, 180]], options);
3295         },
3296
3297         // @method panTo(latlng: LatLng, options?: Pan options): this
3298         // Pans the map to a given center.
3299         panTo: function (center, options) { // (LatLng)
3300                 return this.setView(center, this._zoom, {pan: options});
3301         },
3302
3303         // @method panBy(offset: Point, options?: Pan options): this
3304         // Pans the map by a given number of pixels (animated).
3305         panBy: function (offset, options) {
3306                 offset = toPoint(offset).round();
3307                 options = options || {};
3308
3309                 if (!offset.x && !offset.y) {
3310                         return this.fire('moveend');
3311                 }
3312                 // If we pan too far, Chrome gets issues with tiles
3313                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3314                 if (options.animate !== true && !this.getSize().contains(offset)) {
3315                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3316                         return this;
3317                 }
3318
3319                 if (!this._panAnim) {
3320                         this._panAnim = new PosAnimation();
3321
3322                         this._panAnim.on({
3323                                 'step': this._onPanTransitionStep,
3324                                 'end': this._onPanTransitionEnd
3325                         }, this);
3326                 }
3327
3328                 // don't fire movestart if animating inertia
3329                 if (!options.noMoveStart) {
3330                         this.fire('movestart');
3331                 }
3332
3333                 // animate pan unless animate: false specified
3334                 if (options.animate !== false) {
3335                         addClass(this._mapPane, 'leaflet-pan-anim');
3336
3337                         var newPos = this._getMapPanePos().subtract(offset).round();
3338                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3339                 } else {
3340                         this._rawPanBy(offset);
3341                         this.fire('move').fire('moveend');
3342                 }
3343
3344                 return this;
3345         },
3346
3347         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3348         // Sets the view of the map (geographical center and zoom) performing a smooth
3349         // pan-zoom animation.
3350         flyTo: function (targetCenter, targetZoom, options) {
3351
3352                 options = options || {};
3353                 if (options.animate === false || !any3d) {
3354                         return this.setView(targetCenter, targetZoom, options);
3355                 }
3356
3357                 this._stop();
3358
3359                 var from = this.project(this.getCenter()),
3360                     to = this.project(targetCenter),
3361                     size = this.getSize(),
3362                     startZoom = this._zoom;
3363
3364                 targetCenter = toLatLng(targetCenter);
3365                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3366
3367                 var w0 = Math.max(size.x, size.y),
3368                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3369                     u1 = (to.distanceTo(from)) || 1,
3370                     rho = 1.42,
3371                     rho2 = rho * rho;
3372
3373                 function r(i) {
3374                         var s1 = i ? -1 : 1,
3375                             s2 = i ? w1 : w0,
3376                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3377                             b1 = 2 * s2 * rho2 * u1,
3378                             b = t1 / b1,
3379                             sq = Math.sqrt(b * b + 1) - b;
3380
3381                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
3382                             // thus triggering an infinite loop in flyTo
3383                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
3384
3385                         return log;
3386                 }
3387
3388                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3389                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3390                 function tanh(n) { return sinh(n) / cosh(n); }
3391
3392                 var r0 = r(0);
3393
3394                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3395                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3396
3397                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3398
3399                 var start = Date.now(),
3400                     S = (r(1) - r0) / rho,
3401                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3402
3403                 function frame() {
3404                         var t = (Date.now() - start) / duration,
3405                             s = easeOut(t) * S;
3406
3407                         if (t <= 1) {
3408                                 this._flyToFrame = requestAnimFrame(frame, this);
3409
3410                                 this._move(
3411                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3412                                         this.getScaleZoom(w0 / w(s), startZoom),
3413                                         {flyTo: true});
3414
3415                         } else {
3416                                 this
3417                                         ._move(targetCenter, targetZoom)
3418                                         ._moveEnd(true);
3419                         }
3420                 }
3421
3422                 this._moveStart(true, options.noMoveStart);
3423
3424                 frame.call(this);
3425                 return this;
3426         },
3427
3428         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3429         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3430         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3431         flyToBounds: function (bounds, options) {
3432                 var target = this._getBoundsCenterZoom(bounds, options);
3433                 return this.flyTo(target.center, target.zoom, options);
3434         },
3435
3436         // @method setMaxBounds(bounds: Bounds): this
3437         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3438         setMaxBounds: function (bounds) {
3439                 bounds = toLatLngBounds(bounds);
3440
3441                 if (!bounds.isValid()) {
3442                         this.options.maxBounds = null;
3443                         return this.off('moveend', this._panInsideMaxBounds);
3444                 } else if (this.options.maxBounds) {
3445                         this.off('moveend', this._panInsideMaxBounds);
3446                 }
3447
3448                 this.options.maxBounds = bounds;
3449
3450                 if (this._loaded) {
3451                         this._panInsideMaxBounds();
3452                 }
3453
3454                 return this.on('moveend', this._panInsideMaxBounds);
3455         },
3456
3457         // @method setMinZoom(zoom: Number): this
3458         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3459         setMinZoom: function (zoom) {
3460                 var oldZoom = this.options.minZoom;
3461                 this.options.minZoom = zoom;
3462
3463                 if (this._loaded && oldZoom !== zoom) {
3464                         this.fire('zoomlevelschange');
3465
3466                         if (this.getZoom() < this.options.minZoom) {
3467                                 return this.setZoom(zoom);
3468                         }
3469                 }
3470
3471                 return this;
3472         },
3473
3474         // @method setMaxZoom(zoom: Number): this
3475         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3476         setMaxZoom: function (zoom) {
3477                 var oldZoom = this.options.maxZoom;
3478                 this.options.maxZoom = zoom;
3479
3480                 if (this._loaded && oldZoom !== zoom) {
3481                         this.fire('zoomlevelschange');
3482
3483                         if (this.getZoom() > this.options.maxZoom) {
3484                                 return this.setZoom(zoom);
3485                         }
3486                 }
3487
3488                 return this;
3489         },
3490
3491         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3492         // 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.
3493         panInsideBounds: function (bounds, options) {
3494                 this._enforcingBounds = true;
3495                 var center = this.getCenter(),
3496                     newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3497
3498                 if (!center.equals(newCenter)) {
3499                         this.panTo(newCenter, options);
3500                 }
3501
3502                 this._enforcingBounds = false;
3503                 return this;
3504         },
3505
3506         // @method panInside(latlng: LatLng, options?: options): this
3507         // Pans the map the minimum amount to make the `latlng` visible. Use
3508         // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3509         // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3510         // If `latlng` is already within the (optionally padded) display bounds,
3511         // the map will not be panned.
3512         panInside: function (latlng, options) {
3513                 options = options || {};
3514
3515                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3516                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3517                     center = this.getCenter(),
3518                     pixelCenter = this.project(center),
3519                     pixelPoint = this.project(latlng),
3520                     pixelBounds = this.getPixelBounds(),
3521                     halfPixelBounds = pixelBounds.getSize().divideBy(2),
3522                     paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3523
3524                 if (!paddedBounds.contains(pixelPoint)) {
3525                         this._enforcingBounds = true;
3526                         var diff = pixelCenter.subtract(pixelPoint),
3527                             newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3528
3529                         if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3530                                 newCenter.x = pixelCenter.x - diff.x;
3531                                 if (diff.x > 0) {
3532                                         newCenter.x += halfPixelBounds.x - paddingTL.x;
3533                                 } else {
3534                                         newCenter.x -= halfPixelBounds.x - paddingBR.x;
3535                                 }
3536                         }
3537                         if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3538                                 newCenter.y = pixelCenter.y - diff.y;
3539                                 if (diff.y > 0) {
3540                                         newCenter.y += halfPixelBounds.y - paddingTL.y;
3541                                 } else {
3542                                         newCenter.y -= halfPixelBounds.y - paddingBR.y;
3543                                 }
3544                         }
3545                         this.panTo(this.unproject(newCenter), options);
3546                         this._enforcingBounds = false;
3547                 }
3548                 return this;
3549         },
3550
3551         // @method invalidateSize(options: Zoom/pan options): this
3552         // Checks if the map container size changed and updates the map if so —
3553         // call it after you've changed the map size dynamically, also animating
3554         // pan by default. If `options.pan` is `false`, panning will not occur.
3555         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3556         // that it doesn't happen often even if the method is called many
3557         // times in a row.
3558
3559         // @alternative
3560         // @method invalidateSize(animate: Boolean): this
3561         // Checks if the map container size changed and updates the map if so —
3562         // call it after you've changed the map size dynamically, also animating
3563         // pan by default.
3564         invalidateSize: function (options) {
3565                 if (!this._loaded) { return this; }
3566
3567                 options = extend({
3568                         animate: false,
3569                         pan: true
3570                 }, options === true ? {animate: true} : options);
3571
3572                 var oldSize = this.getSize();
3573                 this._sizeChanged = true;
3574                 this._lastCenter = null;
3575
3576                 var newSize = this.getSize(),
3577                     oldCenter = oldSize.divideBy(2).round(),
3578                     newCenter = newSize.divideBy(2).round(),
3579                     offset = oldCenter.subtract(newCenter);
3580
3581                 if (!offset.x && !offset.y) { return this; }
3582
3583                 if (options.animate && options.pan) {
3584                         this.panBy(offset);
3585
3586                 } else {
3587                         if (options.pan) {
3588                                 this._rawPanBy(offset);
3589                         }
3590
3591                         this.fire('move');
3592
3593                         if (options.debounceMoveend) {
3594                                 clearTimeout(this._sizeTimer);
3595                                 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3596                         } else {
3597                                 this.fire('moveend');
3598                         }
3599                 }
3600
3601                 // @section Map state change events
3602                 // @event resize: ResizeEvent
3603                 // Fired when the map is resized.
3604                 return this.fire('resize', {
3605                         oldSize: oldSize,
3606                         newSize: newSize
3607                 });
3608         },
3609
3610         // @section Methods for modifying map state
3611         // @method stop(): this
3612         // Stops the currently running `panTo` or `flyTo` animation, if any.
3613         stop: function () {
3614                 this.setZoom(this._limitZoom(this._zoom));
3615                 if (!this.options.zoomSnap) {
3616                         this.fire('viewreset');
3617                 }
3618                 return this._stop();
3619         },
3620
3621         // @section Geolocation methods
3622         // @method locate(options?: Locate options): this
3623         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3624         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3625         // and optionally sets the map view to the user's location with respect to
3626         // detection accuracy (or to the world view if geolocation failed).
3627         // Note that, if your page doesn't use HTTPS, this method will fail in
3628         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3629         // See `Locate options` for more details.
3630         locate: function (options) {
3631
3632                 options = this._locateOptions = extend({
3633                         timeout: 10000,
3634                         watch: false
3635                         // setView: false
3636                         // maxZoom: <Number>
3637                         // maximumAge: 0
3638                         // enableHighAccuracy: false
3639                 }, options);
3640
3641                 if (!('geolocation' in navigator)) {
3642                         this._handleGeolocationError({
3643                                 code: 0,
3644                                 message: 'Geolocation not supported.'
3645                         });
3646                         return this;
3647                 }
3648
3649                 var onResponse = bind(this._handleGeolocationResponse, this),
3650                     onError = bind(this._handleGeolocationError, this);
3651
3652                 if (options.watch) {
3653                         this._locationWatchId =
3654                                 navigator.geolocation.watchPosition(onResponse, onError, options);
3655                 } else {
3656                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3657                 }
3658                 return this;
3659         },
3660
3661         // @method stopLocate(): this
3662         // Stops watching location previously initiated by `map.locate({watch: true})`
3663         // and aborts resetting the map view if map.locate was called with
3664         // `{setView: true}`.
3665         stopLocate: function () {
3666                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3667                         navigator.geolocation.clearWatch(this._locationWatchId);
3668                 }
3669                 if (this._locateOptions) {
3670                         this._locateOptions.setView = false;
3671                 }
3672                 return this;
3673         },
3674
3675         _handleGeolocationError: function (error) {
3676                 var c = error.code,
3677                     message = error.message ||
3678                             (c === 1 ? 'permission denied' :
3679                             (c === 2 ? 'position unavailable' : 'timeout'));
3680
3681                 if (this._locateOptions.setView && !this._loaded) {
3682                         this.fitWorld();
3683                 }
3684
3685                 // @section Location events
3686                 // @event locationerror: ErrorEvent
3687                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3688                 this.fire('locationerror', {
3689                         code: c,
3690                         message: 'Geolocation error: ' + message + '.'
3691                 });
3692         },
3693
3694         _handleGeolocationResponse: function (pos) {
3695                 var lat = pos.coords.latitude,
3696                     lng = pos.coords.longitude,
3697                     latlng = new LatLng(lat, lng),
3698                     bounds = latlng.toBounds(pos.coords.accuracy * 2),
3699                     options = this._locateOptions;
3700
3701                 if (options.setView) {
3702                         var zoom = this.getBoundsZoom(bounds);
3703                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3704                 }
3705
3706                 var data = {
3707                         latlng: latlng,
3708                         bounds: bounds,
3709                         timestamp: pos.timestamp
3710                 };
3711
3712                 for (var i in pos.coords) {
3713                         if (typeof pos.coords[i] === 'number') {
3714                                 data[i] = pos.coords[i];
3715                         }
3716                 }
3717
3718                 // @event locationfound: LocationEvent
3719                 // Fired when geolocation (using the [`locate`](#map-locate) method)
3720                 // went successfully.
3721                 this.fire('locationfound', data);
3722         },
3723
3724         // TODO Appropriate docs section?
3725         // @section Other Methods
3726         // @method addHandler(name: String, HandlerClass: Function): this
3727         // Adds a new `Handler` to the map, given its name and constructor function.
3728         addHandler: function (name, HandlerClass) {
3729                 if (!HandlerClass) { return this; }
3730
3731                 var handler = this[name] = new HandlerClass(this);
3732
3733                 this._handlers.push(handler);
3734
3735                 if (this.options[name]) {
3736                         handler.enable();
3737                 }
3738
3739                 return this;
3740         },
3741
3742         // @method remove(): this
3743         // Destroys the map and clears all related event listeners.
3744         remove: function () {
3745
3746                 this._initEvents(true);
3747
3748                 if (this._containerId !== this._container._leaflet_id) {
3749                         throw new Error('Map container is being reused by another instance');
3750                 }
3751
3752                 try {
3753                         // throws error in IE6-8
3754                         delete this._container._leaflet_id;
3755                         delete this._containerId;
3756                 } catch (e) {
3757                         /*eslint-disable */
3758                         this._container._leaflet_id = undefined;
3759                         /* eslint-enable */
3760                         this._containerId = undefined;
3761                 }
3762
3763                 if (this._locationWatchId !== undefined) {
3764                         this.stopLocate();
3765                 }
3766
3767                 this._stop();
3768
3769                 remove(this._mapPane);
3770
3771                 if (this._clearControlPos) {
3772                         this._clearControlPos();
3773                 }
3774                 if (this._resizeRequest) {
3775                         cancelAnimFrame(this._resizeRequest);
3776                         this._resizeRequest = null;
3777                 }
3778
3779                 this._clearHandlers();
3780
3781                 if (this._loaded) {
3782                         // @section Map state change events
3783                         // @event unload: Event
3784                         // Fired when the map is destroyed with [remove](#map-remove) method.
3785                         this.fire('unload');
3786                 }
3787
3788                 var i;
3789                 for (i in this._layers) {
3790                         this._layers[i].remove();
3791                 }
3792                 for (i in this._panes) {
3793                         remove(this._panes[i]);
3794                 }
3795
3796                 this._layers = [];
3797                 this._panes = [];
3798                 delete this._mapPane;
3799                 delete this._renderer;
3800
3801                 return this;
3802         },
3803
3804         // @section Other Methods
3805         // @method createPane(name: String, container?: HTMLElement): HTMLElement
3806         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3807         // then returns it. The pane is created as a child of `container`, or
3808         // as a child of the main map pane if not set.
3809         createPane: function (name, container) {
3810                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3811                     pane = create$1('div', className, container || this._mapPane);
3812
3813                 if (name) {
3814                         this._panes[name] = pane;
3815                 }
3816                 return pane;
3817         },
3818
3819         // @section Methods for Getting Map State
3820
3821         // @method getCenter(): LatLng
3822         // Returns the geographical center of the map view
3823         getCenter: function () {
3824                 this._checkIfLoaded();
3825
3826                 if (this._lastCenter && !this._moved()) {
3827                         return this._lastCenter;
3828                 }
3829                 return this.layerPointToLatLng(this._getCenterLayerPoint());
3830         },
3831
3832         // @method getZoom(): Number
3833         // Returns the current zoom level of the map view
3834         getZoom: function () {
3835                 return this._zoom;
3836         },
3837
3838         // @method getBounds(): LatLngBounds
3839         // Returns the geographical bounds visible in the current map view
3840         getBounds: function () {
3841                 var bounds = this.getPixelBounds(),
3842                     sw = this.unproject(bounds.getBottomLeft()),
3843                     ne = this.unproject(bounds.getTopRight());
3844
3845                 return new LatLngBounds(sw, ne);
3846         },
3847
3848         // @method getMinZoom(): Number
3849         // 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.
3850         getMinZoom: function () {
3851                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3852         },
3853
3854         // @method getMaxZoom(): Number
3855         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3856         getMaxZoom: function () {
3857                 return this.options.maxZoom === undefined ?
3858                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3859                         this.options.maxZoom;
3860         },
3861
3862         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3863         // Returns the maximum zoom level on which the given bounds fit to the map
3864         // view in its entirety. If `inside` (optional) is set to `true`, the method
3865         // instead returns the minimum zoom level on which the map view fits into
3866         // the given bounds in its entirety.
3867         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3868                 bounds = toLatLngBounds(bounds);
3869                 padding = toPoint(padding || [0, 0]);
3870
3871                 var zoom = this.getZoom() || 0,
3872                     min = this.getMinZoom(),
3873                     max = this.getMaxZoom(),
3874                     nw = bounds.getNorthWest(),
3875                     se = bounds.getSouthEast(),
3876                     size = this.getSize().subtract(padding),
3877                     boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3878                     snap = any3d ? this.options.zoomSnap : 1,
3879                     scalex = size.x / boundsSize.x,
3880                     scaley = size.y / boundsSize.y,
3881                     scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3882
3883                 zoom = this.getScaleZoom(scale, zoom);
3884
3885                 if (snap) {
3886                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3887                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3888                 }
3889
3890                 return Math.max(min, Math.min(max, zoom));
3891         },
3892
3893         // @method getSize(): Point
3894         // Returns the current size of the map container (in pixels).
3895         getSize: function () {
3896                 if (!this._size || this._sizeChanged) {
3897                         this._size = new Point(
3898                                 this._container.clientWidth || 0,
3899                                 this._container.clientHeight || 0);
3900
3901                         this._sizeChanged = false;
3902                 }
3903                 return this._size.clone();
3904         },
3905
3906         // @method getPixelBounds(): Bounds
3907         // Returns the bounds of the current map view in projected pixel
3908         // coordinates (sometimes useful in layer and overlay implementations).
3909         getPixelBounds: function (center, zoom) {
3910                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3911                 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3912         },
3913
3914         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3915         // the map pane? "left point of the map layer" can be confusing, specially
3916         // since there can be negative offsets.
3917         // @method getPixelOrigin(): Point
3918         // Returns the projected pixel coordinates of the top left point of
3919         // the map layer (useful in custom layer and overlay implementations).
3920         getPixelOrigin: function () {
3921                 this._checkIfLoaded();
3922                 return this._pixelOrigin;
3923         },
3924
3925         // @method getPixelWorldBounds(zoom?: Number): Bounds
3926         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3927         // If `zoom` is omitted, the map's current zoom level is used.
3928         getPixelWorldBounds: function (zoom) {
3929                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3930         },
3931
3932         // @section Other Methods
3933
3934         // @method getPane(pane: String|HTMLElement): HTMLElement
3935         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3936         getPane: function (pane) {
3937                 return typeof pane === 'string' ? this._panes[pane] : pane;
3938         },
3939
3940         // @method getPanes(): Object
3941         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3942         // the panes as values.
3943         getPanes: function () {
3944                 return this._panes;
3945         },
3946
3947         // @method getContainer: HTMLElement
3948         // Returns the HTML element that contains the map.
3949         getContainer: function () {
3950                 return this._container;
3951         },
3952
3953
3954         // @section Conversion Methods
3955
3956         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3957         // Returns the scale factor to be applied to a map transition from zoom level
3958         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3959         getZoomScale: function (toZoom, fromZoom) {
3960                 // TODO replace with universal implementation after refactoring projections
3961                 var crs = this.options.crs;
3962                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3963                 return crs.scale(toZoom) / crs.scale(fromZoom);
3964         },
3965
3966         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3967         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3968         // level and everything is scaled by a factor of `scale`. Inverse of
3969         // [`getZoomScale`](#map-getZoomScale).
3970         getScaleZoom: function (scale, fromZoom) {
3971                 var crs = this.options.crs;
3972                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3973                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3974                 return isNaN(zoom) ? Infinity : zoom;
3975         },
3976
3977         // @method project(latlng: LatLng, zoom: Number): Point
3978         // Projects a geographical coordinate `LatLng` according to the projection
3979         // of the map's CRS, then scales it according to `zoom` and the CRS's
3980         // `Transformation`. The result is pixel coordinate relative to
3981         // the CRS origin.
3982         project: function (latlng, zoom) {
3983                 zoom = zoom === undefined ? this._zoom : zoom;
3984                 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3985         },
3986
3987         // @method unproject(point: Point, zoom: Number): LatLng
3988         // Inverse of [`project`](#map-project).
3989         unproject: function (point, zoom) {
3990                 zoom = zoom === undefined ? this._zoom : zoom;
3991                 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3992         },
3993
3994         // @method layerPointToLatLng(point: Point): LatLng
3995         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3996         // returns the corresponding geographical coordinate (for the current zoom level).
3997         layerPointToLatLng: function (point) {
3998                 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3999                 return this.unproject(projectedPoint);
4000         },
4001
4002         // @method latLngToLayerPoint(latlng: LatLng): Point
4003         // Given a geographical coordinate, returns the corresponding pixel coordinate
4004         // relative to the [origin pixel](#map-getpixelorigin).
4005         latLngToLayerPoint: function (latlng) {
4006                 var projectedPoint = this.project(toLatLng(latlng))._round();
4007                 return projectedPoint._subtract(this.getPixelOrigin());
4008         },
4009
4010         // @method wrapLatLng(latlng: LatLng): LatLng
4011         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4012         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4013         // CRS's bounds.
4014         // By default this means longitude is wrapped around the dateline so its
4015         // value is between -180 and +180 degrees.
4016         wrapLatLng: function (latlng) {
4017                 return this.options.crs.wrapLatLng(toLatLng(latlng));
4018         },
4019
4020         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4021         // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4022         // its center is within the CRS's bounds.
4023         // By default this means the center longitude is wrapped around the dateline so its
4024         // value is between -180 and +180 degrees, and the majority of the bounds
4025         // overlaps the CRS's bounds.
4026         wrapLatLngBounds: function (latlng) {
4027                 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4028         },
4029
4030         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4031         // Returns the distance between two geographical coordinates according to
4032         // the map's CRS. By default this measures distance in meters.
4033         distance: function (latlng1, latlng2) {
4034                 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4035         },
4036
4037         // @method containerPointToLayerPoint(point: Point): Point
4038         // Given a pixel coordinate relative to the map container, returns the corresponding
4039         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4040         containerPointToLayerPoint: function (point) { // (Point)
4041                 return toPoint(point).subtract(this._getMapPanePos());
4042         },
4043
4044         // @method layerPointToContainerPoint(point: Point): Point
4045         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4046         // returns the corresponding pixel coordinate relative to the map container.
4047         layerPointToContainerPoint: function (point) { // (Point)
4048                 return toPoint(point).add(this._getMapPanePos());
4049         },
4050
4051         // @method containerPointToLatLng(point: Point): LatLng
4052         // Given a pixel coordinate relative to the map container, returns
4053         // the corresponding geographical coordinate (for the current zoom level).
4054         containerPointToLatLng: function (point) {
4055                 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4056                 return this.layerPointToLatLng(layerPoint);
4057         },
4058
4059         // @method latLngToContainerPoint(latlng: LatLng): Point
4060         // Given a geographical coordinate, returns the corresponding pixel coordinate
4061         // relative to the map container.
4062         latLngToContainerPoint: function (latlng) {
4063                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4064         },
4065
4066         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4067         // Given a MouseEvent object, returns the pixel coordinate relative to the
4068         // map container where the event took place.
4069         mouseEventToContainerPoint: function (e) {
4070                 return getMousePosition(e, this._container);
4071         },
4072
4073         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4074         // Given a MouseEvent object, returns the pixel coordinate relative to
4075         // the [origin pixel](#map-getpixelorigin) where the event took place.
4076         mouseEventToLayerPoint: function (e) {
4077                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4078         },
4079
4080         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4081         // Given a MouseEvent object, returns geographical coordinate where the
4082         // event took place.
4083         mouseEventToLatLng: function (e) { // (MouseEvent)
4084                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4085         },
4086
4087
4088         // map initialization methods
4089
4090         _initContainer: function (id) {
4091                 var container = this._container = get(id);
4092
4093                 if (!container) {
4094                         throw new Error('Map container not found.');
4095                 } else if (container._leaflet_id) {
4096                         throw new Error('Map container is already initialized.');
4097                 }
4098
4099                 on(container, 'scroll', this._onScroll, this);
4100                 this._containerId = stamp(container);
4101         },
4102
4103         _initLayout: function () {
4104                 var container = this._container;
4105
4106                 this._fadeAnimated = this.options.fadeAnimation && any3d;
4107
4108                 addClass(container, 'leaflet-container' +
4109                         (touch ? ' leaflet-touch' : '') +
4110                         (retina ? ' leaflet-retina' : '') +
4111                         (ielt9 ? ' leaflet-oldie' : '') +
4112                         (safari ? ' leaflet-safari' : '') +
4113                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4114
4115                 var position = getStyle(container, 'position');
4116
4117                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4118                         container.style.position = 'relative';
4119                 }
4120
4121                 this._initPanes();
4122
4123                 if (this._initControlPos) {
4124                         this._initControlPos();
4125                 }
4126         },
4127
4128         _initPanes: function () {
4129                 var panes = this._panes = {};
4130                 this._paneRenderers = {};
4131
4132                 // @section
4133                 //
4134                 // Panes are DOM elements used to control the ordering of layers on the map. You
4135                 // can access panes with [`map.getPane`](#map-getpane) or
4136                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4137                 // [`map.createPane`](#map-createpane) method.
4138                 //
4139                 // Every map has the following default panes that differ only in zIndex.
4140                 //
4141                 // @pane mapPane: HTMLElement = 'auto'
4142                 // Pane that contains all other map panes
4143
4144                 this._mapPane = this.createPane('mapPane', this._container);
4145                 setPosition(this._mapPane, new Point(0, 0));
4146
4147                 // @pane tilePane: HTMLElement = 200
4148                 // Pane for `GridLayer`s and `TileLayer`s
4149                 this.createPane('tilePane');
4150                 // @pane overlayPane: HTMLElement = 400
4151                 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4152                 this.createPane('shadowPane');
4153                 // @pane shadowPane: HTMLElement = 500
4154                 // Pane for overlay shadows (e.g. `Marker` shadows)
4155                 this.createPane('overlayPane');
4156                 // @pane markerPane: HTMLElement = 600
4157                 // Pane for `Icon`s of `Marker`s
4158                 this.createPane('markerPane');
4159                 // @pane tooltipPane: HTMLElement = 650
4160                 // Pane for `Tooltip`s.
4161                 this.createPane('tooltipPane');
4162                 // @pane popupPane: HTMLElement = 700
4163                 // Pane for `Popup`s.
4164                 this.createPane('popupPane');
4165
4166                 if (!this.options.markerZoomAnimation) {
4167                         addClass(panes.markerPane, 'leaflet-zoom-hide');
4168                         addClass(panes.shadowPane, 'leaflet-zoom-hide');
4169                 }
4170         },
4171
4172
4173         // private methods that modify map state
4174
4175         // @section Map state change events
4176         _resetView: function (center, zoom) {
4177                 setPosition(this._mapPane, new Point(0, 0));
4178
4179                 var loading = !this._loaded;
4180                 this._loaded = true;
4181                 zoom = this._limitZoom(zoom);
4182
4183                 this.fire('viewprereset');
4184
4185                 var zoomChanged = this._zoom !== zoom;
4186                 this
4187                         ._moveStart(zoomChanged, false)
4188                         ._move(center, zoom)
4189                         ._moveEnd(zoomChanged);
4190
4191                 // @event viewreset: Event
4192                 // Fired when the map needs to redraw its content (this usually happens
4193                 // on map zoom or load). Very useful for creating custom overlays.
4194                 this.fire('viewreset');
4195
4196                 // @event load: Event
4197                 // Fired when the map is initialized (when its center and zoom are set
4198                 // for the first time).
4199                 if (loading) {
4200                         this.fire('load');
4201                 }
4202         },
4203
4204         _moveStart: function (zoomChanged, noMoveStart) {
4205                 // @event zoomstart: Event
4206                 // Fired when the map zoom is about to change (e.g. before zoom animation).
4207                 // @event movestart: Event
4208                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4209                 if (zoomChanged) {
4210                         this.fire('zoomstart');
4211                 }
4212                 if (!noMoveStart) {
4213                         this.fire('movestart');
4214                 }
4215                 return this;
4216         },
4217
4218         _move: function (center, zoom, data) {
4219                 if (zoom === undefined) {
4220                         zoom = this._zoom;
4221                 }
4222                 var zoomChanged = this._zoom !== zoom;
4223
4224                 this._zoom = zoom;
4225                 this._lastCenter = center;
4226                 this._pixelOrigin = this._getNewPixelOrigin(center);
4227
4228                 // @event zoom: Event
4229                 // Fired repeatedly during any change in zoom level, including zoom
4230                 // and fly animations.
4231                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
4232                         this.fire('zoom', data);
4233                 }
4234
4235                 // @event move: Event
4236                 // Fired repeatedly during any movement of the map, including pan and
4237                 // fly animations.
4238                 return this.fire('move', data);
4239         },
4240
4241         _moveEnd: function (zoomChanged) {
4242                 // @event zoomend: Event
4243                 // Fired when the map has changed, after any animations.
4244                 if (zoomChanged) {
4245                         this.fire('zoomend');
4246                 }
4247
4248                 // @event moveend: Event
4249                 // Fired when the center of the map stops changing (e.g. user stopped
4250                 // dragging the map).
4251                 return this.fire('moveend');
4252         },
4253
4254         _stop: function () {
4255                 cancelAnimFrame(this._flyToFrame);
4256                 if (this._panAnim) {
4257                         this._panAnim.stop();
4258                 }
4259                 return this;
4260         },
4261
4262         _rawPanBy: function (offset) {
4263                 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4264         },
4265
4266         _getZoomSpan: function () {
4267                 return this.getMaxZoom() - this.getMinZoom();
4268         },
4269
4270         _panInsideMaxBounds: function () {
4271                 if (!this._enforcingBounds) {
4272                         this.panInsideBounds(this.options.maxBounds);
4273                 }
4274         },
4275
4276         _checkIfLoaded: function () {
4277                 if (!this._loaded) {
4278                         throw new Error('Set map center and zoom first.');
4279                 }
4280         },
4281
4282         // DOM event handling
4283
4284         // @section Interaction events
4285         _initEvents: function (remove$$1) {
4286                 this._targets = {};
4287                 this._targets[stamp(this._container)] = this;
4288
4289                 var onOff = remove$$1 ? off : on;
4290
4291                 // @event click: MouseEvent
4292                 // Fired when the user clicks (or taps) the map.
4293                 // @event dblclick: MouseEvent
4294                 // Fired when the user double-clicks (or double-taps) the map.
4295                 // @event mousedown: MouseEvent
4296                 // Fired when the user pushes the mouse button on the map.
4297                 // @event mouseup: MouseEvent
4298                 // Fired when the user releases the mouse button on the map.
4299                 // @event mouseover: MouseEvent
4300                 // Fired when the mouse enters the map.
4301                 // @event mouseout: MouseEvent
4302                 // Fired when the mouse leaves the map.
4303                 // @event mousemove: MouseEvent
4304                 // Fired while the mouse moves over the map.
4305                 // @event contextmenu: MouseEvent
4306                 // Fired when the user pushes the right mouse button on the map, prevents
4307                 // default browser context menu from showing if there are listeners on
4308                 // this event. Also fired on mobile when the user holds a single touch
4309                 // for a second (also called long press).
4310                 // @event keypress: KeyboardEvent
4311                 // Fired when the user presses a key from the keyboard while the map is focused.
4312                 onOff(this._container, 'click dblclick mousedown mouseup ' +
4313                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4314
4315                 if (this.options.trackResize) {
4316                         onOff(window, 'resize', this._onResize, this);
4317                 }
4318
4319                 if (any3d && this.options.transform3DLimit) {
4320                         (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4321                 }
4322         },
4323
4324         _onResize: function () {
4325                 cancelAnimFrame(this._resizeRequest);
4326                 this._resizeRequest = requestAnimFrame(
4327                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
4328         },
4329
4330         _onScroll: function () {
4331                 this._container.scrollTop  = 0;
4332                 this._container.scrollLeft = 0;
4333         },
4334
4335         _onMoveEnd: function () {
4336                 var pos = this._getMapPanePos();
4337                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4338                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4339                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4340                         this._resetView(this.getCenter(), this.getZoom());
4341                 }
4342         },
4343
4344         _findEventTargets: function (e, type) {
4345                 var targets = [],
4346                     target,
4347                     isHover = type === 'mouseout' || type === 'mouseover',
4348                     src = e.target || e.srcElement,
4349                     dragging = false;
4350
4351                 while (src) {
4352                         target = this._targets[stamp(src)];
4353                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4354                                 // Prevent firing click after you just dragged an object.
4355                                 dragging = true;
4356                                 break;
4357                         }
4358                         if (target && target.listens(type, true)) {
4359                                 if (isHover && !isExternalTarget(src, e)) { break; }
4360                                 targets.push(target);
4361                                 if (isHover) { break; }
4362                         }
4363                         if (src === this._container) { break; }
4364                         src = src.parentNode;
4365                 }
4366                 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4367                         targets = [this];
4368                 }
4369                 return targets;
4370         },
4371
4372         _handleDOMEvent: function (e) {
4373                 if (!this._loaded || skipped(e)) { return; }
4374
4375                 var type = e.type;
4376
4377                 if (type === 'mousedown' || type === 'keypress') {
4378                         // prevents outline when clicking on keyboard-focusable element
4379                         preventOutline(e.target || e.srcElement);
4380                 }
4381
4382                 this._fireDOMEvent(e, type);
4383         },
4384
4385         _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4386
4387         _fireDOMEvent: function (e, type, targets) {
4388
4389                 if (e.type === 'click') {
4390                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4391                         // @event preclick: MouseEvent
4392                         // Fired before mouse click on the map (sometimes useful when you
4393                         // want something to happen on click before any existing click
4394                         // handlers start running).
4395                         var synth = extend({}, e);
4396                         synth.type = 'preclick';
4397                         this._fireDOMEvent(synth, synth.type, targets);
4398                 }
4399
4400                 if (e._stopped) { return; }
4401
4402                 // Find the layer the event is propagating from and its parents.
4403                 targets = (targets || []).concat(this._findEventTargets(e, type));
4404
4405                 if (!targets.length) { return; }
4406
4407                 var target = targets[0];
4408                 if (type === 'contextmenu' && target.listens(type, true)) {
4409                         preventDefault(e);
4410                 }
4411
4412                 var data = {
4413                         originalEvent: e
4414                 };
4415
4416                 if (e.type !== 'keypress') {
4417                         var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4418                         data.containerPoint = isMarker ?
4419                                 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4420                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4421                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4422                 }
4423
4424                 for (var i = 0; i < targets.length; i++) {
4425                         targets[i].fire(type, data, true);
4426                         if (data.originalEvent._stopped ||
4427                                 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4428                 }
4429         },
4430
4431         _draggableMoved: function (obj) {
4432                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4433                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4434         },
4435
4436         _clearHandlers: function () {
4437                 for (var i = 0, len = this._handlers.length; i < len; i++) {
4438                         this._handlers[i].disable();
4439                 }
4440         },
4441
4442         // @section Other Methods
4443
4444         // @method whenReady(fn: Function, context?: Object): this
4445         // Runs the given function `fn` when the map gets initialized with
4446         // a view (center and zoom) and at least one layer, or immediately
4447         // if it's already initialized, optionally passing a function context.
4448         whenReady: function (callback, context) {
4449                 if (this._loaded) {
4450                         callback.call(context || this, {target: this});
4451                 } else {
4452                         this.on('load', callback, context);
4453                 }
4454                 return this;
4455         },
4456
4457
4458         // private methods for getting map state
4459
4460         _getMapPanePos: function () {
4461                 return getPosition(this._mapPane) || new Point(0, 0);
4462         },
4463
4464         _moved: function () {
4465                 var pos = this._getMapPanePos();
4466                 return pos && !pos.equals([0, 0]);
4467         },
4468
4469         _getTopLeftPoint: function (center, zoom) {
4470                 var pixelOrigin = center && zoom !== undefined ?
4471                         this._getNewPixelOrigin(center, zoom) :
4472                         this.getPixelOrigin();
4473                 return pixelOrigin.subtract(this._getMapPanePos());
4474         },
4475
4476         _getNewPixelOrigin: function (center, zoom) {
4477                 var viewHalf = this.getSize()._divideBy(2);
4478                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4479         },
4480
4481         _latLngToNewLayerPoint: function (latlng, zoom, center) {
4482                 var topLeft = this._getNewPixelOrigin(center, zoom);
4483                 return this.project(latlng, zoom)._subtract(topLeft);
4484         },
4485
4486         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4487                 var topLeft = this._getNewPixelOrigin(center, zoom);
4488                 return toBounds([
4489                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4490                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4491                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4492                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4493                 ]);
4494         },
4495
4496         // layer point of the current center
4497         _getCenterLayerPoint: function () {
4498                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4499         },
4500
4501         // offset of the specified place to the current center in pixels
4502         _getCenterOffset: function (latlng) {
4503                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4504         },
4505
4506         // adjust center for view to get inside bounds
4507         _limitCenter: function (center, zoom, bounds) {
4508
4509                 if (!bounds) { return center; }
4510
4511                 var centerPoint = this.project(center, zoom),
4512                     viewHalf = this.getSize().divideBy(2),
4513                     viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4514                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4515
4516                 // If offset is less than a pixel, ignore.
4517                 // This prevents unstable projections from getting into
4518                 // an infinite loop of tiny offsets.
4519                 if (offset.round().equals([0, 0])) {
4520                         return center;
4521                 }
4522
4523                 return this.unproject(centerPoint.add(offset), zoom);
4524         },
4525
4526         // adjust offset for view to get inside bounds
4527         _limitOffset: function (offset, bounds) {
4528                 if (!bounds) { return offset; }
4529
4530                 var viewBounds = this.getPixelBounds(),
4531                     newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4532
4533                 return offset.add(this._getBoundsOffset(newBounds, bounds));
4534         },
4535
4536         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4537         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4538                 var projectedMaxBounds = toBounds(
4539                         this.project(maxBounds.getNorthEast(), zoom),
4540                         this.project(maxBounds.getSouthWest(), zoom)
4541                     ),
4542                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4543                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4544
4545                     dx = this._rebound(minOffset.x, -maxOffset.x),
4546                     dy = this._rebound(minOffset.y, -maxOffset.y);
4547
4548                 return new Point(dx, dy);
4549         },
4550
4551         _rebound: function (left, right) {
4552                 return left + right > 0 ?
4553                         Math.round(left - right) / 2 :
4554                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4555         },
4556
4557         _limitZoom: function (zoom) {
4558                 var min = this.getMinZoom(),
4559                     max = this.getMaxZoom(),
4560                     snap = any3d ? this.options.zoomSnap : 1;
4561                 if (snap) {
4562                         zoom = Math.round(zoom / snap) * snap;
4563                 }
4564                 return Math.max(min, Math.min(max, zoom));
4565         },
4566
4567         _onPanTransitionStep: function () {
4568                 this.fire('move');
4569         },
4570
4571         _onPanTransitionEnd: function () {
4572                 removeClass(this._mapPane, 'leaflet-pan-anim');
4573                 this.fire('moveend');
4574         },
4575
4576         _tryAnimatedPan: function (center, options) {
4577                 // difference between the new and current centers in pixels
4578                 var offset = this._getCenterOffset(center)._trunc();
4579
4580                 // don't animate too far unless animate: true specified in options
4581                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4582
4583                 this.panBy(offset, options);
4584
4585                 return true;
4586         },
4587
4588         _createAnimProxy: function () {
4589
4590                 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4591                 this._panes.mapPane.appendChild(proxy);
4592
4593                 this.on('zoomanim', function (e) {
4594                         var prop = TRANSFORM,
4595                             transform = this._proxy.style[prop];
4596
4597                         setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4598
4599                         // workaround for case when transform is the same and so transitionend event is not fired
4600                         if (transform === this._proxy.style[prop] && this._animatingZoom) {
4601                                 this._onZoomTransitionEnd();
4602                         }
4603                 }, this);
4604
4605                 this.on('load moveend', function () {
4606                         var c = this.getCenter(),
4607                             z = this.getZoom();
4608                         setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4609                 }, this);
4610
4611                 this._on('unload', this._destroyAnimProxy, this);
4612         },
4613
4614         _destroyAnimProxy: function () {
4615                 remove(this._proxy);
4616                 delete this._proxy;
4617         },
4618
4619         _catchTransitionEnd: function (e) {
4620                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4621                         this._onZoomTransitionEnd();
4622                 }
4623         },
4624
4625         _nothingToAnimate: function () {
4626                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4627         },
4628
4629         _tryAnimatedZoom: function (center, zoom, options) {
4630
4631                 if (this._animatingZoom) { return true; }
4632
4633                 options = options || {};
4634
4635                 // don't animate if disabled, not supported or zoom difference is too large
4636                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4637                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4638
4639                 // offset is the pixel coords of the zoom origin relative to the current center
4640                 var scale = this.getZoomScale(zoom),
4641                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4642
4643                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4644                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4645
4646                 requestAnimFrame(function () {
4647                         this
4648                             ._moveStart(true, false)
4649                             ._animateZoom(center, zoom, true);
4650                 }, this);
4651
4652                 return true;
4653         },
4654
4655         _animateZoom: function (center, zoom, startAnim, noUpdate) {
4656                 if (!this._mapPane) { return; }
4657
4658                 if (startAnim) {
4659                         this._animatingZoom = true;
4660
4661                         // remember what center/zoom to set after animation
4662                         this._animateToCenter = center;
4663                         this._animateToZoom = zoom;
4664
4665                         addClass(this._mapPane, 'leaflet-zoom-anim');
4666                 }
4667
4668                 // @event zoomanim: ZoomAnimEvent
4669                 // Fired at least once per zoom animation. For continous zoom, like pinch zooming, fired once per frame during zoom.
4670                 this.fire('zoomanim', {
4671                         center: center,
4672                         zoom: zoom,
4673                         noUpdate: noUpdate
4674                 });
4675
4676                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4677                 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4678         },
4679
4680         _onZoomTransitionEnd: function () {
4681                 if (!this._animatingZoom) { return; }
4682
4683                 if (this._mapPane) {
4684                         removeClass(this._mapPane, 'leaflet-zoom-anim');
4685                 }
4686
4687                 this._animatingZoom = false;
4688
4689                 this._move(this._animateToCenter, this._animateToZoom);
4690
4691                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4692                 requestAnimFrame(function () {
4693                         this._moveEnd(true);
4694                 }, this);
4695         }
4696 });
4697
4698 // @section
4699
4700 // @factory L.map(id: String, options?: Map options)
4701 // Instantiates a map object given the DOM ID of a `<div>` element
4702 // and optionally an object literal with `Map options`.
4703 //
4704 // @alternative
4705 // @factory L.map(el: HTMLElement, options?: Map options)
4706 // Instantiates a map object given an instance of a `<div>` HTML element
4707 // and optionally an object literal with `Map options`.
4708 function createMap(id, options) {
4709         return new Map(id, options);
4710 }
4711
4712 /*
4713  * @class Control
4714  * @aka L.Control
4715  * @inherits Class
4716  *
4717  * L.Control is a base class for implementing map controls. Handles positioning.
4718  * All other controls extend from this class.
4719  */
4720
4721 var Control = Class.extend({
4722         // @section
4723         // @aka Control options
4724         options: {
4725                 // @option position: String = 'topright'
4726                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4727                 // `'topright'`, `'bottomleft'` or `'bottomright'`
4728                 position: 'topright'
4729         },
4730
4731         initialize: function (options) {
4732                 setOptions(this, options);
4733         },
4734
4735         /* @section
4736          * Classes extending L.Control will inherit the following methods:
4737          *
4738          * @method getPosition: string
4739          * Returns the position of the control.
4740          */
4741         getPosition: function () {
4742                 return this.options.position;
4743         },
4744
4745         // @method setPosition(position: string): this
4746         // Sets the position of the control.
4747         setPosition: function (position) {
4748                 var map = this._map;
4749
4750                 if (map) {
4751                         map.removeControl(this);
4752                 }
4753
4754                 this.options.position = position;
4755
4756                 if (map) {
4757                         map.addControl(this);
4758                 }
4759
4760                 return this;
4761         },
4762
4763         // @method getContainer: HTMLElement
4764         // Returns the HTMLElement that contains the control.
4765         getContainer: function () {
4766                 return this._container;
4767         },
4768
4769         // @method addTo(map: Map): this
4770         // Adds the control to the given map.
4771         addTo: function (map) {
4772                 this.remove();
4773                 this._map = map;
4774
4775                 var container = this._container = this.onAdd(map),
4776                     pos = this.getPosition(),
4777                     corner = map._controlCorners[pos];
4778
4779                 addClass(container, 'leaflet-control');
4780
4781                 if (pos.indexOf('bottom') !== -1) {
4782                         corner.insertBefore(container, corner.firstChild);
4783                 } else {
4784                         corner.appendChild(container);
4785                 }
4786
4787                 return this;
4788         },
4789
4790         // @method remove: this
4791         // Removes the control from the map it is currently active on.
4792         remove: function () {
4793                 if (!this._map) {
4794                         return this;
4795                 }
4796
4797                 remove(this._container);
4798
4799                 if (this.onRemove) {
4800                         this.onRemove(this._map);
4801                 }
4802
4803                 this._map = null;
4804
4805                 return this;
4806         },
4807
4808         _refocusOnMap: function (e) {
4809                 // if map exists and event is not a keyboard event
4810                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4811                         this._map.getContainer().focus();
4812                 }
4813         }
4814 });
4815
4816 var control = function (options) {
4817         return new Control(options);
4818 };
4819
4820 /* @section Extension methods
4821  * @uninheritable
4822  *
4823  * Every control should extend from `L.Control` and (re-)implement the following methods.
4824  *
4825  * @method onAdd(map: Map): HTMLElement
4826  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4827  *
4828  * @method onRemove(map: Map)
4829  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4830  */
4831
4832 /* @namespace Map
4833  * @section Methods for Layers and Controls
4834  */
4835 Map.include({
4836         // @method addControl(control: Control): this
4837         // Adds the given control to the map
4838         addControl: function (control) {
4839                 control.addTo(this);
4840                 return this;
4841         },
4842
4843         // @method removeControl(control: Control): this
4844         // Removes the given control from the map
4845         removeControl: function (control) {
4846                 control.remove();
4847                 return this;
4848         },
4849
4850         _initControlPos: function () {
4851                 var corners = this._controlCorners = {},
4852                     l = 'leaflet-',
4853                     container = this._controlContainer =
4854                             create$1('div', l + 'control-container', this._container);
4855
4856                 function createCorner(vSide, hSide) {
4857                         var className = l + vSide + ' ' + l + hSide;
4858
4859                         corners[vSide + hSide] = create$1('div', className, container);
4860                 }
4861
4862                 createCorner('top', 'left');
4863                 createCorner('top', 'right');
4864                 createCorner('bottom', 'left');
4865                 createCorner('bottom', 'right');
4866         },
4867
4868         _clearControlPos: function () {
4869                 for (var i in this._controlCorners) {
4870                         remove(this._controlCorners[i]);
4871                 }
4872                 remove(this._controlContainer);
4873                 delete this._controlCorners;
4874                 delete this._controlContainer;
4875         }
4876 });
4877
4878 /*
4879  * @class Control.Layers
4880  * @aka L.Control.Layers
4881  * @inherits Control
4882  *
4883  * 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`.
4884  *
4885  * @example
4886  *
4887  * ```js
4888  * var baseLayers = {
4889  *      "Mapbox": mapbox,
4890  *      "OpenStreetMap": osm
4891  * };
4892  *
4893  * var overlays = {
4894  *      "Marker": marker,
4895  *      "Roads": roadsLayer
4896  * };
4897  *
4898  * L.control.layers(baseLayers, overlays).addTo(map);
4899  * ```
4900  *
4901  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4902  *
4903  * ```js
4904  * {
4905  *     "<someName1>": layer1,
4906  *     "<someName2>": layer2
4907  * }
4908  * ```
4909  *
4910  * The layer names can contain HTML, which allows you to add additional styling to the items:
4911  *
4912  * ```js
4913  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4914  * ```
4915  */
4916
4917 var Layers = Control.extend({
4918         // @section
4919         // @aka Control.Layers options
4920         options: {
4921                 // @option collapsed: Boolean = true
4922                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4923                 collapsed: true,
4924                 position: 'topright',
4925
4926                 // @option autoZIndex: Boolean = true
4927                 // 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.
4928                 autoZIndex: true,
4929
4930                 // @option hideSingleBase: Boolean = false
4931                 // If `true`, the base layers in the control will be hidden when there is only one.
4932                 hideSingleBase: false,
4933
4934                 // @option sortLayers: Boolean = false
4935                 // Whether to sort the layers. When `false`, layers will keep the order
4936                 // in which they were added to the control.
4937                 sortLayers: false,
4938
4939                 // @option sortFunction: Function = *
4940                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4941                 // that will be used for sorting the layers, when `sortLayers` is `true`.
4942                 // The function receives both the `L.Layer` instances and their names, as in
4943                 // `sortFunction(layerA, layerB, nameA, nameB)`.
4944                 // By default, it sorts layers alphabetically by their name.
4945                 sortFunction: function (layerA, layerB, nameA, nameB) {
4946                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4947                 }
4948         },
4949
4950         initialize: function (baseLayers, overlays, options) {
4951                 setOptions(this, options);
4952
4953                 this._layerControlInputs = [];
4954                 this._layers = [];
4955                 this._lastZIndex = 0;
4956                 this._handlingClick = false;
4957
4958                 for (var i in baseLayers) {
4959                         this._addLayer(baseLayers[i], i);
4960                 }
4961
4962                 for (i in overlays) {
4963                         this._addLayer(overlays[i], i, true);
4964                 }
4965         },
4966
4967         onAdd: function (map) {
4968                 this._initLayout();
4969                 this._update();
4970
4971                 this._map = map;
4972                 map.on('zoomend', this._checkDisabledLayers, this);
4973
4974                 for (var i = 0; i < this._layers.length; i++) {
4975                         this._layers[i].layer.on('add remove', this._onLayerChange, this);
4976                 }
4977
4978                 return this._container;
4979         },
4980
4981         addTo: function (map) {
4982                 Control.prototype.addTo.call(this, map);
4983                 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4984                 return this._expandIfNotCollapsed();
4985         },
4986
4987         onRemove: function () {
4988                 this._map.off('zoomend', this._checkDisabledLayers, this);
4989
4990                 for (var i = 0; i < this._layers.length; i++) {
4991                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
4992                 }
4993         },
4994
4995         // @method addBaseLayer(layer: Layer, name: String): this
4996         // Adds a base layer (radio button entry) with the given name to the control.
4997         addBaseLayer: function (layer, name) {
4998                 this._addLayer(layer, name);
4999                 return (this._map) ? this._update() : this;
5000         },
5001
5002         // @method addOverlay(layer: Layer, name: String): this
5003         // Adds an overlay (checkbox entry) with the given name to the control.
5004         addOverlay: function (layer, name) {
5005                 this._addLayer(layer, name, true);
5006                 return (this._map) ? this._update() : this;
5007         },
5008
5009         // @method removeLayer(layer: Layer): this
5010         // Remove the given layer from the control.
5011         removeLayer: function (layer) {
5012                 layer.off('add remove', this._onLayerChange, this);
5013
5014                 var obj = this._getLayer(stamp(layer));
5015                 if (obj) {
5016                         this._layers.splice(this._layers.indexOf(obj), 1);
5017                 }
5018                 return (this._map) ? this._update() : this;
5019         },
5020
5021         // @method expand(): this
5022         // Expand the control container if collapsed.
5023         expand: function () {
5024                 addClass(this._container, 'leaflet-control-layers-expanded');
5025                 this._section.style.height = null;
5026                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5027                 if (acceptableHeight < this._section.clientHeight) {
5028                         addClass(this._section, 'leaflet-control-layers-scrollbar');
5029                         this._section.style.height = acceptableHeight + 'px';
5030                 } else {
5031                         removeClass(this._section, 'leaflet-control-layers-scrollbar');
5032                 }
5033                 this._checkDisabledLayers();
5034                 return this;
5035         },
5036
5037         // @method collapse(): this
5038         // Collapse the control container if expanded.
5039         collapse: function () {
5040                 removeClass(this._container, 'leaflet-control-layers-expanded');
5041                 return this;
5042         },
5043
5044         _initLayout: function () {
5045                 var className = 'leaflet-control-layers',
5046                     container = this._container = create$1('div', className),
5047                     collapsed = this.options.collapsed;
5048
5049                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5050                 container.setAttribute('aria-haspopup', true);
5051
5052                 disableClickPropagation(container);
5053                 disableScrollPropagation(container);
5054
5055                 var section = this._section = create$1('section', className + '-list');
5056
5057                 if (collapsed) {
5058                         this._map.on('click', this.collapse, this);
5059
5060                         if (!android) {
5061                                 on(container, {
5062                                         mouseenter: this.expand,
5063                                         mouseleave: this.collapse
5064                                 }, this);
5065                         }
5066                 }
5067
5068                 var link = this._layersLink = create$1('a', className + '-toggle', container);
5069                 link.href = '#';
5070                 link.title = 'Layers';
5071
5072                 if (touch) {
5073                         on(link, 'click', stop);
5074                         on(link, 'click', this.expand, this);
5075                 } else {
5076                         on(link, 'focus', this.expand, this);
5077                 }
5078
5079                 if (!collapsed) {
5080                         this.expand();
5081                 }
5082
5083                 this._baseLayersList = create$1('div', className + '-base', section);
5084                 this._separator = create$1('div', className + '-separator', section);
5085                 this._overlaysList = create$1('div', className + '-overlays', section);
5086
5087                 container.appendChild(section);
5088         },
5089
5090         _getLayer: function (id) {
5091                 for (var i = 0; i < this._layers.length; i++) {
5092
5093                         if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5094                                 return this._layers[i];
5095                         }
5096                 }
5097         },
5098
5099         _addLayer: function (layer, name, overlay) {
5100                 if (this._map) {
5101                         layer.on('add remove', this._onLayerChange, this);
5102                 }
5103
5104                 this._layers.push({
5105                         layer: layer,
5106                         name: name,
5107                         overlay: overlay
5108                 });
5109
5110                 if (this.options.sortLayers) {
5111                         this._layers.sort(bind(function (a, b) {
5112                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5113                         }, this));
5114                 }
5115
5116                 if (this.options.autoZIndex && layer.setZIndex) {
5117                         this._lastZIndex++;
5118                         layer.setZIndex(this._lastZIndex);
5119                 }
5120
5121                 this._expandIfNotCollapsed();
5122         },
5123
5124         _update: function () {
5125                 if (!this._container) { return this; }
5126
5127                 empty(this._baseLayersList);
5128                 empty(this._overlaysList);
5129
5130                 this._layerControlInputs = [];
5131                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5132
5133                 for (i = 0; i < this._layers.length; i++) {
5134                         obj = this._layers[i];
5135                         this._addItem(obj);
5136                         overlaysPresent = overlaysPresent || obj.overlay;
5137                         baseLayersPresent = baseLayersPresent || !obj.overlay;
5138                         baseLayersCount += !obj.overlay ? 1 : 0;
5139                 }
5140
5141                 // Hide base layers section if there's only one layer.
5142                 if (this.options.hideSingleBase) {
5143                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5144                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5145                 }
5146
5147                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5148
5149                 return this;
5150         },
5151
5152         _onLayerChange: function (e) {
5153                 if (!this._handlingClick) {
5154                         this._update();
5155                 }
5156
5157                 var obj = this._getLayer(stamp(e.target));
5158
5159                 // @namespace Map
5160                 // @section Layer events
5161                 // @event baselayerchange: LayersControlEvent
5162                 // Fired when the base layer is changed through the [layer control](#control-layers).
5163                 // @event overlayadd: LayersControlEvent
5164                 // Fired when an overlay is selected through the [layer control](#control-layers).
5165                 // @event overlayremove: LayersControlEvent
5166                 // Fired when an overlay is deselected through the [layer control](#control-layers).
5167                 // @namespace Control.Layers
5168                 var type = obj.overlay ?
5169                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5170                         (e.type === 'add' ? 'baselayerchange' : null);
5171
5172                 if (type) {
5173                         this._map.fire(type, obj);
5174                 }
5175         },
5176
5177         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5178         _createRadioElement: function (name, checked) {
5179
5180                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5181                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5182
5183                 var radioFragment = document.createElement('div');
5184                 radioFragment.innerHTML = radioHtml;
5185
5186                 return radioFragment.firstChild;
5187         },
5188
5189         _addItem: function (obj) {
5190                 var label = document.createElement('label'),
5191                     checked = this._map.hasLayer(obj.layer),
5192                     input;
5193
5194                 if (obj.overlay) {
5195                         input = document.createElement('input');
5196                         input.type = 'checkbox';
5197                         input.className = 'leaflet-control-layers-selector';
5198                         input.defaultChecked = checked;
5199                 } else {
5200                         input = this._createRadioElement('leaflet-base-layers', checked);
5201                 }
5202
5203                 this._layerControlInputs.push(input);
5204                 input.layerId = stamp(obj.layer);
5205
5206                 on(input, 'click', this._onInputClick, this);
5207
5208                 var name = document.createElement('span');
5209                 name.innerHTML = ' ' + obj.name;
5210
5211                 // Helps from preventing layer control flicker when checkboxes are disabled
5212                 // https://github.com/Leaflet/Leaflet/issues/2771
5213                 var holder = document.createElement('div');
5214
5215                 label.appendChild(holder);
5216                 holder.appendChild(input);
5217                 holder.appendChild(name);
5218
5219                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5220                 container.appendChild(label);
5221
5222                 this._checkDisabledLayers();
5223                 return label;
5224         },
5225
5226         _onInputClick: function () {
5227                 var inputs = this._layerControlInputs,
5228                     input, layer;
5229                 var addedLayers = [],
5230                     removedLayers = [];
5231
5232                 this._handlingClick = true;
5233
5234                 for (var i = inputs.length - 1; i >= 0; i--) {
5235                         input = inputs[i];
5236                         layer = this._getLayer(input.layerId).layer;
5237
5238                         if (input.checked) {
5239                                 addedLayers.push(layer);
5240                         } else if (!input.checked) {
5241                                 removedLayers.push(layer);
5242                         }
5243                 }
5244
5245                 // Bugfix issue 2318: Should remove all old layers before readding new ones
5246                 for (i = 0; i < removedLayers.length; i++) {
5247                         if (this._map.hasLayer(removedLayers[i])) {
5248                                 this._map.removeLayer(removedLayers[i]);
5249                         }
5250                 }
5251                 for (i = 0; i < addedLayers.length; i++) {
5252                         if (!this._map.hasLayer(addedLayers[i])) {
5253                                 this._map.addLayer(addedLayers[i]);
5254                         }
5255                 }
5256
5257                 this._handlingClick = false;
5258
5259                 this._refocusOnMap();
5260         },
5261
5262         _checkDisabledLayers: function () {
5263                 var inputs = this._layerControlInputs,
5264                     input,
5265                     layer,
5266                     zoom = this._map.getZoom();
5267
5268                 for (var i = inputs.length - 1; i >= 0; i--) {
5269                         input = inputs[i];
5270                         layer = this._getLayer(input.layerId).layer;
5271                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5272                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5273
5274                 }
5275         },
5276
5277         _expandIfNotCollapsed: function () {
5278                 if (this._map && !this.options.collapsed) {
5279                         this.expand();
5280                 }
5281                 return this;
5282         },
5283
5284         _expand: function () {
5285                 // Backward compatibility, remove me in 1.1.
5286                 return this.expand();
5287         },
5288
5289         _collapse: function () {
5290                 // Backward compatibility, remove me in 1.1.
5291                 return this.collapse();
5292         }
5293
5294 });
5295
5296
5297 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5298 // 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.
5299 var layers = function (baseLayers, overlays, options) {
5300         return new Layers(baseLayers, overlays, options);
5301 };
5302
5303 /*
5304  * @class Control.Zoom
5305  * @aka L.Control.Zoom
5306  * @inherits Control
5307  *
5308  * 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`.
5309  */
5310
5311 var Zoom = Control.extend({
5312         // @section
5313         // @aka Control.Zoom options
5314         options: {
5315                 position: 'topleft',
5316
5317                 // @option zoomInText: String = '+'
5318                 // The text set on the 'zoom in' button.
5319                 zoomInText: '+',
5320
5321                 // @option zoomInTitle: String = 'Zoom in'
5322                 // The title set on the 'zoom in' button.
5323                 zoomInTitle: 'Zoom in',
5324
5325                 // @option zoomOutText: String = '&#x2212;'
5326                 // The text set on the 'zoom out' button.
5327                 zoomOutText: '&#x2212;',
5328
5329                 // @option zoomOutTitle: String = 'Zoom out'
5330                 // The title set on the 'zoom out' button.
5331                 zoomOutTitle: 'Zoom out'
5332         },
5333
5334         onAdd: function (map) {
5335                 var zoomName = 'leaflet-control-zoom',
5336                     container = create$1('div', zoomName + ' leaflet-bar'),
5337                     options = this.options;
5338
5339                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
5340                         zoomName + '-in',  container, this._zoomIn);
5341                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5342                         zoomName + '-out', container, this._zoomOut);
5343
5344                 this._updateDisabled();
5345                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5346
5347                 return container;
5348         },
5349
5350         onRemove: function (map) {
5351                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5352         },
5353
5354         disable: function () {
5355                 this._disabled = true;
5356                 this._updateDisabled();
5357                 return this;
5358         },
5359
5360         enable: function () {
5361                 this._disabled = false;
5362                 this._updateDisabled();
5363                 return this;
5364         },
5365
5366         _zoomIn: function (e) {
5367                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5368                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5369                 }
5370         },
5371
5372         _zoomOut: function (e) {
5373                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5374                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5375                 }
5376         },
5377
5378         _createButton: function (html, title, className, container, fn) {
5379                 var link = create$1('a', className, container);
5380                 link.innerHTML = html;
5381                 link.href = '#';
5382                 link.title = title;
5383
5384                 /*
5385                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5386                  */
5387                 link.setAttribute('role', 'button');
5388                 link.setAttribute('aria-label', title);
5389
5390                 disableClickPropagation(link);
5391                 on(link, 'click', stop);
5392                 on(link, 'click', fn, this);
5393                 on(link, 'click', this._refocusOnMap, this);
5394
5395                 return link;
5396         },
5397
5398         _updateDisabled: function () {
5399                 var map = this._map,
5400                     className = 'leaflet-disabled';
5401
5402                 removeClass(this._zoomInButton, className);
5403                 removeClass(this._zoomOutButton, className);
5404
5405                 if (this._disabled || map._zoom === map.getMinZoom()) {
5406                         addClass(this._zoomOutButton, className);
5407                 }
5408                 if (this._disabled || map._zoom === map.getMaxZoom()) {
5409                         addClass(this._zoomInButton, className);
5410                 }
5411         }
5412 });
5413
5414 // @namespace Map
5415 // @section Control options
5416 // @option zoomControl: Boolean = true
5417 // Whether a [zoom control](#control-zoom) is added to the map by default.
5418 Map.mergeOptions({
5419         zoomControl: true
5420 });
5421
5422 Map.addInitHook(function () {
5423         if (this.options.zoomControl) {
5424                 // @section Controls
5425                 // @property zoomControl: Control.Zoom
5426                 // The default zoom control (only available if the
5427                 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5428                 this.zoomControl = new Zoom();
5429                 this.addControl(this.zoomControl);
5430         }
5431 });
5432
5433 // @namespace Control.Zoom
5434 // @factory L.control.zoom(options: Control.Zoom options)
5435 // Creates a zoom control
5436 var zoom = function (options) {
5437         return new Zoom(options);
5438 };
5439
5440 /*
5441  * @class Control.Scale
5442  * @aka L.Control.Scale
5443  * @inherits Control
5444  *
5445  * 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`.
5446  *
5447  * @example
5448  *
5449  * ```js
5450  * L.control.scale().addTo(map);
5451  * ```
5452  */
5453
5454 var Scale = Control.extend({
5455         // @section
5456         // @aka Control.Scale options
5457         options: {
5458                 position: 'bottomleft',
5459
5460                 // @option maxWidth: Number = 100
5461                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5462                 maxWidth: 100,
5463
5464                 // @option metric: Boolean = True
5465                 // Whether to show the metric scale line (m/km).
5466                 metric: true,
5467
5468                 // @option imperial: Boolean = True
5469                 // Whether to show the imperial scale line (mi/ft).
5470                 imperial: true
5471
5472                 // @option updateWhenIdle: Boolean = false
5473                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5474         },
5475
5476         onAdd: function (map) {
5477                 var className = 'leaflet-control-scale',
5478                     container = create$1('div', className),
5479                     options = this.options;
5480
5481                 this._addScales(options, className + '-line', container);
5482
5483                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5484                 map.whenReady(this._update, this);
5485
5486                 return container;
5487         },
5488
5489         onRemove: function (map) {
5490                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5491         },
5492
5493         _addScales: function (options, className, container) {
5494                 if (options.metric) {
5495                         this._mScale = create$1('div', className, container);
5496                 }
5497                 if (options.imperial) {
5498                         this._iScale = create$1('div', className, container);
5499                 }
5500         },
5501
5502         _update: function () {
5503                 var map = this._map,
5504                     y = map.getSize().y / 2;
5505
5506                 var maxMeters = map.distance(
5507                         map.containerPointToLatLng([0, y]),
5508                         map.containerPointToLatLng([this.options.maxWidth, y]));
5509
5510                 this._updateScales(maxMeters);
5511         },
5512
5513         _updateScales: function (maxMeters) {
5514                 if (this.options.metric && maxMeters) {
5515                         this._updateMetric(maxMeters);
5516                 }
5517                 if (this.options.imperial && maxMeters) {
5518                         this._updateImperial(maxMeters);
5519                 }
5520         },
5521
5522         _updateMetric: function (maxMeters) {
5523                 var meters = this._getRoundNum(maxMeters),
5524                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5525
5526                 this._updateScale(this._mScale, label, meters / maxMeters);
5527         },
5528
5529         _updateImperial: function (maxMeters) {
5530                 var maxFeet = maxMeters * 3.2808399,
5531                     maxMiles, miles, feet;
5532
5533                 if (maxFeet > 5280) {
5534                         maxMiles = maxFeet / 5280;
5535                         miles = this._getRoundNum(maxMiles);
5536                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5537
5538                 } else {
5539                         feet = this._getRoundNum(maxFeet);
5540                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5541                 }
5542         },
5543
5544         _updateScale: function (scale, text, ratio) {
5545                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5546                 scale.innerHTML = text;
5547         },
5548
5549         _getRoundNum: function (num) {
5550                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5551                     d = num / pow10;
5552
5553                 d = d >= 10 ? 10 :
5554                     d >= 5 ? 5 :
5555                     d >= 3 ? 3 :
5556                     d >= 2 ? 2 : 1;
5557
5558                 return pow10 * d;
5559         }
5560 });
5561
5562
5563 // @factory L.control.scale(options?: Control.Scale options)
5564 // Creates an scale control with the given options.
5565 var scale = function (options) {
5566         return new Scale(options);
5567 };
5568
5569 /*
5570  * @class Control.Attribution
5571  * @aka L.Control.Attribution
5572  * @inherits Control
5573  *
5574  * 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.
5575  */
5576
5577 var Attribution = Control.extend({
5578         // @section
5579         // @aka Control.Attribution options
5580         options: {
5581                 position: 'bottomright',
5582
5583                 // @option prefix: String = 'Leaflet'
5584                 // The HTML text shown before the attributions. Pass `false` to disable.
5585                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5586         },
5587
5588         initialize: function (options) {
5589                 setOptions(this, options);
5590
5591                 this._attributions = {};
5592         },
5593
5594         onAdd: function (map) {
5595                 map.attributionControl = this;
5596                 this._container = create$1('div', 'leaflet-control-attribution');
5597                 disableClickPropagation(this._container);
5598
5599                 // TODO ugly, refactor
5600                 for (var i in map._layers) {
5601                         if (map._layers[i].getAttribution) {
5602                                 this.addAttribution(map._layers[i].getAttribution());
5603                         }
5604                 }
5605
5606                 this._update();
5607
5608                 return this._container;
5609         },
5610
5611         // @method setPrefix(prefix: String): this
5612         // Sets the text before the attributions.
5613         setPrefix: function (prefix) {
5614                 this.options.prefix = prefix;
5615                 this._update();
5616                 return this;
5617         },
5618
5619         // @method addAttribution(text: String): this
5620         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5621         addAttribution: function (text) {
5622                 if (!text) { return this; }
5623
5624                 if (!this._attributions[text]) {
5625                         this._attributions[text] = 0;
5626                 }
5627                 this._attributions[text]++;
5628
5629                 this._update();
5630
5631                 return this;
5632         },
5633
5634         // @method removeAttribution(text: String): this
5635         // Removes an attribution text.
5636         removeAttribution: function (text) {
5637                 if (!text) { return this; }
5638
5639                 if (this._attributions[text]) {
5640                         this._attributions[text]--;
5641                         this._update();
5642                 }
5643
5644                 return this;
5645         },
5646
5647         _update: function () {
5648                 if (!this._map) { return; }
5649
5650                 var attribs = [];
5651
5652                 for (var i in this._attributions) {
5653                         if (this._attributions[i]) {
5654                                 attribs.push(i);
5655                         }
5656                 }
5657
5658                 var prefixAndAttribs = [];
5659
5660                 if (this.options.prefix) {
5661                         prefixAndAttribs.push(this.options.prefix);
5662                 }
5663                 if (attribs.length) {
5664                         prefixAndAttribs.push(attribs.join(', '));
5665                 }
5666
5667                 this._container.innerHTML = prefixAndAttribs.join(' | ');
5668         }
5669 });
5670
5671 // @namespace Map
5672 // @section Control options
5673 // @option attributionControl: Boolean = true
5674 // Whether a [attribution control](#control-attribution) is added to the map by default.
5675 Map.mergeOptions({
5676         attributionControl: true
5677 });
5678
5679 Map.addInitHook(function () {
5680         if (this.options.attributionControl) {
5681                 new Attribution().addTo(this);
5682         }
5683 });
5684
5685 // @namespace Control.Attribution
5686 // @factory L.control.attribution(options: Control.Attribution options)
5687 // Creates an attribution control.
5688 var attribution = function (options) {
5689         return new Attribution(options);
5690 };
5691
5692 Control.Layers = Layers;
5693 Control.Zoom = Zoom;
5694 Control.Scale = Scale;
5695 Control.Attribution = Attribution;
5696
5697 control.layers = layers;
5698 control.zoom = zoom;
5699 control.scale = scale;
5700 control.attribution = attribution;
5701
5702 /*
5703         L.Handler is a base class for handler classes that are used internally to inject
5704         interaction features like dragging to classes like Map and Marker.
5705 */
5706
5707 // @class Handler
5708 // @aka L.Handler
5709 // Abstract class for map interaction handlers
5710
5711 var Handler = Class.extend({
5712         initialize: function (map) {
5713                 this._map = map;
5714         },
5715
5716         // @method enable(): this
5717         // Enables the handler
5718         enable: function () {
5719                 if (this._enabled) { return this; }
5720
5721                 this._enabled = true;
5722                 this.addHooks();
5723                 return this;
5724         },
5725
5726         // @method disable(): this
5727         // Disables the handler
5728         disable: function () {
5729                 if (!this._enabled) { return this; }
5730
5731                 this._enabled = false;
5732                 this.removeHooks();
5733                 return this;
5734         },
5735
5736         // @method enabled(): Boolean
5737         // Returns `true` if the handler is enabled
5738         enabled: function () {
5739                 return !!this._enabled;
5740         }
5741
5742         // @section Extension methods
5743         // Classes inheriting from `Handler` must implement the two following methods:
5744         // @method addHooks()
5745         // Called when the handler is enabled, should add event hooks.
5746         // @method removeHooks()
5747         // Called when the handler is disabled, should remove the event hooks added previously.
5748 });
5749
5750 // @section There is static function which can be called without instantiating L.Handler:
5751 // @function addTo(map: Map, name: String): this
5752 // Adds a new Handler to the given map with the given name.
5753 Handler.addTo = function (map, name) {
5754         map.addHandler(name, this);
5755         return this;
5756 };
5757
5758 var Mixin = {Events: Events};
5759
5760 /*
5761  * @class Draggable
5762  * @aka L.Draggable
5763  * @inherits Evented
5764  *
5765  * A class for making DOM elements draggable (including touch support).
5766  * Used internally for map and marker dragging. Only works for elements
5767  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5768  *
5769  * @example
5770  * ```js
5771  * var draggable = new L.Draggable(elementToDrag);
5772  * draggable.enable();
5773  * ```
5774  */
5775
5776 var START = touch ? 'touchstart mousedown' : 'mousedown';
5777 var END = {
5778         mousedown: 'mouseup',
5779         touchstart: 'touchend',
5780         pointerdown: 'touchend',
5781         MSPointerDown: 'touchend'
5782 };
5783 var MOVE = {
5784         mousedown: 'mousemove',
5785         touchstart: 'touchmove',
5786         pointerdown: 'touchmove',
5787         MSPointerDown: 'touchmove'
5788 };
5789
5790
5791 var Draggable = Evented.extend({
5792
5793         options: {
5794                 // @section
5795                 // @aka Draggable options
5796                 // @option clickTolerance: Number = 3
5797                 // The max number of pixels a user can shift the mouse pointer during a click
5798                 // for it to be considered a valid click (as opposed to a mouse drag).
5799                 clickTolerance: 3
5800         },
5801
5802         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5803         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5804         initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5805                 setOptions(this, options);
5806
5807                 this._element = element;
5808                 this._dragStartTarget = dragStartTarget || element;
5809                 this._preventOutline = preventOutline$$1;
5810         },
5811
5812         // @method enable()
5813         // Enables the dragging ability
5814         enable: function () {
5815                 if (this._enabled) { return; }
5816
5817                 on(this._dragStartTarget, START, this._onDown, this);
5818
5819                 this._enabled = true;
5820         },
5821
5822         // @method disable()
5823         // Disables the dragging ability
5824         disable: function () {
5825                 if (!this._enabled) { return; }
5826
5827                 // If we're currently dragging this draggable,
5828                 // disabling it counts as first ending the drag.
5829                 if (Draggable._dragging === this) {
5830                         this.finishDrag();
5831                 }
5832
5833                 off(this._dragStartTarget, START, this._onDown, this);
5834
5835                 this._enabled = false;
5836                 this._moved = false;
5837         },
5838
5839         _onDown: function (e) {
5840                 // Ignore simulated events, since we handle both touch and
5841                 // mouse explicitly; otherwise we risk getting duplicates of
5842                 // touch events, see #4315.
5843                 // Also ignore the event if disabled; this happens in IE11
5844                 // under some circumstances, see #3666.
5845                 if (e._simulated || !this._enabled) { return; }
5846
5847                 this._moved = false;
5848
5849                 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5850
5851                 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5852                 Draggable._dragging = this;  // Prevent dragging multiple objects at once.
5853
5854                 if (this._preventOutline) {
5855                         preventOutline(this._element);
5856                 }
5857
5858                 disableImageDrag();
5859                 disableTextSelection();
5860
5861                 if (this._moving) { return; }
5862
5863                 // @event down: Event
5864                 // Fired when a drag is about to start.
5865                 this.fire('down');
5866
5867                 var first = e.touches ? e.touches[0] : e,
5868                     sizedParent = getSizedParentNode(this._element);
5869
5870                 this._startPoint = new Point(first.clientX, first.clientY);
5871
5872                 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5873                 this._parentScale = getScale(sizedParent);
5874
5875                 on(document, MOVE[e.type], this._onMove, this);
5876                 on(document, END[e.type], this._onUp, this);
5877         },
5878
5879         _onMove: function (e) {
5880                 // Ignore simulated events, since we handle both touch and
5881                 // mouse explicitly; otherwise we risk getting duplicates of
5882                 // touch events, see #4315.
5883                 // Also ignore the event if disabled; this happens in IE11
5884                 // under some circumstances, see #3666.
5885                 if (e._simulated || !this._enabled) { return; }
5886
5887                 if (e.touches && e.touches.length > 1) {
5888                         this._moved = true;
5889                         return;
5890                 }
5891
5892                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5893                     offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5894
5895                 if (!offset.x && !offset.y) { return; }
5896                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5897
5898                 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5899                 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5900                 // and we can use the cached value for the scale.
5901                 offset.x /= this._parentScale.x;
5902                 offset.y /= this._parentScale.y;
5903
5904                 preventDefault(e);
5905
5906                 if (!this._moved) {
5907                         // @event dragstart: Event
5908                         // Fired when a drag starts
5909                         this.fire('dragstart');
5910
5911                         this._moved = true;
5912                         this._startPos = getPosition(this._element).subtract(offset);
5913
5914                         addClass(document.body, 'leaflet-dragging');
5915
5916                         this._lastTarget = e.target || e.srcElement;
5917                         // IE and Edge do not give the <use> element, so fetch it
5918                         // if necessary
5919                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5920                                 this._lastTarget = this._lastTarget.correspondingUseElement;
5921                         }
5922                         addClass(this._lastTarget, 'leaflet-drag-target');
5923                 }
5924
5925                 this._newPos = this._startPos.add(offset);
5926                 this._moving = true;
5927
5928                 cancelAnimFrame(this._animRequest);
5929                 this._lastEvent = e;
5930                 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5931         },
5932
5933         _updatePosition: function () {
5934                 var e = {originalEvent: this._lastEvent};
5935
5936                 // @event predrag: Event
5937                 // Fired continuously during dragging *before* each corresponding
5938                 // update of the element's position.
5939                 this.fire('predrag', e);
5940                 setPosition(this._element, this._newPos);
5941
5942                 // @event drag: Event
5943                 // Fired continuously during dragging.
5944                 this.fire('drag', e);
5945         },
5946
5947         _onUp: function (e) {
5948                 // Ignore simulated events, since we handle both touch and
5949                 // mouse explicitly; otherwise we risk getting duplicates of
5950                 // touch events, see #4315.
5951                 // Also ignore the event if disabled; this happens in IE11
5952                 // under some circumstances, see #3666.
5953                 if (e._simulated || !this._enabled) { return; }
5954                 this.finishDrag();
5955         },
5956
5957         finishDrag: function () {
5958                 removeClass(document.body, 'leaflet-dragging');
5959
5960                 if (this._lastTarget) {
5961                         removeClass(this._lastTarget, 'leaflet-drag-target');
5962                         this._lastTarget = null;
5963                 }
5964
5965                 for (var i in MOVE) {
5966                         off(document, MOVE[i], this._onMove, this);
5967                         off(document, END[i], this._onUp, this);
5968                 }
5969
5970                 enableImageDrag();
5971                 enableTextSelection();
5972
5973                 if (this._moved && this._moving) {
5974                         // ensure drag is not fired after dragend
5975                         cancelAnimFrame(this._animRequest);
5976
5977                         // @event dragend: DragEndEvent
5978                         // Fired when the drag ends.
5979                         this.fire('dragend', {
5980                                 distance: this._newPos.distanceTo(this._startPos)
5981                         });
5982                 }
5983
5984                 this._moving = false;
5985                 Draggable._dragging = false;
5986         }
5987
5988 });
5989
5990 /*
5991  * @namespace LineUtil
5992  *
5993  * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
5994  */
5995
5996 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5997 // Improves rendering performance dramatically by lessening the number of points to draw.
5998
5999 // @function simplify(points: Point[], tolerance: Number): Point[]
6000 // Dramatically reduces the number of points in a polyline while retaining
6001 // its shape and returns a new array of simplified points, using the
6002 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
6003 // Used for a huge performance boost when processing/displaying Leaflet polylines for
6004 // each zoom level and also reducing visual noise. tolerance affects the amount of
6005 // simplification (lesser value means higher quality but slower and with more points).
6006 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
6007 function simplify(points, tolerance) {
6008         if (!tolerance || !points.length) {
6009                 return points.slice();
6010         }
6011
6012         var sqTolerance = tolerance * tolerance;
6013
6014             // stage 1: vertex reduction
6015             points = _reducePoints(points, sqTolerance);
6016
6017             // stage 2: Douglas-Peucker simplification
6018             points = _simplifyDP(points, sqTolerance);
6019
6020         return points;
6021 }
6022
6023 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6024 // Returns the distance between point `p` and segment `p1` to `p2`.
6025 function pointToSegmentDistance(p, p1, p2) {
6026         return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6027 }
6028
6029 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6030 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6031 function closestPointOnSegment(p, p1, p2) {
6032         return _sqClosestPointOnSegment(p, p1, p2);
6033 }
6034
6035 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6036 function _simplifyDP(points, sqTolerance) {
6037
6038         var len = points.length,
6039             ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6040             markers = new ArrayConstructor(len);
6041
6042             markers[0] = markers[len - 1] = 1;
6043
6044         _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6045
6046         var i,
6047             newPoints = [];
6048
6049         for (i = 0; i < len; i++) {
6050                 if (markers[i]) {
6051                         newPoints.push(points[i]);
6052                 }
6053         }
6054
6055         return newPoints;
6056 }
6057
6058 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6059
6060         var maxSqDist = 0,
6061         index, i, sqDist;
6062
6063         for (i = first + 1; i <= last - 1; i++) {
6064                 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6065
6066                 if (sqDist > maxSqDist) {
6067                         index = i;
6068                         maxSqDist = sqDist;
6069                 }
6070         }
6071
6072         if (maxSqDist > sqTolerance) {
6073                 markers[index] = 1;
6074
6075                 _simplifyDPStep(points, markers, sqTolerance, first, index);
6076                 _simplifyDPStep(points, markers, sqTolerance, index, last);
6077         }
6078 }
6079
6080 // reduce points that are too close to each other to a single point
6081 function _reducePoints(points, sqTolerance) {
6082         var reducedPoints = [points[0]];
6083
6084         for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6085                 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6086                         reducedPoints.push(points[i]);
6087                         prev = i;
6088                 }
6089         }
6090         if (prev < len - 1) {
6091                 reducedPoints.push(points[len - 1]);
6092         }
6093         return reducedPoints;
6094 }
6095
6096 var _lastCode;
6097
6098 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6099 // Clips the segment a to b by rectangular bounds with the
6100 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6101 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6102 // points that are on the screen or near, increasing performance.
6103 function clipSegment(a, b, bounds, useLastCode, round) {
6104         var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6105             codeB = _getBitCode(b, bounds),
6106
6107             codeOut, p, newCode;
6108
6109             // save 2nd code to avoid calculating it on the next segment
6110             _lastCode = codeB;
6111
6112         while (true) {
6113                 // if a,b is inside the clip window (trivial accept)
6114                 if (!(codeA | codeB)) {
6115                         return [a, b];
6116                 }
6117
6118                 // if a,b is outside the clip window (trivial reject)
6119                 if (codeA & codeB) {
6120                         return false;
6121                 }
6122
6123                 // other cases
6124                 codeOut = codeA || codeB;
6125                 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6126                 newCode = _getBitCode(p, bounds);
6127
6128                 if (codeOut === codeA) {
6129                         a = p;
6130                         codeA = newCode;
6131                 } else {
6132                         b = p;
6133                         codeB = newCode;
6134                 }
6135         }
6136 }
6137
6138 function _getEdgeIntersection(a, b, code, bounds, round) {
6139         var dx = b.x - a.x,
6140             dy = b.y - a.y,
6141             min = bounds.min,
6142             max = bounds.max,
6143             x, y;
6144
6145         if (code & 8) { // top
6146                 x = a.x + dx * (max.y - a.y) / dy;
6147                 y = max.y;
6148
6149         } else if (code & 4) { // bottom
6150                 x = a.x + dx * (min.y - a.y) / dy;
6151                 y = min.y;
6152
6153         } else if (code & 2) { // right
6154                 x = max.x;
6155                 y = a.y + dy * (max.x - a.x) / dx;
6156
6157         } else if (code & 1) { // left
6158                 x = min.x;
6159                 y = a.y + dy * (min.x - a.x) / dx;
6160         }
6161
6162         return new Point(x, y, round);
6163 }
6164
6165 function _getBitCode(p, bounds) {
6166         var code = 0;
6167
6168         if (p.x < bounds.min.x) { // left
6169                 code |= 1;
6170         } else if (p.x > bounds.max.x) { // right
6171                 code |= 2;
6172         }
6173
6174         if (p.y < bounds.min.y) { // bottom
6175                 code |= 4;
6176         } else if (p.y > bounds.max.y) { // top
6177                 code |= 8;
6178         }
6179
6180         return code;
6181 }
6182
6183 // square distance (to avoid unnecessary Math.sqrt calls)
6184 function _sqDist(p1, p2) {
6185         var dx = p2.x - p1.x,
6186             dy = p2.y - p1.y;
6187         return dx * dx + dy * dy;
6188 }
6189
6190 // return closest point on segment or distance to that point
6191 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6192         var x = p1.x,
6193             y = p1.y,
6194             dx = p2.x - x,
6195             dy = p2.y - y,
6196             dot = dx * dx + dy * dy,
6197             t;
6198
6199         if (dot > 0) {
6200                 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6201
6202                 if (t > 1) {
6203                         x = p2.x;
6204                         y = p2.y;
6205                 } else if (t > 0) {
6206                         x += dx * t;
6207                         y += dy * t;
6208                 }
6209         }
6210
6211         dx = p.x - x;
6212         dy = p.y - y;
6213
6214         return sqDist ? dx * dx + dy * dy : new Point(x, y);
6215 }
6216
6217
6218 // @function isFlat(latlngs: LatLng[]): Boolean
6219 // Returns true if `latlngs` is a flat array, false is nested.
6220 function isFlat(latlngs) {
6221         return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6222 }
6223
6224 function _flat(latlngs) {
6225         console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6226         return isFlat(latlngs);
6227 }
6228
6229
6230 var LineUtil = (Object.freeze || Object)({
6231         simplify: simplify,
6232         pointToSegmentDistance: pointToSegmentDistance,
6233         closestPointOnSegment: closestPointOnSegment,
6234         clipSegment: clipSegment,
6235         _getEdgeIntersection: _getEdgeIntersection,
6236         _getBitCode: _getBitCode,
6237         _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6238         isFlat: isFlat,
6239         _flat: _flat
6240 });
6241
6242 /*
6243  * @namespace PolyUtil
6244  * Various utility functions for polygon geometries.
6245  */
6246
6247 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6248  * 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)).
6249  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6250  * performance. Note that polygon points needs different algorithm for clipping
6251  * than polyline, so there's a separate method for it.
6252  */
6253 function clipPolygon(points, bounds, round) {
6254         var clippedPoints,
6255             edges = [1, 4, 2, 8],
6256             i, j, k,
6257             a, b,
6258             len, edge, p;
6259
6260         for (i = 0, len = points.length; i < len; i++) {
6261                 points[i]._code = _getBitCode(points[i], bounds);
6262         }
6263
6264         // for each edge (left, bottom, right, top)
6265         for (k = 0; k < 4; k++) {
6266                 edge = edges[k];
6267                 clippedPoints = [];
6268
6269                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6270                         a = points[i];
6271                         b = points[j];
6272
6273                         // if a is inside the clip window
6274                         if (!(a._code & edge)) {
6275                                 // if b is outside the clip window (a->b goes out of screen)
6276                                 if (b._code & edge) {
6277                                         p = _getEdgeIntersection(b, a, edge, bounds, round);
6278                                         p._code = _getBitCode(p, bounds);
6279                                         clippedPoints.push(p);
6280                                 }
6281                                 clippedPoints.push(a);
6282
6283                         // else if b is inside the clip window (a->b enters the screen)
6284                         } else if (!(b._code & edge)) {
6285                                 p = _getEdgeIntersection(b, a, edge, bounds, round);
6286                                 p._code = _getBitCode(p, bounds);
6287                                 clippedPoints.push(p);
6288                         }
6289                 }
6290                 points = clippedPoints;
6291         }
6292
6293         return points;
6294 }
6295
6296
6297 var PolyUtil = (Object.freeze || Object)({
6298         clipPolygon: clipPolygon
6299 });
6300
6301 /*
6302  * @namespace Projection
6303  * @section
6304  * Leaflet comes with a set of already defined Projections out of the box:
6305  *
6306  * @projection L.Projection.LonLat
6307  *
6308  * Equirectangular, or Plate Carree projection — the most simple projection,
6309  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6310  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6311  * `EPSG:4326` and `Simple` CRS.
6312  */
6313
6314 var LonLat = {
6315         project: function (latlng) {
6316                 return new Point(latlng.lng, latlng.lat);
6317         },
6318
6319         unproject: function (point) {
6320                 return new LatLng(point.y, point.x);
6321         },
6322
6323         bounds: new Bounds([-180, -90], [180, 90])
6324 };
6325
6326 /*
6327  * @namespace Projection
6328  * @projection L.Projection.Mercator
6329  *
6330  * 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.
6331  */
6332
6333 var Mercator = {
6334         R: 6378137,
6335         R_MINOR: 6356752.314245179,
6336
6337         bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6338
6339         project: function (latlng) {
6340                 var d = Math.PI / 180,
6341                     r = this.R,
6342                     y = latlng.lat * d,
6343                     tmp = this.R_MINOR / r,
6344                     e = Math.sqrt(1 - tmp * tmp),
6345                     con = e * Math.sin(y);
6346
6347                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6348                 y = -r * Math.log(Math.max(ts, 1E-10));
6349
6350                 return new Point(latlng.lng * d * r, y);
6351         },
6352
6353         unproject: function (point) {
6354                 var d = 180 / Math.PI,
6355                     r = this.R,
6356                     tmp = this.R_MINOR / r,
6357                     e = Math.sqrt(1 - tmp * tmp),
6358                     ts = Math.exp(-point.y / r),
6359                     phi = Math.PI / 2 - 2 * Math.atan(ts);
6360
6361                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6362                         con = e * Math.sin(phi);
6363                         con = Math.pow((1 - con) / (1 + con), e / 2);
6364                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6365                         phi += dphi;
6366                 }
6367
6368                 return new LatLng(phi * d, point.x * d / r);
6369         }
6370 };
6371
6372 /*
6373  * @class Projection
6374
6375  * An object with methods for projecting geographical coordinates of the world onto
6376  * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6377
6378  * @property bounds: Bounds
6379  * The bounds (specified in CRS units) where the projection is valid
6380
6381  * @method project(latlng: LatLng): Point
6382  * Projects geographical coordinates into a 2D point.
6383  * Only accepts actual `L.LatLng` instances, not arrays.
6384
6385  * @method unproject(point: Point): LatLng
6386  * The inverse of `project`. Projects a 2D point into a geographical location.
6387  * Only accepts actual `L.Point` instances, not arrays.
6388
6389  * Note that the projection instances do not inherit from Leafet's `Class` object,
6390  * and can't be instantiated. Also, new classes can't inherit from them,
6391  * and methods can't be added to them with the `include` function.
6392
6393  */
6394
6395
6396
6397
6398 var index = (Object.freeze || Object)({
6399         LonLat: LonLat,
6400         Mercator: Mercator,
6401         SphericalMercator: SphericalMercator
6402 });
6403
6404 /*
6405  * @namespace CRS
6406  * @crs L.CRS.EPSG3395
6407  *
6408  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6409  */
6410 var EPSG3395 = extend({}, Earth, {
6411         code: 'EPSG:3395',
6412         projection: Mercator,
6413
6414         transformation: (function () {
6415                 var scale = 0.5 / (Math.PI * Mercator.R);
6416                 return toTransformation(scale, 0.5, -scale, 0.5);
6417         }())
6418 });
6419
6420 /*
6421  * @namespace CRS
6422  * @crs L.CRS.EPSG4326
6423  *
6424  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6425  *
6426  * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6427  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
6428  * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6429  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6430  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6431  */
6432
6433 var EPSG4326 = extend({}, Earth, {
6434         code: 'EPSG:4326',
6435         projection: LonLat,
6436         transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6437 });
6438
6439 /*
6440  * @namespace CRS
6441  * @crs L.CRS.Simple
6442  *
6443  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6444  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6445  * axis should still be inverted (going from bottom to top). `distance()` returns
6446  * simple euclidean distance.
6447  */
6448
6449 var Simple = extend({}, CRS, {
6450         projection: LonLat,
6451         transformation: toTransformation(1, 0, -1, 0),
6452
6453         scale: function (zoom) {
6454                 return Math.pow(2, zoom);
6455         },
6456
6457         zoom: function (scale) {
6458                 return Math.log(scale) / Math.LN2;
6459         },
6460
6461         distance: function (latlng1, latlng2) {
6462                 var dx = latlng2.lng - latlng1.lng,
6463                     dy = latlng2.lat - latlng1.lat;
6464
6465                 return Math.sqrt(dx * dx + dy * dy);
6466         },
6467
6468         infinite: true
6469 });
6470
6471 CRS.Earth = Earth;
6472 CRS.EPSG3395 = EPSG3395;
6473 CRS.EPSG3857 = EPSG3857;
6474 CRS.EPSG900913 = EPSG900913;
6475 CRS.EPSG4326 = EPSG4326;
6476 CRS.Simple = Simple;
6477
6478 /*
6479  * @class Layer
6480  * @inherits Evented
6481  * @aka L.Layer
6482  * @aka ILayer
6483  *
6484  * A set of methods from the Layer base class that all Leaflet layers use.
6485  * Inherits all methods, options and events from `L.Evented`.
6486  *
6487  * @example
6488  *
6489  * ```js
6490  * var layer = L.Marker(latlng).addTo(map);
6491  * layer.addTo(map);
6492  * layer.remove();
6493  * ```
6494  *
6495  * @event add: Event
6496  * Fired after the layer is added to a map
6497  *
6498  * @event remove: Event
6499  * Fired after the layer is removed from a map
6500  */
6501
6502
6503 var Layer = Evented.extend({
6504
6505         // Classes extending `L.Layer` will inherit the following options:
6506         options: {
6507                 // @option pane: String = 'overlayPane'
6508                 // 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.
6509                 pane: 'overlayPane',
6510
6511                 // @option attribution: String = null
6512                 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
6513                 attribution: null,
6514
6515                 bubblingMouseEvents: true
6516         },
6517
6518         /* @section
6519          * Classes extending `L.Layer` will inherit the following methods:
6520          *
6521          * @method addTo(map: Map|LayerGroup): this
6522          * Adds the layer to the given map or layer group.
6523          */
6524         addTo: function (map) {
6525                 map.addLayer(this);
6526                 return this;
6527         },
6528
6529         // @method remove: this
6530         // Removes the layer from the map it is currently active on.
6531         remove: function () {
6532                 return this.removeFrom(this._map || this._mapToAdd);
6533         },
6534
6535         // @method removeFrom(map: Map): this
6536         // Removes the layer from the given map
6537         removeFrom: function (obj) {
6538                 if (obj) {
6539                         obj.removeLayer(this);
6540                 }
6541                 return this;
6542         },
6543
6544         // @method getPane(name? : String): HTMLElement
6545         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6546         getPane: function (name) {
6547                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6548         },
6549
6550         addInteractiveTarget: function (targetEl) {
6551                 this._map._targets[stamp(targetEl)] = this;
6552                 return this;
6553         },
6554
6555         removeInteractiveTarget: function (targetEl) {
6556                 delete this._map._targets[stamp(targetEl)];
6557                 return this;
6558         },
6559
6560         // @method getAttribution: String
6561         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6562         getAttribution: function () {
6563                 return this.options.attribution;
6564         },
6565
6566         _layerAdd: function (e) {
6567                 var map = e.target;
6568
6569                 // check in case layer gets added and then removed before the map is ready
6570                 if (!map.hasLayer(this)) { return; }
6571
6572                 this._map = map;
6573                 this._zoomAnimated = map._zoomAnimated;
6574
6575                 if (this.getEvents) {
6576                         var events = this.getEvents();
6577                         map.on(events, this);
6578                         this.once('remove', function () {
6579                                 map.off(events, this);
6580                         }, this);
6581                 }
6582
6583                 this.onAdd(map);
6584
6585                 if (this.getAttribution && map.attributionControl) {
6586                         map.attributionControl.addAttribution(this.getAttribution());
6587                 }
6588
6589                 this.fire('add');
6590                 map.fire('layeradd', {layer: this});
6591         }
6592 });
6593
6594 /* @section Extension methods
6595  * @uninheritable
6596  *
6597  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6598  *
6599  * @method onAdd(map: Map): this
6600  * 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).
6601  *
6602  * @method onRemove(map: Map): this
6603  * 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).
6604  *
6605  * @method getEvents(): Object
6606  * 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.
6607  *
6608  * @method getAttribution(): String
6609  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6610  *
6611  * @method beforeAdd(map: Map): this
6612  * 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.
6613  */
6614
6615
6616 /* @namespace Map
6617  * @section Layer events
6618  *
6619  * @event layeradd: LayerEvent
6620  * Fired when a new layer is added to the map.
6621  *
6622  * @event layerremove: LayerEvent
6623  * Fired when some layer is removed from the map
6624  *
6625  * @section Methods for Layers and Controls
6626  */
6627 Map.include({
6628         // @method addLayer(layer: Layer): this
6629         // Adds the given layer to the map
6630         addLayer: function (layer) {
6631                 if (!layer._layerAdd) {
6632                         throw new Error('The provided object is not a Layer.');
6633                 }
6634
6635                 var id = stamp(layer);
6636                 if (this._layers[id]) { return this; }
6637                 this._layers[id] = layer;
6638
6639                 layer._mapToAdd = this;
6640
6641                 if (layer.beforeAdd) {
6642                         layer.beforeAdd(this);
6643                 }
6644
6645                 this.whenReady(layer._layerAdd, layer);
6646
6647                 return this;
6648         },
6649
6650         // @method removeLayer(layer: Layer): this
6651         // Removes the given layer from the map.
6652         removeLayer: function (layer) {
6653                 var id = stamp(layer);
6654
6655                 if (!this._layers[id]) { return this; }
6656
6657                 if (this._loaded) {
6658                         layer.onRemove(this);
6659                 }
6660
6661                 if (layer.getAttribution && this.attributionControl) {
6662                         this.attributionControl.removeAttribution(layer.getAttribution());
6663                 }
6664
6665                 delete this._layers[id];
6666
6667                 if (this._loaded) {
6668                         this.fire('layerremove', {layer: layer});
6669                         layer.fire('remove');
6670                 }
6671
6672                 layer._map = layer._mapToAdd = null;
6673
6674                 return this;
6675         },
6676
6677         // @method hasLayer(layer: Layer): Boolean
6678         // Returns `true` if the given layer is currently added to the map
6679         hasLayer: function (layer) {
6680                 return !!layer && (stamp(layer) in this._layers);
6681         },
6682
6683         /* @method eachLayer(fn: Function, context?: Object): this
6684          * Iterates over the layers of the map, optionally specifying context of the iterator function.
6685          * ```
6686          * map.eachLayer(function(layer){
6687          *     layer.bindPopup('Hello');
6688          * });
6689          * ```
6690          */
6691         eachLayer: function (method, context) {
6692                 for (var i in this._layers) {
6693                         method.call(context, this._layers[i]);
6694                 }
6695                 return this;
6696         },
6697
6698         _addLayers: function (layers) {
6699                 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6700
6701                 for (var i = 0, len = layers.length; i < len; i++) {
6702                         this.addLayer(layers[i]);
6703                 }
6704         },
6705
6706         _addZoomLimit: function (layer) {
6707                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6708                         this._zoomBoundLayers[stamp(layer)] = layer;
6709                         this._updateZoomLevels();
6710                 }
6711         },
6712
6713         _removeZoomLimit: function (layer) {
6714                 var id = stamp(layer);
6715
6716                 if (this._zoomBoundLayers[id]) {
6717                         delete this._zoomBoundLayers[id];
6718                         this._updateZoomLevels();
6719                 }
6720         },
6721
6722         _updateZoomLevels: function () {
6723                 var minZoom = Infinity,
6724                     maxZoom = -Infinity,
6725                     oldZoomSpan = this._getZoomSpan();
6726
6727                 for (var i in this._zoomBoundLayers) {
6728                         var options = this._zoomBoundLayers[i].options;
6729
6730                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6731                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6732                 }
6733
6734                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6735                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6736
6737                 // @section Map state change events
6738                 // @event zoomlevelschange: Event
6739                 // Fired when the number of zoomlevels on the map is changed due
6740                 // to adding or removing a layer.
6741                 if (oldZoomSpan !== this._getZoomSpan()) {
6742                         this.fire('zoomlevelschange');
6743                 }
6744
6745                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6746                         this.setZoom(this._layersMaxZoom);
6747                 }
6748                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6749                         this.setZoom(this._layersMinZoom);
6750                 }
6751         }
6752 });
6753
6754 /*
6755  * @class LayerGroup
6756  * @aka L.LayerGroup
6757  * @inherits Layer
6758  *
6759  * Used to group several layers and handle them as one. If you add it to the map,
6760  * any layers added or removed from the group will be added/removed on the map as
6761  * well. Extends `Layer`.
6762  *
6763  * @example
6764  *
6765  * ```js
6766  * L.layerGroup([marker1, marker2])
6767  *      .addLayer(polyline)
6768  *      .addTo(map);
6769  * ```
6770  */
6771
6772 var LayerGroup = Layer.extend({
6773
6774         initialize: function (layers, options) {
6775                 setOptions(this, options);
6776
6777                 this._layers = {};
6778
6779                 var i, len;
6780
6781                 if (layers) {
6782                         for (i = 0, len = layers.length; i < len; i++) {
6783                                 this.addLayer(layers[i]);
6784                         }
6785                 }
6786         },
6787
6788         // @method addLayer(layer: Layer): this
6789         // Adds the given layer to the group.
6790         addLayer: function (layer) {
6791                 var id = this.getLayerId(layer);
6792
6793                 this._layers[id] = layer;
6794
6795                 if (this._map) {
6796                         this._map.addLayer(layer);
6797                 }
6798
6799                 return this;
6800         },
6801
6802         // @method removeLayer(layer: Layer): this
6803         // Removes the given layer from the group.
6804         // @alternative
6805         // @method removeLayer(id: Number): this
6806         // Removes the layer with the given internal ID from the group.
6807         removeLayer: function (layer) {
6808                 var id = layer in this._layers ? layer : this.getLayerId(layer);
6809
6810                 if (this._map && this._layers[id]) {
6811                         this._map.removeLayer(this._layers[id]);
6812                 }
6813
6814                 delete this._layers[id];
6815
6816                 return this;
6817         },
6818
6819         // @method hasLayer(layer: Layer): Boolean
6820         // Returns `true` if the given layer is currently added to the group.
6821         // @alternative
6822         // @method hasLayer(id: Number): Boolean
6823         // Returns `true` if the given internal ID is currently added to the group.
6824         hasLayer: function (layer) {
6825                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6826         },
6827
6828         // @method clearLayers(): this
6829         // Removes all the layers from the group.
6830         clearLayers: function () {
6831                 return this.eachLayer(this.removeLayer, this);
6832         },
6833
6834         // @method invoke(methodName: String, …): this
6835         // Calls `methodName` on every layer contained in this group, passing any
6836         // additional parameters. Has no effect if the layers contained do not
6837         // implement `methodName`.
6838         invoke: function (methodName) {
6839                 var args = Array.prototype.slice.call(arguments, 1),
6840                     i, layer;
6841
6842                 for (i in this._layers) {
6843                         layer = this._layers[i];
6844
6845                         if (layer[methodName]) {
6846                                 layer[methodName].apply(layer, args);
6847                         }
6848                 }
6849
6850                 return this;
6851         },
6852
6853         onAdd: function (map) {
6854                 this.eachLayer(map.addLayer, map);
6855         },
6856
6857         onRemove: function (map) {
6858                 this.eachLayer(map.removeLayer, map);
6859         },
6860
6861         // @method eachLayer(fn: Function, context?: Object): this
6862         // Iterates over the layers of the group, optionally specifying context of the iterator function.
6863         // ```js
6864         // group.eachLayer(function (layer) {
6865         //      layer.bindPopup('Hello');
6866         // });
6867         // ```
6868         eachLayer: function (method, context) {
6869                 for (var i in this._layers) {
6870                         method.call(context, this._layers[i]);
6871                 }
6872                 return this;
6873         },
6874
6875         // @method getLayer(id: Number): Layer
6876         // Returns the layer with the given internal ID.
6877         getLayer: function (id) {
6878                 return this._layers[id];
6879         },
6880
6881         // @method getLayers(): Layer[]
6882         // Returns an array of all the layers added to the group.
6883         getLayers: function () {
6884                 var layers = [];
6885                 this.eachLayer(layers.push, layers);
6886                 return layers;
6887         },
6888
6889         // @method setZIndex(zIndex: Number): this
6890         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6891         setZIndex: function (zIndex) {
6892                 return this.invoke('setZIndex', zIndex);
6893         },
6894
6895         // @method getLayerId(layer: Layer): Number
6896         // Returns the internal ID for a layer
6897         getLayerId: function (layer) {
6898                 return stamp(layer);
6899         }
6900 });
6901
6902
6903 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6904 // Create a layer group, optionally given an initial set of layers and an `options` object.
6905 var layerGroup = function (layers, options) {
6906         return new LayerGroup(layers, options);
6907 };
6908
6909 /*
6910  * @class FeatureGroup
6911  * @aka L.FeatureGroup
6912  * @inherits LayerGroup
6913  *
6914  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6915  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6916  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
6917  * handler, it will handle events from any of the layers. This includes mouse events
6918  * and custom events.
6919  *  * Has `layeradd` and `layerremove` events
6920  *
6921  * @example
6922  *
6923  * ```js
6924  * L.featureGroup([marker1, marker2, polyline])
6925  *      .bindPopup('Hello world!')
6926  *      .on('click', function() { alert('Clicked on a member of the group!'); })
6927  *      .addTo(map);
6928  * ```
6929  */
6930
6931 var FeatureGroup = LayerGroup.extend({
6932
6933         addLayer: function (layer) {
6934                 if (this.hasLayer(layer)) {
6935                         return this;
6936                 }
6937
6938                 layer.addEventParent(this);
6939
6940                 LayerGroup.prototype.addLayer.call(this, layer);
6941
6942                 // @event layeradd: LayerEvent
6943                 // Fired when a layer is added to this `FeatureGroup`
6944                 return this.fire('layeradd', {layer: layer});
6945         },
6946
6947         removeLayer: function (layer) {
6948                 if (!this.hasLayer(layer)) {
6949                         return this;
6950                 }
6951                 if (layer in this._layers) {
6952                         layer = this._layers[layer];
6953                 }
6954
6955                 layer.removeEventParent(this);
6956
6957                 LayerGroup.prototype.removeLayer.call(this, layer);
6958
6959                 // @event layerremove: LayerEvent
6960                 // Fired when a layer is removed from this `FeatureGroup`
6961                 return this.fire('layerremove', {layer: layer});
6962         },
6963
6964         // @method setStyle(style: Path options): this
6965         // Sets the given path options to each layer of the group that has a `setStyle` method.
6966         setStyle: function (style) {
6967                 return this.invoke('setStyle', style);
6968         },
6969
6970         // @method bringToFront(): this
6971         // Brings the layer group to the top of all other layers
6972         bringToFront: function () {
6973                 return this.invoke('bringToFront');
6974         },
6975
6976         // @method bringToBack(): this
6977         // Brings the layer group to the back of all other layers
6978         bringToBack: function () {
6979                 return this.invoke('bringToBack');
6980         },
6981
6982         // @method getBounds(): LatLngBounds
6983         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6984         getBounds: function () {
6985                 var bounds = new LatLngBounds();
6986
6987                 for (var id in this._layers) {
6988                         var layer = this._layers[id];
6989                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6990                 }
6991                 return bounds;
6992         }
6993 });
6994
6995 // @factory L.featureGroup(layers: Layer[])
6996 // Create a feature group, optionally given an initial set of layers.
6997 var featureGroup = function (layers) {
6998         return new FeatureGroup(layers);
6999 };
7000
7001 /*
7002  * @class Icon
7003  * @aka L.Icon
7004  *
7005  * Represents an icon to provide when creating a marker.
7006  *
7007  * @example
7008  *
7009  * ```js
7010  * var myIcon = L.icon({
7011  *     iconUrl: 'my-icon.png',
7012  *     iconRetinaUrl: 'my-icon@2x.png',
7013  *     iconSize: [38, 95],
7014  *     iconAnchor: [22, 94],
7015  *     popupAnchor: [-3, -76],
7016  *     shadowUrl: 'my-icon-shadow.png',
7017  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
7018  *     shadowSize: [68, 95],
7019  *     shadowAnchor: [22, 94]
7020  * });
7021  *
7022  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7023  * ```
7024  *
7025  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7026  *
7027  */
7028
7029 var Icon = Class.extend({
7030
7031         /* @section
7032          * @aka Icon options
7033          *
7034          * @option iconUrl: String = null
7035          * **(required)** The URL to the icon image (absolute or relative to your script path).
7036          *
7037          * @option iconRetinaUrl: String = null
7038          * The URL to a retina sized version of the icon image (absolute or relative to your
7039          * script path). Used for Retina screen devices.
7040          *
7041          * @option iconSize: Point = null
7042          * Size of the icon image in pixels.
7043          *
7044          * @option iconAnchor: Point = null
7045          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7046          * will be aligned so that this point is at the marker's geographical location. Centered
7047          * by default if size is specified, also can be set in CSS with negative margins.
7048          *
7049          * @option popupAnchor: Point = [0, 0]
7050          * The coordinates of the point from which popups will "open", relative to the icon anchor.
7051          *
7052          * @option tooltipAnchor: Point = [0, 0]
7053          * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7054          *
7055          * @option shadowUrl: String = null
7056          * The URL to the icon shadow image. If not specified, no shadow image will be created.
7057          *
7058          * @option shadowRetinaUrl: String = null
7059          *
7060          * @option shadowSize: Point = null
7061          * Size of the shadow image in pixels.
7062          *
7063          * @option shadowAnchor: Point = null
7064          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7065          * as iconAnchor if not specified).
7066          *
7067          * @option className: String = ''
7068          * A custom class name to assign to both icon and shadow images. Empty by default.
7069          */
7070
7071         options: {
7072                 popupAnchor: [0, 0],
7073                 tooltipAnchor: [0, 0]
7074         },
7075
7076         initialize: function (options) {
7077                 setOptions(this, options);
7078         },
7079
7080         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7081         // Called internally when the icon has to be shown, returns a `<img>` HTML element
7082         // styled according to the options.
7083         createIcon: function (oldIcon) {
7084                 return this._createIcon('icon', oldIcon);
7085         },
7086
7087         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7088         // As `createIcon`, but for the shadow beneath it.
7089         createShadow: function (oldIcon) {
7090                 return this._createIcon('shadow', oldIcon);
7091         },
7092
7093         _createIcon: function (name, oldIcon) {
7094                 var src = this._getIconUrl(name);
7095
7096                 if (!src) {
7097                         if (name === 'icon') {
7098                                 throw new Error('iconUrl not set in Icon options (see the docs).');
7099                         }
7100                         return null;
7101                 }
7102
7103                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7104                 this._setIconStyles(img, name);
7105
7106                 return img;
7107         },
7108
7109         _setIconStyles: function (img, name) {
7110                 var options = this.options;
7111                 var sizeOption = options[name + 'Size'];
7112
7113                 if (typeof sizeOption === 'number') {
7114                         sizeOption = [sizeOption, sizeOption];
7115                 }
7116
7117                 var size = toPoint(sizeOption),
7118                     anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7119                             size && size.divideBy(2, true));
7120
7121                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7122
7123                 if (anchor) {
7124                         img.style.marginLeft = (-anchor.x) + 'px';
7125                         img.style.marginTop  = (-anchor.y) + 'px';
7126                 }
7127
7128                 if (size) {
7129                         img.style.width  = size.x + 'px';
7130                         img.style.height = size.y + 'px';
7131                 }
7132         },
7133
7134         _createImg: function (src, el) {
7135                 el = el || document.createElement('img');
7136                 el.src = src;
7137                 return el;
7138         },
7139
7140         _getIconUrl: function (name) {
7141                 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7142         }
7143 });
7144
7145
7146 // @factory L.icon(options: Icon options)
7147 // Creates an icon instance with the given options.
7148 function icon(options) {
7149         return new Icon(options);
7150 }
7151
7152 /*
7153  * @miniclass Icon.Default (Icon)
7154  * @aka L.Icon.Default
7155  * @section
7156  *
7157  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7158  * no icon is specified. Points to the blue marker image distributed with Leaflet
7159  * releases.
7160  *
7161  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7162  * (which is a set of `Icon options`).
7163  *
7164  * If you want to _completely_ replace the default icon, override the
7165  * `L.Marker.prototype.options.icon` with your own icon instead.
7166  */
7167
7168 var IconDefault = Icon.extend({
7169
7170         options: {
7171                 iconUrl:       'marker-icon.png',
7172                 iconRetinaUrl: 'marker-icon-2x.png',
7173                 shadowUrl:     'marker-shadow.png',
7174                 iconSize:    [25, 41],
7175                 iconAnchor:  [12, 41],
7176                 popupAnchor: [1, -34],
7177                 tooltipAnchor: [16, -28],
7178                 shadowSize:  [41, 41]
7179         },
7180
7181         _getIconUrl: function (name) {
7182                 if (!IconDefault.imagePath) {   // Deprecated, backwards-compatibility only
7183                         IconDefault.imagePath = this._detectIconPath();
7184                 }
7185
7186                 // @option imagePath: String
7187                 // `Icon.Default` will try to auto-detect the location of the
7188                 // blue icon images. If you are placing these images in a non-standard
7189                 // way, set this option to point to the right path.
7190                 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7191         },
7192
7193         _detectIconPath: function () {
7194                 var el = create$1('div',  'leaflet-default-icon-path', document.body);
7195                 var path = getStyle(el, 'background-image') ||
7196                            getStyle(el, 'backgroundImage');     // IE8
7197
7198                 document.body.removeChild(el);
7199
7200                 if (path === null || path.indexOf('url') !== 0) {
7201                         path = '';
7202                 } else {
7203                         path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7204                 }
7205
7206                 return path;
7207         }
7208 });
7209
7210 /*
7211  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7212  */
7213
7214
7215 /* @namespace Marker
7216  * @section Interaction handlers
7217  *
7218  * 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:
7219  *
7220  * ```js
7221  * marker.dragging.disable();
7222  * ```
7223  *
7224  * @property dragging: Handler
7225  * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7226  */
7227
7228 var MarkerDrag = Handler.extend({
7229         initialize: function (marker) {
7230                 this._marker = marker;
7231         },
7232
7233         addHooks: function () {
7234                 var icon = this._marker._icon;
7235
7236                 if (!this._draggable) {
7237                         this._draggable = new Draggable(icon, icon, true);
7238                 }
7239
7240                 this._draggable.on({
7241                         dragstart: this._onDragStart,
7242                         predrag: this._onPreDrag,
7243                         drag: this._onDrag,
7244                         dragend: this._onDragEnd
7245                 }, this).enable();
7246
7247                 addClass(icon, 'leaflet-marker-draggable');
7248         },
7249
7250         removeHooks: function () {
7251                 this._draggable.off({
7252                         dragstart: this._onDragStart,
7253                         predrag: this._onPreDrag,
7254                         drag: this._onDrag,
7255                         dragend: this._onDragEnd
7256                 }, this).disable();
7257
7258                 if (this._marker._icon) {
7259                         removeClass(this._marker._icon, 'leaflet-marker-draggable');
7260                 }
7261         },
7262
7263         moved: function () {
7264                 return this._draggable && this._draggable._moved;
7265         },
7266
7267         _adjustPan: function (e) {
7268                 var marker = this._marker,
7269                     map = marker._map,
7270                     speed = this._marker.options.autoPanSpeed,
7271                     padding = this._marker.options.autoPanPadding,
7272                     iconPos = getPosition(marker._icon),
7273                     bounds = map.getPixelBounds(),
7274                     origin = map.getPixelOrigin();
7275
7276                 var panBounds = toBounds(
7277                         bounds.min._subtract(origin).add(padding),
7278                         bounds.max._subtract(origin).subtract(padding)
7279                 );
7280
7281                 if (!panBounds.contains(iconPos)) {
7282                         // Compute incremental movement
7283                         var movement = toPoint(
7284                                 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7285                                 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7286
7287                                 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7288                                 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7289                         ).multiplyBy(speed);
7290
7291                         map.panBy(movement, {animate: false});
7292
7293                         this._draggable._newPos._add(movement);
7294                         this._draggable._startPos._add(movement);
7295
7296                         setPosition(marker._icon, this._draggable._newPos);
7297                         this._onDrag(e);
7298
7299                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7300                 }
7301         },
7302
7303         _onDragStart: function () {
7304                 // @section Dragging events
7305                 // @event dragstart: Event
7306                 // Fired when the user starts dragging the marker.
7307
7308                 // @event movestart: Event
7309                 // Fired when the marker starts moving (because of dragging).
7310
7311                 this._oldLatLng = this._marker.getLatLng();
7312                 this._marker
7313                     .closePopup()
7314                     .fire('movestart')
7315                     .fire('dragstart');
7316         },
7317
7318         _onPreDrag: function (e) {
7319                 if (this._marker.options.autoPan) {
7320                         cancelAnimFrame(this._panRequest);
7321                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7322                 }
7323         },
7324
7325         _onDrag: function (e) {
7326                 var marker = this._marker,
7327                     shadow = marker._shadow,
7328                     iconPos = getPosition(marker._icon),
7329                     latlng = marker._map.layerPointToLatLng(iconPos);
7330
7331                 // update shadow position
7332                 if (shadow) {
7333                         setPosition(shadow, iconPos);
7334                 }
7335
7336                 marker._latlng = latlng;
7337                 e.latlng = latlng;
7338                 e.oldLatLng = this._oldLatLng;
7339
7340                 // @event drag: Event
7341                 // Fired repeatedly while the user drags the marker.
7342                 marker
7343                     .fire('move', e)
7344                     .fire('drag', e);
7345         },
7346
7347         _onDragEnd: function (e) {
7348                 // @event dragend: DragEndEvent
7349                 // Fired when the user stops dragging the marker.
7350
7351                  cancelAnimFrame(this._panRequest);
7352
7353                 // @event moveend: Event
7354                 // Fired when the marker stops moving (because of dragging).
7355                 delete this._oldLatLng;
7356                 this._marker
7357                     .fire('moveend')
7358                     .fire('dragend', e);
7359         }
7360 });
7361
7362 /*
7363  * @class Marker
7364  * @inherits Interactive layer
7365  * @aka L.Marker
7366  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7367  *
7368  * @example
7369  *
7370  * ```js
7371  * L.marker([50.5, 30.5]).addTo(map);
7372  * ```
7373  */
7374
7375 var Marker = Layer.extend({
7376
7377         // @section
7378         // @aka Marker options
7379         options: {
7380                 // @option icon: Icon = *
7381                 // Icon instance to use for rendering the marker.
7382                 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7383                 // If not specified, a common instance of `L.Icon.Default` is used.
7384                 icon: new IconDefault(),
7385
7386                 // Option inherited from "Interactive layer" abstract class
7387                 interactive: true,
7388
7389                 // @option keyboard: Boolean = true
7390                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7391                 keyboard: true,
7392
7393                 // @option title: String = ''
7394                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7395                 title: '',
7396
7397                 // @option alt: String = ''
7398                 // Text for the `alt` attribute of the icon image (useful for accessibility).
7399                 alt: '',
7400
7401                 // @option zIndexOffset: Number = 0
7402                 // 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).
7403                 zIndexOffset: 0,
7404
7405                 // @option opacity: Number = 1.0
7406                 // The opacity of the marker.
7407                 opacity: 1,
7408
7409                 // @option riseOnHover: Boolean = false
7410                 // If `true`, the marker will get on top of others when you hover the mouse over it.
7411                 riseOnHover: false,
7412
7413                 // @option riseOffset: Number = 250
7414                 // The z-index offset used for the `riseOnHover` feature.
7415                 riseOffset: 250,
7416
7417                 // @option pane: String = 'markerPane'
7418                 // `Map pane` where the markers icon will be added.
7419                 pane: 'markerPane',
7420
7421                 // @option bubblingMouseEvents: Boolean = false
7422                 // When `true`, a mouse event on this marker will trigger the same event on the map
7423                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7424                 bubblingMouseEvents: false,
7425
7426                 // @section Draggable marker options
7427                 // @option draggable: Boolean = false
7428                 // Whether the marker is draggable with mouse/touch or not.
7429                 draggable: false,
7430
7431                 // @option autoPan: Boolean = false
7432                 // Whether to pan the map when dragging this marker near its edge or not.
7433                 autoPan: false,
7434
7435                 // @option autoPanPadding: Point = Point(50, 50)
7436                 // Distance (in pixels to the left/right and to the top/bottom) of the
7437                 // map edge to start panning the map.
7438                 autoPanPadding: [50, 50],
7439
7440                 // @option autoPanSpeed: Number = 10
7441                 // Number of pixels the map should pan by.
7442                 autoPanSpeed: 10
7443         },
7444
7445         /* @section
7446          *
7447          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7448          */
7449
7450         initialize: function (latlng, options) {
7451                 setOptions(this, options);
7452                 this._latlng = toLatLng(latlng);
7453         },
7454
7455         onAdd: function (map) {
7456                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7457
7458                 if (this._zoomAnimated) {
7459                         map.on('zoomanim', this._animateZoom, this);
7460                 }
7461
7462                 this._initIcon();
7463                 this.update();
7464         },
7465
7466         onRemove: function (map) {
7467                 if (this.dragging && this.dragging.enabled()) {
7468                         this.options.draggable = true;
7469                         this.dragging.removeHooks();
7470                 }
7471                 delete this.dragging;
7472
7473                 if (this._zoomAnimated) {
7474                         map.off('zoomanim', this._animateZoom, this);
7475                 }
7476
7477                 this._removeIcon();
7478                 this._removeShadow();
7479         },
7480
7481         getEvents: function () {
7482                 return {
7483                         zoom: this.update,
7484                         viewreset: this.update
7485                 };
7486         },
7487
7488         // @method getLatLng: LatLng
7489         // Returns the current geographical position of the marker.
7490         getLatLng: function () {
7491                 return this._latlng;
7492         },
7493
7494         // @method setLatLng(latlng: LatLng): this
7495         // Changes the marker position to the given point.
7496         setLatLng: function (latlng) {
7497                 var oldLatLng = this._latlng;
7498                 this._latlng = toLatLng(latlng);
7499                 this.update();
7500
7501                 // @event move: Event
7502                 // 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`.
7503                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7504         },
7505
7506         // @method setZIndexOffset(offset: Number): this
7507         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7508         setZIndexOffset: function (offset) {
7509                 this.options.zIndexOffset = offset;
7510                 return this.update();
7511         },
7512
7513         // @method setIcon(icon: Icon): this
7514         // Changes the marker icon.
7515         setIcon: function (icon) {
7516
7517                 this.options.icon = icon;
7518
7519                 if (this._map) {
7520                         this._initIcon();
7521                         this.update();
7522                 }
7523
7524                 if (this._popup) {
7525                         this.bindPopup(this._popup, this._popup.options);
7526                 }
7527
7528                 return this;
7529         },
7530
7531         getElement: function () {
7532                 return this._icon;
7533         },
7534
7535         update: function () {
7536
7537                 if (this._icon && this._map) {
7538                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
7539                         this._setPos(pos);
7540                 }
7541
7542                 return this;
7543         },
7544
7545         _initIcon: function () {
7546                 var options = this.options,
7547                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7548
7549                 var icon = options.icon.createIcon(this._icon),
7550                     addIcon = false;
7551
7552                 // if we're not reusing the icon, remove the old one and init new one
7553                 if (icon !== this._icon) {
7554                         if (this._icon) {
7555                                 this._removeIcon();
7556                         }
7557                         addIcon = true;
7558
7559                         if (options.title) {
7560                                 icon.title = options.title;
7561                         }
7562
7563                         if (icon.tagName === 'IMG') {
7564                                 icon.alt = options.alt || '';
7565                         }
7566                 }
7567
7568                 addClass(icon, classToAdd);
7569
7570                 if (options.keyboard) {
7571                         icon.tabIndex = '0';
7572                 }
7573
7574                 this._icon = icon;
7575
7576                 if (options.riseOnHover) {
7577                         this.on({
7578                                 mouseover: this._bringToFront,
7579                                 mouseout: this._resetZIndex
7580                         });
7581                 }
7582
7583                 var newShadow = options.icon.createShadow(this._shadow),
7584                     addShadow = false;
7585
7586                 if (newShadow !== this._shadow) {
7587                         this._removeShadow();
7588                         addShadow = true;
7589                 }
7590
7591                 if (newShadow) {
7592                         addClass(newShadow, classToAdd);
7593                         newShadow.alt = '';
7594                 }
7595                 this._shadow = newShadow;
7596
7597
7598                 if (options.opacity < 1) {
7599                         this._updateOpacity();
7600                 }
7601
7602
7603                 if (addIcon) {
7604                         this.getPane().appendChild(this._icon);
7605                 }
7606                 this._initInteraction();
7607                 if (newShadow && addShadow) {
7608                         this.getPane('shadowPane').appendChild(this._shadow);
7609                 }
7610         },
7611
7612         _removeIcon: function () {
7613                 if (this.options.riseOnHover) {
7614                         this.off({
7615                                 mouseover: this._bringToFront,
7616                                 mouseout: this._resetZIndex
7617                         });
7618                 }
7619
7620                 remove(this._icon);
7621                 this.removeInteractiveTarget(this._icon);
7622
7623                 this._icon = null;
7624         },
7625
7626         _removeShadow: function () {
7627                 if (this._shadow) {
7628                         remove(this._shadow);
7629                 }
7630                 this._shadow = null;
7631         },
7632
7633         _setPos: function (pos) {
7634                 setPosition(this._icon, pos);
7635
7636                 if (this._shadow) {
7637                         setPosition(this._shadow, pos);
7638                 }
7639
7640                 this._zIndex = pos.y + this.options.zIndexOffset;
7641
7642                 this._resetZIndex();
7643         },
7644
7645         _updateZIndex: function (offset) {
7646                 this._icon.style.zIndex = this._zIndex + offset;
7647         },
7648
7649         _animateZoom: function (opt) {
7650                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7651
7652                 this._setPos(pos);
7653         },
7654
7655         _initInteraction: function () {
7656
7657                 if (!this.options.interactive) { return; }
7658
7659                 addClass(this._icon, 'leaflet-interactive');
7660
7661                 this.addInteractiveTarget(this._icon);
7662
7663                 if (MarkerDrag) {
7664                         var draggable = this.options.draggable;
7665                         if (this.dragging) {
7666                                 draggable = this.dragging.enabled();
7667                                 this.dragging.disable();
7668                         }
7669
7670                         this.dragging = new MarkerDrag(this);
7671
7672                         if (draggable) {
7673                                 this.dragging.enable();
7674                         }
7675                 }
7676         },
7677
7678         // @method setOpacity(opacity: Number): this
7679         // Changes the opacity of the marker.
7680         setOpacity: function (opacity) {
7681                 this.options.opacity = opacity;
7682                 if (this._map) {
7683                         this._updateOpacity();
7684                 }
7685
7686                 return this;
7687         },
7688
7689         _updateOpacity: function () {
7690                 var opacity = this.options.opacity;
7691
7692                 setOpacity(this._icon, opacity);
7693
7694                 if (this._shadow) {
7695                         setOpacity(this._shadow, opacity);
7696                 }
7697         },
7698
7699         _bringToFront: function () {
7700                 this._updateZIndex(this.options.riseOffset);
7701         },
7702
7703         _resetZIndex: function () {
7704                 this._updateZIndex(0);
7705         },
7706
7707         _getPopupAnchor: function () {
7708                 return this.options.icon.options.popupAnchor;
7709         },
7710
7711         _getTooltipAnchor: function () {
7712                 return this.options.icon.options.tooltipAnchor;
7713         }
7714 });
7715
7716
7717 // factory L.marker(latlng: LatLng, options? : Marker options)
7718
7719 // @factory L.marker(latlng: LatLng, options? : Marker options)
7720 // Instantiates a Marker object given a geographical point and optionally an options object.
7721 function marker(latlng, options) {
7722         return new Marker(latlng, options);
7723 }
7724
7725 /*
7726  * @class Path
7727  * @aka L.Path
7728  * @inherits Interactive layer
7729  *
7730  * An abstract class that contains options and constants shared between vector
7731  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7732  */
7733
7734 var Path = Layer.extend({
7735
7736         // @section
7737         // @aka Path options
7738         options: {
7739                 // @option stroke: Boolean = true
7740                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7741                 stroke: true,
7742
7743                 // @option color: String = '#3388ff'
7744                 // Stroke color
7745                 color: '#3388ff',
7746
7747                 // @option weight: Number = 3
7748                 // Stroke width in pixels
7749                 weight: 3,
7750
7751                 // @option opacity: Number = 1.0
7752                 // Stroke opacity
7753                 opacity: 1,
7754
7755                 // @option lineCap: String= 'round'
7756                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7757                 lineCap: 'round',
7758
7759                 // @option lineJoin: String = 'round'
7760                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7761                 lineJoin: 'round',
7762
7763                 // @option dashArray: String = null
7764                 // 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).
7765                 dashArray: null,
7766
7767                 // @option dashOffset: String = null
7768                 // 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).
7769                 dashOffset: null,
7770
7771                 // @option fill: Boolean = depends
7772                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7773                 fill: false,
7774
7775                 // @option fillColor: String = *
7776                 // Fill color. Defaults to the value of the [`color`](#path-color) option
7777                 fillColor: null,
7778
7779                 // @option fillOpacity: Number = 0.2
7780                 // Fill opacity.
7781                 fillOpacity: 0.2,
7782
7783                 // @option fillRule: String = 'evenodd'
7784                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7785                 fillRule: 'evenodd',
7786
7787                 // className: '',
7788
7789                 // Option inherited from "Interactive layer" abstract class
7790                 interactive: true,
7791
7792                 // @option bubblingMouseEvents: Boolean = true
7793                 // When `true`, a mouse event on this path will trigger the same event on the map
7794                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7795                 bubblingMouseEvents: true
7796         },
7797
7798         beforeAdd: function (map) {
7799                 // Renderer is set here because we need to call renderer.getEvents
7800                 // before this.getEvents.
7801                 this._renderer = map.getRenderer(this);
7802         },
7803
7804         onAdd: function () {
7805                 this._renderer._initPath(this);
7806                 this._reset();
7807                 this._renderer._addPath(this);
7808         },
7809
7810         onRemove: function () {
7811                 this._renderer._removePath(this);
7812         },
7813
7814         // @method redraw(): this
7815         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7816         redraw: function () {
7817                 if (this._map) {
7818                         this._renderer._updatePath(this);
7819                 }
7820                 return this;
7821         },
7822
7823         // @method setStyle(style: Path options): this
7824         // Changes the appearance of a Path based on the options in the `Path options` object.
7825         setStyle: function (style) {
7826                 setOptions(this, style);
7827                 if (this._renderer) {
7828                         this._renderer._updateStyle(this);
7829                 }
7830                 return this;
7831         },
7832
7833         // @method bringToFront(): this
7834         // Brings the layer to the top of all path layers.
7835         bringToFront: function () {
7836                 if (this._renderer) {
7837                         this._renderer._bringToFront(this);
7838                 }
7839                 return this;
7840         },
7841
7842         // @method bringToBack(): this
7843         // Brings the layer to the bottom of all path layers.
7844         bringToBack: function () {
7845                 if (this._renderer) {
7846                         this._renderer._bringToBack(this);
7847                 }
7848                 return this;
7849         },
7850
7851         getElement: function () {
7852                 return this._path;
7853         },
7854
7855         _reset: function () {
7856                 // defined in child classes
7857                 this._project();
7858                 this._update();
7859         },
7860
7861         _clickTolerance: function () {
7862                 // used when doing hit detection for Canvas layers
7863                 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7864         }
7865 });
7866
7867 /*
7868  * @class CircleMarker
7869  * @aka L.CircleMarker
7870  * @inherits Path
7871  *
7872  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7873  */
7874
7875 var CircleMarker = Path.extend({
7876
7877         // @section
7878         // @aka CircleMarker options
7879         options: {
7880                 fill: true,
7881
7882                 // @option radius: Number = 10
7883                 // Radius of the circle marker, in pixels
7884                 radius: 10
7885         },
7886
7887         initialize: function (latlng, options) {
7888                 setOptions(this, options);
7889                 this._latlng = toLatLng(latlng);
7890                 this._radius = this.options.radius;
7891         },
7892
7893         // @method setLatLng(latLng: LatLng): this
7894         // Sets the position of a circle marker to a new location.
7895         setLatLng: function (latlng) {
7896                 this._latlng = toLatLng(latlng);
7897                 this.redraw();
7898                 return this.fire('move', {latlng: this._latlng});
7899         },
7900
7901         // @method getLatLng(): LatLng
7902         // Returns the current geographical position of the circle marker
7903         getLatLng: function () {
7904                 return this._latlng;
7905         },
7906
7907         // @method setRadius(radius: Number): this
7908         // Sets the radius of a circle marker. Units are in pixels.
7909         setRadius: function (radius) {
7910                 this.options.radius = this._radius = radius;
7911                 return this.redraw();
7912         },
7913
7914         // @method getRadius(): Number
7915         // Returns the current radius of the circle
7916         getRadius: function () {
7917                 return this._radius;
7918         },
7919
7920         setStyle : function (options) {
7921                 var radius = options && options.radius || this._radius;
7922                 Path.prototype.setStyle.call(this, options);
7923                 this.setRadius(radius);
7924                 return this;
7925         },
7926
7927         _project: function () {
7928                 this._point = this._map.latLngToLayerPoint(this._latlng);
7929                 this._updateBounds();
7930         },
7931
7932         _updateBounds: function () {
7933                 var r = this._radius,
7934                     r2 = this._radiusY || r,
7935                     w = this._clickTolerance(),
7936                     p = [r + w, r2 + w];
7937                 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7938         },
7939
7940         _update: function () {
7941                 if (this._map) {
7942                         this._updatePath();
7943                 }
7944         },
7945
7946         _updatePath: function () {
7947                 this._renderer._updateCircle(this);
7948         },
7949
7950         _empty: function () {
7951                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7952         },
7953
7954         // Needed by the `Canvas` renderer for interactivity
7955         _containsPoint: function (p) {
7956                 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7957         }
7958 });
7959
7960
7961 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7962 // Instantiates a circle marker object given a geographical point, and an optional options object.
7963 function circleMarker(latlng, options) {
7964         return new CircleMarker(latlng, options);
7965 }
7966
7967 /*
7968  * @class Circle
7969  * @aka L.Circle
7970  * @inherits CircleMarker
7971  *
7972  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7973  *
7974  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7975  *
7976  * @example
7977  *
7978  * ```js
7979  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7980  * ```
7981  */
7982
7983 var Circle = CircleMarker.extend({
7984
7985         initialize: function (latlng, options, legacyOptions) {
7986                 if (typeof options === 'number') {
7987                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7988                         options = extend({}, legacyOptions, {radius: options});
7989                 }
7990                 setOptions(this, options);
7991                 this._latlng = toLatLng(latlng);
7992
7993                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7994
7995                 // @section
7996                 // @aka Circle options
7997                 // @option radius: Number; Radius of the circle, in meters.
7998                 this._mRadius = this.options.radius;
7999         },
8000
8001         // @method setRadius(radius: Number): this
8002         // Sets the radius of a circle. Units are in meters.
8003         setRadius: function (radius) {
8004                 this._mRadius = radius;
8005                 return this.redraw();
8006         },
8007
8008         // @method getRadius(): Number
8009         // Returns the current radius of a circle. Units are in meters.
8010         getRadius: function () {
8011                 return this._mRadius;
8012         },
8013
8014         // @method getBounds(): LatLngBounds
8015         // Returns the `LatLngBounds` of the path.
8016         getBounds: function () {
8017                 var half = [this._radius, this._radiusY || this._radius];
8018
8019                 return new LatLngBounds(
8020                         this._map.layerPointToLatLng(this._point.subtract(half)),
8021                         this._map.layerPointToLatLng(this._point.add(half)));
8022         },
8023
8024         setStyle: Path.prototype.setStyle,
8025
8026         _project: function () {
8027
8028                 var lng = this._latlng.lng,
8029                     lat = this._latlng.lat,
8030                     map = this._map,
8031                     crs = map.options.crs;
8032
8033                 if (crs.distance === Earth.distance) {
8034                         var d = Math.PI / 180,
8035                             latR = (this._mRadius / Earth.R) / d,
8036                             top = map.project([lat + latR, lng]),
8037                             bottom = map.project([lat - latR, lng]),
8038                             p = top.add(bottom).divideBy(2),
8039                             lat2 = map.unproject(p).lat,
8040                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8041                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8042
8043                         if (isNaN(lngR) || lngR === 0) {
8044                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8045                         }
8046
8047                         this._point = p.subtract(map.getPixelOrigin());
8048                         this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8049                         this._radiusY = p.y - top.y;
8050
8051                 } else {
8052                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8053
8054                         this._point = map.latLngToLayerPoint(this._latlng);
8055                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8056                 }
8057
8058                 this._updateBounds();
8059         }
8060 });
8061
8062 // @factory L.circle(latlng: LatLng, options?: Circle options)
8063 // Instantiates a circle object given a geographical point, and an options object
8064 // which contains the circle radius.
8065 // @alternative
8066 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8067 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8068 // Do not use in new applications or plugins.
8069 function circle(latlng, options, legacyOptions) {
8070         return new Circle(latlng, options, legacyOptions);
8071 }
8072
8073 /*
8074  * @class Polyline
8075  * @aka L.Polyline
8076  * @inherits Path
8077  *
8078  * A class for drawing polyline overlays on a map. Extends `Path`.
8079  *
8080  * @example
8081  *
8082  * ```js
8083  * // create a red polyline from an array of LatLng points
8084  * var latlngs = [
8085  *      [45.51, -122.68],
8086  *      [37.77, -122.43],
8087  *      [34.04, -118.2]
8088  * ];
8089  *
8090  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8091  *
8092  * // zoom the map to the polyline
8093  * map.fitBounds(polyline.getBounds());
8094  * ```
8095  *
8096  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8097  *
8098  * ```js
8099  * // create a red polyline from an array of arrays of LatLng points
8100  * var latlngs = [
8101  *      [[45.51, -122.68],
8102  *       [37.77, -122.43],
8103  *       [34.04, -118.2]],
8104  *      [[40.78, -73.91],
8105  *       [41.83, -87.62],
8106  *       [32.76, -96.72]]
8107  * ];
8108  * ```
8109  */
8110
8111
8112 var Polyline = Path.extend({
8113
8114         // @section
8115         // @aka Polyline options
8116         options: {
8117                 // @option smoothFactor: Number = 1.0
8118                 // How much to simplify the polyline on each zoom level. More means
8119                 // better performance and smoother look, and less means more accurate representation.
8120                 smoothFactor: 1.0,
8121
8122                 // @option noClip: Boolean = false
8123                 // Disable polyline clipping.
8124                 noClip: false
8125         },
8126
8127         initialize: function (latlngs, options) {
8128                 setOptions(this, options);
8129                 this._setLatLngs(latlngs);
8130         },
8131
8132         // @method getLatLngs(): LatLng[]
8133         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8134         getLatLngs: function () {
8135                 return this._latlngs;
8136         },
8137
8138         // @method setLatLngs(latlngs: LatLng[]): this
8139         // Replaces all the points in the polyline with the given array of geographical points.
8140         setLatLngs: function (latlngs) {
8141                 this._setLatLngs(latlngs);
8142                 return this.redraw();
8143         },
8144
8145         // @method isEmpty(): Boolean
8146         // Returns `true` if the Polyline has no LatLngs.
8147         isEmpty: function () {
8148                 return !this._latlngs.length;
8149         },
8150
8151         // @method closestLayerPoint(p: Point): Point
8152         // Returns the point closest to `p` on the Polyline.
8153         closestLayerPoint: function (p) {
8154                 var minDistance = Infinity,
8155                     minPoint = null,
8156                     closest = _sqClosestPointOnSegment,
8157                     p1, p2;
8158
8159                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8160                         var points = this._parts[j];
8161
8162                         for (var i = 1, len = points.length; i < len; i++) {
8163                                 p1 = points[i - 1];
8164                                 p2 = points[i];
8165
8166                                 var sqDist = closest(p, p1, p2, true);
8167
8168                                 if (sqDist < minDistance) {
8169                                         minDistance = sqDist;
8170                                         minPoint = closest(p, p1, p2);
8171                                 }
8172                         }
8173                 }
8174                 if (minPoint) {
8175                         minPoint.distance = Math.sqrt(minDistance);
8176                 }
8177                 return minPoint;
8178         },
8179
8180         // @method getCenter(): LatLng
8181         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8182         getCenter: function () {
8183                 // throws error when not yet added to map as this center calculation requires projected coordinates
8184                 if (!this._map) {
8185                         throw new Error('Must add layer to map before using getCenter()');
8186                 }
8187
8188                 var i, halfDist, segDist, dist, p1, p2, ratio,
8189                     points = this._rings[0],
8190                     len = points.length;
8191
8192                 if (!len) { return null; }
8193
8194                 // polyline centroid algorithm; only uses the first ring if there are multiple
8195
8196                 for (i = 0, halfDist = 0; i < len - 1; i++) {
8197                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
8198                 }
8199
8200                 // The line is so small in the current view that all points are on the same pixel.
8201                 if (halfDist === 0) {
8202                         return this._map.layerPointToLatLng(points[0]);
8203                 }
8204
8205                 for (i = 0, dist = 0; i < len - 1; i++) {
8206                         p1 = points[i];
8207                         p2 = points[i + 1];
8208                         segDist = p1.distanceTo(p2);
8209                         dist += segDist;
8210
8211                         if (dist > halfDist) {
8212                                 ratio = (dist - halfDist) / segDist;
8213                                 return this._map.layerPointToLatLng([
8214                                         p2.x - ratio * (p2.x - p1.x),
8215                                         p2.y - ratio * (p2.y - p1.y)
8216                                 ]);
8217                         }
8218                 }
8219         },
8220
8221         // @method getBounds(): LatLngBounds
8222         // Returns the `LatLngBounds` of the path.
8223         getBounds: function () {
8224                 return this._bounds;
8225         },
8226
8227         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8228         // Adds a given point to the polyline. By default, adds to the first ring of
8229         // the polyline in case of a multi-polyline, but can be overridden by passing
8230         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8231         addLatLng: function (latlng, latlngs) {
8232                 latlngs = latlngs || this._defaultShape();
8233                 latlng = toLatLng(latlng);
8234                 latlngs.push(latlng);
8235                 this._bounds.extend(latlng);
8236                 return this.redraw();
8237         },
8238
8239         _setLatLngs: function (latlngs) {
8240                 this._bounds = new LatLngBounds();
8241                 this._latlngs = this._convertLatLngs(latlngs);
8242         },
8243
8244         _defaultShape: function () {
8245                 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8246         },
8247
8248         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8249         _convertLatLngs: function (latlngs) {
8250                 var result = [],
8251                     flat = isFlat(latlngs);
8252
8253                 for (var i = 0, len = latlngs.length; i < len; i++) {
8254                         if (flat) {
8255                                 result[i] = toLatLng(latlngs[i]);
8256                                 this._bounds.extend(result[i]);
8257                         } else {
8258                                 result[i] = this._convertLatLngs(latlngs[i]);
8259                         }
8260                 }
8261
8262                 return result;
8263         },
8264
8265         _project: function () {
8266                 var pxBounds = new Bounds();
8267                 this._rings = [];
8268                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8269
8270                 var w = this._clickTolerance(),
8271                     p = new Point(w, w);
8272
8273                 if (this._bounds.isValid() && pxBounds.isValid()) {
8274                         pxBounds.min._subtract(p);
8275                         pxBounds.max._add(p);
8276                         this._pxBounds = pxBounds;
8277                 }
8278         },
8279
8280         // recursively turns latlngs into a set of rings with projected coordinates
8281         _projectLatlngs: function (latlngs, result, projectedBounds) {
8282                 var flat = latlngs[0] instanceof LatLng,
8283                     len = latlngs.length,
8284                     i, ring;
8285
8286                 if (flat) {
8287                         ring = [];
8288                         for (i = 0; i < len; i++) {
8289                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8290                                 projectedBounds.extend(ring[i]);
8291                         }
8292                         result.push(ring);
8293                 } else {
8294                         for (i = 0; i < len; i++) {
8295                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8296                         }
8297                 }
8298         },
8299
8300         // clip polyline by renderer bounds so that we have less to render for performance
8301         _clipPoints: function () {
8302                 var bounds = this._renderer._bounds;
8303
8304                 this._parts = [];
8305                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8306                         return;
8307                 }
8308
8309                 if (this.options.noClip) {
8310                         this._parts = this._rings;
8311                         return;
8312                 }
8313
8314                 var parts = this._parts,
8315                     i, j, k, len, len2, segment, points;
8316
8317                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8318                         points = this._rings[i];
8319
8320                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8321                                 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8322
8323                                 if (!segment) { continue; }
8324
8325                                 parts[k] = parts[k] || [];
8326                                 parts[k].push(segment[0]);
8327
8328                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8329                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8330                                         parts[k].push(segment[1]);
8331                                         k++;
8332                                 }
8333                         }
8334                 }
8335         },
8336
8337         // simplify each clipped part of the polyline for performance
8338         _simplifyPoints: function () {
8339                 var parts = this._parts,
8340                     tolerance = this.options.smoothFactor;
8341
8342                 for (var i = 0, len = parts.length; i < len; i++) {
8343                         parts[i] = simplify(parts[i], tolerance);
8344                 }
8345         },
8346
8347         _update: function () {
8348                 if (!this._map) { return; }
8349
8350                 this._clipPoints();
8351                 this._simplifyPoints();
8352                 this._updatePath();
8353         },
8354
8355         _updatePath: function () {
8356                 this._renderer._updatePoly(this);
8357         },
8358
8359         // Needed by the `Canvas` renderer for interactivity
8360         _containsPoint: function (p, closed) {
8361                 var i, j, k, len, len2, part,
8362                     w = this._clickTolerance();
8363
8364                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8365
8366                 // hit detection for polylines
8367                 for (i = 0, len = this._parts.length; i < len; i++) {
8368                         part = this._parts[i];
8369
8370                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8371                                 if (!closed && (j === 0)) { continue; }
8372
8373                                 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8374                                         return true;
8375                                 }
8376                         }
8377                 }
8378                 return false;
8379         }
8380 });
8381
8382 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8383 // Instantiates a polyline object given an array of geographical points and
8384 // optionally an options object. You can create a `Polyline` object with
8385 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8386 // of geographic points.
8387 function polyline(latlngs, options) {
8388         return new Polyline(latlngs, options);
8389 }
8390
8391 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8392 Polyline._flat = _flat;
8393
8394 /*
8395  * @class Polygon
8396  * @aka L.Polygon
8397  * @inherits Polyline
8398  *
8399  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8400  *
8401  * 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.
8402  *
8403  *
8404  * @example
8405  *
8406  * ```js
8407  * // create a red polygon from an array of LatLng points
8408  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8409  *
8410  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8411  *
8412  * // zoom the map to the polygon
8413  * map.fitBounds(polygon.getBounds());
8414  * ```
8415  *
8416  * 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:
8417  *
8418  * ```js
8419  * var latlngs = [
8420  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8421  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8422  * ];
8423  * ```
8424  *
8425  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8426  *
8427  * ```js
8428  * var latlngs = [
8429  *   [ // first polygon
8430  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8431  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8432  *   ],
8433  *   [ // second polygon
8434  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8435  *   ]
8436  * ];
8437  * ```
8438  */
8439
8440 var Polygon = Polyline.extend({
8441
8442         options: {
8443                 fill: true
8444         },
8445
8446         isEmpty: function () {
8447                 return !this._latlngs.length || !this._latlngs[0].length;
8448         },
8449
8450         getCenter: function () {
8451                 // throws error when not yet added to map as this center calculation requires projected coordinates
8452                 if (!this._map) {
8453                         throw new Error('Must add layer to map before using getCenter()');
8454                 }
8455
8456                 var i, j, p1, p2, f, area, x, y, center,
8457                     points = this._rings[0],
8458                     len = points.length;
8459
8460                 if (!len) { return null; }
8461
8462                 // polygon centroid algorithm; only uses the first ring if there are multiple
8463
8464                 area = x = y = 0;
8465
8466                 for (i = 0, j = len - 1; i < len; j = i++) {
8467                         p1 = points[i];
8468                         p2 = points[j];
8469
8470                         f = p1.y * p2.x - p2.y * p1.x;
8471                         x += (p1.x + p2.x) * f;
8472                         y += (p1.y + p2.y) * f;
8473                         area += f * 3;
8474                 }
8475
8476                 if (area === 0) {
8477                         // Polygon is so small that all points are on same pixel.
8478                         center = points[0];
8479                 } else {
8480                         center = [x / area, y / area];
8481                 }
8482                 return this._map.layerPointToLatLng(center);
8483         },
8484
8485         _convertLatLngs: function (latlngs) {
8486                 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8487                     len = result.length;
8488
8489                 // remove last point if it equals first one
8490                 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8491                         result.pop();
8492                 }
8493                 return result;
8494         },
8495
8496         _setLatLngs: function (latlngs) {
8497                 Polyline.prototype._setLatLngs.call(this, latlngs);
8498                 if (isFlat(this._latlngs)) {
8499                         this._latlngs = [this._latlngs];
8500                 }
8501         },
8502
8503         _defaultShape: function () {
8504                 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8505         },
8506
8507         _clipPoints: function () {
8508                 // polygons need a different clipping algorithm so we redefine that
8509
8510                 var bounds = this._renderer._bounds,
8511                     w = this.options.weight,
8512                     p = new Point(w, w);
8513
8514                 // increase clip padding by stroke width to avoid stroke on clip edges
8515                 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8516
8517                 this._parts = [];
8518                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8519                         return;
8520                 }
8521
8522                 if (this.options.noClip) {
8523                         this._parts = this._rings;
8524                         return;
8525                 }
8526
8527                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8528                         clipped = clipPolygon(this._rings[i], bounds, true);
8529                         if (clipped.length) {
8530                                 this._parts.push(clipped);
8531                         }
8532                 }
8533         },
8534
8535         _updatePath: function () {
8536                 this._renderer._updatePoly(this, true);
8537         },
8538
8539         // Needed by the `Canvas` renderer for interactivity
8540         _containsPoint: function (p) {
8541                 var inside = false,
8542                     part, p1, p2, i, j, k, len, len2;
8543
8544                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8545
8546                 // ray casting algorithm for detecting if point is in polygon
8547                 for (i = 0, len = this._parts.length; i < len; i++) {
8548                         part = this._parts[i];
8549
8550                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8551                                 p1 = part[j];
8552                                 p2 = part[k];
8553
8554                                 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)) {
8555                                         inside = !inside;
8556                                 }
8557                         }
8558                 }
8559
8560                 // also check if it's on polygon stroke
8561                 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8562         }
8563
8564 });
8565
8566
8567 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8568 function polygon(latlngs, options) {
8569         return new Polygon(latlngs, options);
8570 }
8571
8572 /*
8573  * @class GeoJSON
8574  * @aka L.GeoJSON
8575  * @inherits FeatureGroup
8576  *
8577  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8578  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8579  *
8580  * @example
8581  *
8582  * ```js
8583  * L.geoJSON(data, {
8584  *      style: function (feature) {
8585  *              return {color: feature.properties.color};
8586  *      }
8587  * }).bindPopup(function (layer) {
8588  *      return layer.feature.properties.description;
8589  * }).addTo(map);
8590  * ```
8591  */
8592
8593 var GeoJSON = FeatureGroup.extend({
8594
8595         /* @section
8596          * @aka GeoJSON options
8597          *
8598          * @option pointToLayer: Function = *
8599          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8600          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8601          * The default is to spawn a default `Marker`:
8602          * ```js
8603          * function(geoJsonPoint, latlng) {
8604          *      return L.marker(latlng);
8605          * }
8606          * ```
8607          *
8608          * @option style: Function = *
8609          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8610          * called internally when data is added.
8611          * The default value is to not override any defaults:
8612          * ```js
8613          * function (geoJsonFeature) {
8614          *      return {}
8615          * }
8616          * ```
8617          *
8618          * @option onEachFeature: Function = *
8619          * A `Function` that will be called once for each created `Feature`, after it has
8620          * been created and styled. Useful for attaching events and popups to features.
8621          * The default is to do nothing with the newly created layers:
8622          * ```js
8623          * function (feature, layer) {}
8624          * ```
8625          *
8626          * @option filter: Function = *
8627          * A `Function` that will be used to decide whether to include a feature or not.
8628          * The default is to include all features:
8629          * ```js
8630          * function (geoJsonFeature) {
8631          *      return true;
8632          * }
8633          * ```
8634          * Note: dynamically changing the `filter` option will have effect only on newly
8635          * added data. It will _not_ re-evaluate already included features.
8636          *
8637          * @option coordsToLatLng: Function = *
8638          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8639          * The default is the `coordsToLatLng` static method.
8640          */
8641
8642         initialize: function (geojson, options) {
8643                 setOptions(this, options);
8644
8645                 this._layers = {};
8646
8647                 if (geojson) {
8648                         this.addData(geojson);
8649                 }
8650         },
8651
8652         // @method addData( <GeoJSON> data ): this
8653         // Adds a GeoJSON object to the layer.
8654         addData: function (geojson) {
8655                 var features = isArray(geojson) ? geojson : geojson.features,
8656                     i, len, feature;
8657
8658                 if (features) {
8659                         for (i = 0, len = features.length; i < len; i++) {
8660                                 // only add this if geometry or geometries are set and not null
8661                                 feature = features[i];
8662                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8663                                         this.addData(feature);
8664                                 }
8665                         }
8666                         return this;
8667                 }
8668
8669                 var options = this.options;
8670
8671                 if (options.filter && !options.filter(geojson)) { return this; }
8672
8673                 var layer = geometryToLayer(geojson, options);
8674                 if (!layer) {
8675                         return this;
8676                 }
8677                 layer.feature = asFeature(geojson);
8678
8679                 layer.defaultOptions = layer.options;
8680                 this.resetStyle(layer);
8681
8682                 if (options.onEachFeature) {
8683                         options.onEachFeature(geojson, layer);
8684                 }
8685
8686                 return this.addLayer(layer);
8687         },
8688
8689         // @method resetStyle( <Path> layer ): this
8690         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8691         resetStyle: function (layer) {
8692                 // reset any custom styles
8693                 layer.options = extend({}, layer.defaultOptions);
8694                 this._setLayerStyle(layer, this.options.style);
8695                 return this;
8696         },
8697
8698         // @method setStyle( <Function> style ): this
8699         // Changes styles of GeoJSON vector layers with the given style function.
8700         setStyle: function (style) {
8701                 return this.eachLayer(function (layer) {
8702                         this._setLayerStyle(layer, style);
8703                 }, this);
8704         },
8705
8706         _setLayerStyle: function (layer, style) {
8707                 if (typeof style === 'function') {
8708                         style = style(layer.feature);
8709                 }
8710                 if (layer.setStyle) {
8711                         layer.setStyle(style);
8712                 }
8713         }
8714 });
8715
8716 // @section
8717 // There are several static functions which can be called without instantiating L.GeoJSON:
8718
8719 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8720 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8721 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8722 // functions if provided as options.
8723 function geometryToLayer(geojson, options) {
8724
8725         var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8726             coords = geometry ? geometry.coordinates : null,
8727             layers = [],
8728             pointToLayer = options && options.pointToLayer,
8729             _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8730             latlng, latlngs, i, len;
8731
8732         if (!coords && !geometry) {
8733                 return null;
8734         }
8735
8736         switch (geometry.type) {
8737         case 'Point':
8738                 latlng = _coordsToLatLng(coords);
8739                 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8740
8741         case 'MultiPoint':
8742                 for (i = 0, len = coords.length; i < len; i++) {
8743                         latlng = _coordsToLatLng(coords[i]);
8744                         layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8745                 }
8746                 return new FeatureGroup(layers);
8747
8748         case 'LineString':
8749         case 'MultiLineString':
8750                 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8751                 return new Polyline(latlngs, options);
8752
8753         case 'Polygon':
8754         case 'MultiPolygon':
8755                 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8756                 return new Polygon(latlngs, options);
8757
8758         case 'GeometryCollection':
8759                 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8760                         var layer = geometryToLayer({
8761                                 geometry: geometry.geometries[i],
8762                                 type: 'Feature',
8763                                 properties: geojson.properties
8764                         }, options);
8765
8766                         if (layer) {
8767                                 layers.push(layer);
8768                         }
8769                 }
8770                 return new FeatureGroup(layers);
8771
8772         default:
8773                 throw new Error('Invalid GeoJSON object.');
8774         }
8775 }
8776
8777 // @function coordsToLatLng(coords: Array): LatLng
8778 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8779 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8780 function coordsToLatLng(coords) {
8781         return new LatLng(coords[1], coords[0], coords[2]);
8782 }
8783
8784 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8785 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8786 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8787 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8788 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8789         var latlngs = [];
8790
8791         for (var i = 0, len = coords.length, latlng; i < len; i++) {
8792                 latlng = levelsDeep ?
8793                         coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8794                         (_coordsToLatLng || coordsToLatLng)(coords[i]);
8795
8796                 latlngs.push(latlng);
8797         }
8798
8799         return latlngs;
8800 }
8801
8802 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8803 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8804 function latLngToCoords(latlng, precision) {
8805         precision = typeof precision === 'number' ? precision : 6;
8806         return latlng.alt !== undefined ?
8807                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8808                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8809 }
8810
8811 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8812 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8813 // `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.
8814 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8815         var coords = [];
8816
8817         for (var i = 0, len = latlngs.length; i < len; i++) {
8818                 coords.push(levelsDeep ?
8819                         latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8820                         latLngToCoords(latlngs[i], precision));
8821         }
8822
8823         if (!levelsDeep && closed) {
8824                 coords.push(coords[0]);
8825         }
8826
8827         return coords;
8828 }
8829
8830 function getFeature(layer, newGeometry) {
8831         return layer.feature ?
8832                 extend({}, layer.feature, {geometry: newGeometry}) :
8833                 asFeature(newGeometry);
8834 }
8835
8836 // @function asFeature(geojson: Object): Object
8837 // Normalize GeoJSON geometries/features into GeoJSON features.
8838 function asFeature(geojson) {
8839         if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8840                 return geojson;
8841         }
8842
8843         return {
8844                 type: 'Feature',
8845                 properties: {},
8846                 geometry: geojson
8847         };
8848 }
8849
8850 var PointToGeoJSON = {
8851         toGeoJSON: function (precision) {
8852                 return getFeature(this, {
8853                         type: 'Point',
8854                         coordinates: latLngToCoords(this.getLatLng(), precision)
8855                 });
8856         }
8857 };
8858
8859 // @namespace Marker
8860 // @method toGeoJSON(): Object
8861 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8862 Marker.include(PointToGeoJSON);
8863
8864 // @namespace CircleMarker
8865 // @method toGeoJSON(): Object
8866 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8867 Circle.include(PointToGeoJSON);
8868 CircleMarker.include(PointToGeoJSON);
8869
8870
8871 // @namespace Polyline
8872 // @method toGeoJSON(): Object
8873 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8874 Polyline.include({
8875         toGeoJSON: function (precision) {
8876                 var multi = !isFlat(this._latlngs);
8877
8878                 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8879
8880                 return getFeature(this, {
8881                         type: (multi ? 'Multi' : '') + 'LineString',
8882                         coordinates: coords
8883                 });
8884         }
8885 });
8886
8887 // @namespace Polygon
8888 // @method toGeoJSON(): Object
8889 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8890 Polygon.include({
8891         toGeoJSON: function (precision) {
8892                 var holes = !isFlat(this._latlngs),
8893                     multi = holes && !isFlat(this._latlngs[0]);
8894
8895                 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8896
8897                 if (!holes) {
8898                         coords = [coords];
8899                 }
8900
8901                 return getFeature(this, {
8902                         type: (multi ? 'Multi' : '') + 'Polygon',
8903                         coordinates: coords
8904                 });
8905         }
8906 });
8907
8908
8909 // @namespace LayerGroup
8910 LayerGroup.include({
8911         toMultiPoint: function (precision) {
8912                 var coords = [];
8913
8914                 this.eachLayer(function (layer) {
8915                         coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8916                 });
8917
8918                 return getFeature(this, {
8919                         type: 'MultiPoint',
8920                         coordinates: coords
8921                 });
8922         },
8923
8924         // @method toGeoJSON(): Object
8925         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8926         toGeoJSON: function (precision) {
8927
8928                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8929
8930                 if (type === 'MultiPoint') {
8931                         return this.toMultiPoint(precision);
8932                 }
8933
8934                 var isGeometryCollection = type === 'GeometryCollection',
8935                     jsons = [];
8936
8937                 this.eachLayer(function (layer) {
8938                         if (layer.toGeoJSON) {
8939                                 var json = layer.toGeoJSON(precision);
8940                                 if (isGeometryCollection) {
8941                                         jsons.push(json.geometry);
8942                                 } else {
8943                                         var feature = asFeature(json);
8944                                         // Squash nested feature collections
8945                                         if (feature.type === 'FeatureCollection') {
8946                                                 jsons.push.apply(jsons, feature.features);
8947                                         } else {
8948                                                 jsons.push(feature);
8949                                         }
8950                                 }
8951                         }
8952                 });
8953
8954                 if (isGeometryCollection) {
8955                         return getFeature(this, {
8956                                 geometries: jsons,
8957                                 type: 'GeometryCollection'
8958                         });
8959                 }
8960
8961                 return {
8962                         type: 'FeatureCollection',
8963                         features: jsons
8964                 };
8965         }
8966 });
8967
8968 // @namespace GeoJSON
8969 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8970 // Creates a GeoJSON layer. Optionally accepts an object in
8971 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
8972 // (you can alternatively add it later with `addData` method) and an `options` object.
8973 function geoJSON(geojson, options) {
8974         return new GeoJSON(geojson, options);
8975 }
8976
8977 // Backward compatibility.
8978 var geoJson = geoJSON;
8979
8980 /*
8981  * @class ImageOverlay
8982  * @aka L.ImageOverlay
8983  * @inherits Interactive layer
8984  *
8985  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8986  *
8987  * @example
8988  *
8989  * ```js
8990  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8991  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8992  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8993  * ```
8994  */
8995
8996 var ImageOverlay = Layer.extend({
8997
8998         // @section
8999         // @aka ImageOverlay options
9000         options: {
9001                 // @option opacity: Number = 1.0
9002                 // The opacity of the image overlay.
9003                 opacity: 1,
9004
9005                 // @option alt: String = ''
9006                 // Text for the `alt` attribute of the image (useful for accessibility).
9007                 alt: '',
9008
9009                 // @option interactive: Boolean = false
9010                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9011                 interactive: false,
9012
9013                 // @option crossOrigin: Boolean|String = false
9014                 // Whether the crossOrigin attribute will be added to the image.
9015                 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
9016                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9017                 crossOrigin: false,
9018
9019                 // @option errorOverlayUrl: String = ''
9020                 // URL to the overlay image to show in place of the overlay that failed to load.
9021                 errorOverlayUrl: '',
9022
9023                 // @option zIndex: Number = 1
9024                 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9025                 zIndex: 1,
9026
9027                 // @option className: String = ''
9028                 // A custom class name to assign to the image. Empty by default.
9029                 className: ''
9030         },
9031
9032         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9033                 this._url = url;
9034                 this._bounds = toLatLngBounds(bounds);
9035
9036                 setOptions(this, options);
9037         },
9038
9039         onAdd: function () {
9040                 if (!this._image) {
9041                         this._initImage();
9042
9043                         if (this.options.opacity < 1) {
9044                                 this._updateOpacity();
9045                         }
9046                 }
9047
9048                 if (this.options.interactive) {
9049                         addClass(this._image, 'leaflet-interactive');
9050                         this.addInteractiveTarget(this._image);
9051                 }
9052
9053                 this.getPane().appendChild(this._image);
9054                 this._reset();
9055         },
9056
9057         onRemove: function () {
9058                 remove(this._image);
9059                 if (this.options.interactive) {
9060                         this.removeInteractiveTarget(this._image);
9061                 }
9062         },
9063
9064         // @method setOpacity(opacity: Number): this
9065         // Sets the opacity of the overlay.
9066         setOpacity: function (opacity) {
9067                 this.options.opacity = opacity;
9068
9069                 if (this._image) {
9070                         this._updateOpacity();
9071                 }
9072                 return this;
9073         },
9074
9075         setStyle: function (styleOpts) {
9076                 if (styleOpts.opacity) {
9077                         this.setOpacity(styleOpts.opacity);
9078                 }
9079                 return this;
9080         },
9081
9082         // @method bringToFront(): this
9083         // Brings the layer to the top of all overlays.
9084         bringToFront: function () {
9085                 if (this._map) {
9086                         toFront(this._image);
9087                 }
9088                 return this;
9089         },
9090
9091         // @method bringToBack(): this
9092         // Brings the layer to the bottom of all overlays.
9093         bringToBack: function () {
9094                 if (this._map) {
9095                         toBack(this._image);
9096                 }
9097                 return this;
9098         },
9099
9100         // @method setUrl(url: String): this
9101         // Changes the URL of the image.
9102         setUrl: function (url) {
9103                 this._url = url;
9104
9105                 if (this._image) {
9106                         this._image.src = url;
9107                 }
9108                 return this;
9109         },
9110
9111         // @method setBounds(bounds: LatLngBounds): this
9112         // Update the bounds that this ImageOverlay covers
9113         setBounds: function (bounds) {
9114                 this._bounds = toLatLngBounds(bounds);
9115
9116                 if (this._map) {
9117                         this._reset();
9118                 }
9119                 return this;
9120         },
9121
9122         getEvents: function () {
9123                 var events = {
9124                         zoom: this._reset,
9125                         viewreset: this._reset
9126                 };
9127
9128                 if (this._zoomAnimated) {
9129                         events.zoomanim = this._animateZoom;
9130                 }
9131
9132                 return events;
9133         },
9134
9135         // @method setZIndex(value: Number): this
9136         // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9137         setZIndex: function (value) {
9138                 this.options.zIndex = value;
9139                 this._updateZIndex();
9140                 return this;
9141         },
9142
9143         // @method getBounds(): LatLngBounds
9144         // Get the bounds that this ImageOverlay covers
9145         getBounds: function () {
9146                 return this._bounds;
9147         },
9148
9149         // @method getElement(): HTMLElement
9150         // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9151         // used by this overlay.
9152         getElement: function () {
9153                 return this._image;
9154         },
9155
9156         _initImage: function () {
9157                 var wasElementSupplied = this._url.tagName === 'IMG';
9158                 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9159
9160                 addClass(img, 'leaflet-image-layer');
9161                 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9162                 if (this.options.className) { addClass(img, this.options.className); }
9163
9164                 img.onselectstart = falseFn;
9165                 img.onmousemove = falseFn;
9166
9167                 // @event load: Event
9168                 // Fired when the ImageOverlay layer has loaded its image
9169                 img.onload = bind(this.fire, this, 'load');
9170                 img.onerror = bind(this._overlayOnError, this, 'error');
9171
9172                 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9173                         img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9174                 }
9175
9176                 if (this.options.zIndex) {
9177                         this._updateZIndex();
9178                 }
9179
9180                 if (wasElementSupplied) {
9181                         this._url = img.src;
9182                         return;
9183                 }
9184
9185                 img.src = this._url;
9186                 img.alt = this.options.alt;
9187         },
9188
9189         _animateZoom: function (e) {
9190                 var scale = this._map.getZoomScale(e.zoom),
9191                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9192
9193                 setTransform(this._image, offset, scale);
9194         },
9195
9196         _reset: function () {
9197                 var image = this._image,
9198                     bounds = new Bounds(
9199                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9200                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9201                     size = bounds.getSize();
9202
9203                 setPosition(image, bounds.min);
9204
9205                 image.style.width  = size.x + 'px';
9206                 image.style.height = size.y + 'px';
9207         },
9208
9209         _updateOpacity: function () {
9210                 setOpacity(this._image, this.options.opacity);
9211         },
9212
9213         _updateZIndex: function () {
9214                 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9215                         this._image.style.zIndex = this.options.zIndex;
9216                 }
9217         },
9218
9219         _overlayOnError: function () {
9220                 // @event error: Event
9221                 // Fired when the ImageOverlay layer fails to load its image
9222                 this.fire('error');
9223
9224                 var errorUrl = this.options.errorOverlayUrl;
9225                 if (errorUrl && this._url !== errorUrl) {
9226                         this._url = errorUrl;
9227                         this._image.src = errorUrl;
9228                 }
9229         }
9230 });
9231
9232 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9233 // Instantiates an image overlay object given the URL of the image and the
9234 // geographical bounds it is tied to.
9235 var imageOverlay = function (url, bounds, options) {
9236         return new ImageOverlay(url, bounds, options);
9237 };
9238
9239 /*
9240  * @class VideoOverlay
9241  * @aka L.VideoOverlay
9242  * @inherits ImageOverlay
9243  *
9244  * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9245  *
9246  * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9247  * HTML5 element.
9248  *
9249  * @example
9250  *
9251  * ```js
9252  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9253  *      videoBounds = [[ 32, -130], [ 13, -100]];
9254  * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9255  * ```
9256  */
9257
9258 var VideoOverlay = ImageOverlay.extend({
9259
9260         // @section
9261         // @aka VideoOverlay options
9262         options: {
9263                 // @option autoplay: Boolean = true
9264                 // Whether the video starts playing automatically when loaded.
9265                 autoplay: true,
9266
9267                 // @option loop: Boolean = true
9268                 // Whether the video will loop back to the beginning when played.
9269                 loop: true
9270         },
9271
9272         _initImage: function () {
9273                 var wasElementSupplied = this._url.tagName === 'VIDEO';
9274                 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9275
9276                 addClass(vid, 'leaflet-image-layer');
9277                 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9278
9279                 vid.onselectstart = falseFn;
9280                 vid.onmousemove = falseFn;
9281
9282                 // @event load: Event
9283                 // Fired when the video has finished loading the first frame
9284                 vid.onloadeddata = bind(this.fire, this, 'load');
9285
9286                 if (wasElementSupplied) {
9287                         var sourceElements = vid.getElementsByTagName('source');
9288                         var sources = [];
9289                         for (var j = 0; j < sourceElements.length; j++) {
9290                                 sources.push(sourceElements[j].src);
9291                         }
9292
9293                         this._url = (sourceElements.length > 0) ? sources : [vid.src];
9294                         return;
9295                 }
9296
9297                 if (!isArray(this._url)) { this._url = [this._url]; }
9298
9299                 vid.autoplay = !!this.options.autoplay;
9300                 vid.loop = !!this.options.loop;
9301                 for (var i = 0; i < this._url.length; i++) {
9302                         var source = create$1('source');
9303                         source.src = this._url[i];
9304                         vid.appendChild(source);
9305                 }
9306         }
9307
9308         // @method getElement(): HTMLVideoElement
9309         // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9310         // used by this overlay.
9311 });
9312
9313
9314 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9315 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9316 // geographical bounds it is tied to.
9317
9318 function videoOverlay(video, bounds, options) {
9319         return new VideoOverlay(video, bounds, options);
9320 }
9321
9322 /*
9323  * @class DivOverlay
9324  * @inherits Layer
9325  * @aka L.DivOverlay
9326  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9327  */
9328
9329 // @namespace DivOverlay
9330 var DivOverlay = Layer.extend({
9331
9332         // @section
9333         // @aka DivOverlay options
9334         options: {
9335                 // @option offset: Point = Point(0, 7)
9336                 // The offset of the popup position. Useful to control the anchor
9337                 // of the popup when opening it on some overlays.
9338                 offset: [0, 7],
9339
9340                 // @option className: String = ''
9341                 // A custom CSS class name to assign to the popup.
9342                 className: '',
9343
9344                 // @option pane: String = 'popupPane'
9345                 // `Map pane` where the popup will be added.
9346                 pane: 'popupPane'
9347         },
9348
9349         initialize: function (options, source) {
9350                 setOptions(this, options);
9351
9352                 this._source = source;
9353         },
9354
9355         onAdd: function (map) {
9356                 this._zoomAnimated = map._zoomAnimated;
9357
9358                 if (!this._container) {
9359                         this._initLayout();
9360                 }
9361
9362                 if (map._fadeAnimated) {
9363                         setOpacity(this._container, 0);
9364                 }
9365
9366                 clearTimeout(this._removeTimeout);
9367                 this.getPane().appendChild(this._container);
9368                 this.update();
9369
9370                 if (map._fadeAnimated) {
9371                         setOpacity(this._container, 1);
9372                 }
9373
9374                 this.bringToFront();
9375         },
9376
9377         onRemove: function (map) {
9378                 if (map._fadeAnimated) {
9379                         setOpacity(this._container, 0);
9380                         this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9381                 } else {
9382                         remove(this._container);
9383                 }
9384         },
9385
9386         // @namespace Popup
9387         // @method getLatLng: LatLng
9388         // Returns the geographical point of popup.
9389         getLatLng: function () {
9390                 return this._latlng;
9391         },
9392
9393         // @method setLatLng(latlng: LatLng): this
9394         // Sets the geographical point where the popup will open.
9395         setLatLng: function (latlng) {
9396                 this._latlng = toLatLng(latlng);
9397                 if (this._map) {
9398                         this._updatePosition();
9399                         this._adjustPan();
9400                 }
9401                 return this;
9402         },
9403
9404         // @method getContent: String|HTMLElement
9405         // Returns the content of the popup.
9406         getContent: function () {
9407                 return this._content;
9408         },
9409
9410         // @method setContent(htmlContent: String|HTMLElement|Function): this
9411         // 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.
9412         setContent: function (content) {
9413                 this._content = content;
9414                 this.update();
9415                 return this;
9416         },
9417
9418         // @method getElement: String|HTMLElement
9419         // Alias for [getContent()](#popup-getcontent)
9420         getElement: function () {
9421                 return this._container;
9422         },
9423
9424         // @method update: null
9425         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9426         update: function () {
9427                 if (!this._map) { return; }
9428
9429                 this._container.style.visibility = 'hidden';
9430
9431                 this._updateContent();
9432                 this._updateLayout();
9433                 this._updatePosition();
9434
9435                 this._container.style.visibility = '';
9436
9437                 this._adjustPan();
9438         },
9439
9440         getEvents: function () {
9441                 var events = {
9442                         zoom: this._updatePosition,
9443                         viewreset: this._updatePosition
9444                 };
9445
9446                 if (this._zoomAnimated) {
9447                         events.zoomanim = this._animateZoom;
9448                 }
9449                 return events;
9450         },
9451
9452         // @method isOpen: Boolean
9453         // Returns `true` when the popup is visible on the map.
9454         isOpen: function () {
9455                 return !!this._map && this._map.hasLayer(this);
9456         },
9457
9458         // @method bringToFront: this
9459         // Brings this popup in front of other popups (in the same map pane).
9460         bringToFront: function () {
9461                 if (this._map) {
9462                         toFront(this._container);
9463                 }
9464                 return this;
9465         },
9466
9467         // @method bringToBack: this
9468         // Brings this popup to the back of other popups (in the same map pane).
9469         bringToBack: function () {
9470                 if (this._map) {
9471                         toBack(this._container);
9472                 }
9473                 return this;
9474         },
9475
9476         _updateContent: function () {
9477                 if (!this._content) { return; }
9478
9479                 var node = this._contentNode;
9480                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9481
9482                 if (typeof content === 'string') {
9483                         node.innerHTML = content;
9484                 } else {
9485                         while (node.hasChildNodes()) {
9486                                 node.removeChild(node.firstChild);
9487                         }
9488                         node.appendChild(content);
9489                 }
9490                 this.fire('contentupdate');
9491         },
9492
9493         _updatePosition: function () {
9494                 if (!this._map) { return; }
9495
9496                 var pos = this._map.latLngToLayerPoint(this._latlng),
9497                     offset = toPoint(this.options.offset),
9498                     anchor = this._getAnchor();
9499
9500                 if (this._zoomAnimated) {
9501                         setPosition(this._container, pos.add(anchor));
9502                 } else {
9503                         offset = offset.add(pos).add(anchor);
9504                 }
9505
9506                 var bottom = this._containerBottom = -offset.y,
9507                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9508
9509                 // bottom position the popup in case the height of the popup changes (images loading etc)
9510                 this._container.style.bottom = bottom + 'px';
9511                 this._container.style.left = left + 'px';
9512         },
9513
9514         _getAnchor: function () {
9515                 return [0, 0];
9516         }
9517
9518 });
9519
9520 /*
9521  * @class Popup
9522  * @inherits DivOverlay
9523  * @aka L.Popup
9524  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9525  * open popups while making sure that only one popup is open at one time
9526  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9527  *
9528  * @example
9529  *
9530  * If you want to just bind a popup to marker click and then open it, it's really easy:
9531  *
9532  * ```js
9533  * marker.bindPopup(popupContent).openPopup();
9534  * ```
9535  * Path overlays like polylines also have a `bindPopup` method.
9536  * Here's a more complicated way to open a popup on a map:
9537  *
9538  * ```js
9539  * var popup = L.popup()
9540  *      .setLatLng(latlng)
9541  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9542  *      .openOn(map);
9543  * ```
9544  */
9545
9546
9547 // @namespace Popup
9548 var Popup = DivOverlay.extend({
9549
9550         // @section
9551         // @aka Popup options
9552         options: {
9553                 // @option maxWidth: Number = 300
9554                 // Max width of the popup, in pixels.
9555                 maxWidth: 300,
9556
9557                 // @option minWidth: Number = 50
9558                 // Min width of the popup, in pixels.
9559                 minWidth: 50,
9560
9561                 // @option maxHeight: Number = null
9562                 // If set, creates a scrollable container of the given height
9563                 // inside a popup if its content exceeds it.
9564                 maxHeight: null,
9565
9566                 // @option autoPan: Boolean = true
9567                 // Set it to `false` if you don't want the map to do panning animation
9568                 // to fit the opened popup.
9569                 autoPan: true,
9570
9571                 // @option autoPanPaddingTopLeft: Point = null
9572                 // The margin between the popup and the top left corner of the map
9573                 // view after autopanning was performed.
9574                 autoPanPaddingTopLeft: null,
9575
9576                 // @option autoPanPaddingBottomRight: Point = null
9577                 // The margin between the popup and the bottom right corner of the map
9578                 // view after autopanning was performed.
9579                 autoPanPaddingBottomRight: null,
9580
9581                 // @option autoPanPadding: Point = Point(5, 5)
9582                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9583                 autoPanPadding: [5, 5],
9584
9585                 // @option keepInView: Boolean = false
9586                 // Set it to `true` if you want to prevent users from panning the popup
9587                 // off of the screen while it is open.
9588                 keepInView: false,
9589
9590                 // @option closeButton: Boolean = true
9591                 // Controls the presence of a close button in the popup.
9592                 closeButton: true,
9593
9594                 // @option autoClose: Boolean = true
9595                 // Set it to `false` if you want to override the default behavior of
9596                 // the popup closing when another popup is opened.
9597                 autoClose: true,
9598
9599                 // @option closeOnEscapeKey: Boolean = true
9600                 // Set it to `false` if you want to override the default behavior of
9601                 // the ESC key for closing of the popup.
9602                 closeOnEscapeKey: true,
9603
9604                 // @option closeOnClick: Boolean = *
9605                 // Set it if you want to override the default behavior of the popup closing when user clicks
9606                 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9607
9608                 // @option className: String = ''
9609                 // A custom CSS class name to assign to the popup.
9610                 className: ''
9611         },
9612
9613         // @namespace Popup
9614         // @method openOn(map: Map): this
9615         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9616         openOn: function (map) {
9617                 map.openPopup(this);
9618                 return this;
9619         },
9620
9621         onAdd: function (map) {
9622                 DivOverlay.prototype.onAdd.call(this, map);
9623
9624                 // @namespace Map
9625                 // @section Popup events
9626                 // @event popupopen: PopupEvent
9627                 // Fired when a popup is opened in the map
9628                 map.fire('popupopen', {popup: this});
9629
9630                 if (this._source) {
9631                         // @namespace Layer
9632                         // @section Popup events
9633                         // @event popupopen: PopupEvent
9634                         // Fired when a popup bound to this layer is opened
9635                         this._source.fire('popupopen', {popup: this}, true);
9636                         // For non-path layers, we toggle the popup when clicking
9637                         // again the layer, so prevent the map to reopen it.
9638                         if (!(this._source instanceof Path)) {
9639                                 this._source.on('preclick', stopPropagation);
9640                         }
9641                 }
9642         },
9643
9644         onRemove: function (map) {
9645                 DivOverlay.prototype.onRemove.call(this, map);
9646
9647                 // @namespace Map
9648                 // @section Popup events
9649                 // @event popupclose: PopupEvent
9650                 // Fired when a popup in the map is closed
9651                 map.fire('popupclose', {popup: this});
9652
9653                 if (this._source) {
9654                         // @namespace Layer
9655                         // @section Popup events
9656                         // @event popupclose: PopupEvent
9657                         // Fired when a popup bound to this layer is closed
9658                         this._source.fire('popupclose', {popup: this}, true);
9659                         if (!(this._source instanceof Path)) {
9660                                 this._source.off('preclick', stopPropagation);
9661                         }
9662                 }
9663         },
9664
9665         getEvents: function () {
9666                 var events = DivOverlay.prototype.getEvents.call(this);
9667
9668                 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9669                         events.preclick = this._close;
9670                 }
9671
9672                 if (this.options.keepInView) {
9673                         events.moveend = this._adjustPan;
9674                 }
9675
9676                 return events;
9677         },
9678
9679         _close: function () {
9680                 if (this._map) {
9681                         this._map.closePopup(this);
9682                 }
9683         },
9684
9685         _initLayout: function () {
9686                 var prefix = 'leaflet-popup',
9687                     container = this._container = create$1('div',
9688                         prefix + ' ' + (this.options.className || '') +
9689                         ' leaflet-zoom-animated');
9690
9691                 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9692                 this._contentNode = create$1('div', prefix + '-content', wrapper);
9693
9694                 disableClickPropagation(wrapper);
9695                 disableScrollPropagation(this._contentNode);
9696                 on(wrapper, 'contextmenu', stopPropagation);
9697
9698                 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9699                 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9700
9701                 if (this.options.closeButton) {
9702                         var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9703                         closeButton.href = '#close';
9704                         closeButton.innerHTML = '&#215;';
9705
9706                         on(closeButton, 'click', this._onCloseButtonClick, this);
9707                 }
9708         },
9709
9710         _updateLayout: function () {
9711                 var container = this._contentNode,
9712                     style = container.style;
9713
9714                 style.width = '';
9715                 style.whiteSpace = 'nowrap';
9716
9717                 var width = container.offsetWidth;
9718                 width = Math.min(width, this.options.maxWidth);
9719                 width = Math.max(width, this.options.minWidth);
9720
9721                 style.width = (width + 1) + 'px';
9722                 style.whiteSpace = '';
9723
9724                 style.height = '';
9725
9726                 var height = container.offsetHeight,
9727                     maxHeight = this.options.maxHeight,
9728                     scrolledClass = 'leaflet-popup-scrolled';
9729
9730                 if (maxHeight && height > maxHeight) {
9731                         style.height = maxHeight + 'px';
9732                         addClass(container, scrolledClass);
9733                 } else {
9734                         removeClass(container, scrolledClass);
9735                 }
9736
9737                 this._containerWidth = this._container.offsetWidth;
9738         },
9739
9740         _animateZoom: function (e) {
9741                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9742                     anchor = this._getAnchor();
9743                 setPosition(this._container, pos.add(anchor));
9744         },
9745
9746         _adjustPan: function () {
9747                 if (!this.options.autoPan) { return; }
9748                 if (this._map._panAnim) { this._map._panAnim.stop(); }
9749
9750                 var map = this._map,
9751                     marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9752                     containerHeight = this._container.offsetHeight + marginBottom,
9753                     containerWidth = this._containerWidth,
9754                     layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9755
9756                 layerPos._add(getPosition(this._container));
9757
9758                 var containerPos = map.layerPointToContainerPoint(layerPos),
9759                     padding = toPoint(this.options.autoPanPadding),
9760                     paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9761                     paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9762                     size = map.getSize(),
9763                     dx = 0,
9764                     dy = 0;
9765
9766                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9767                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9768                 }
9769                 if (containerPos.x - dx - paddingTL.x < 0) { // left
9770                         dx = containerPos.x - paddingTL.x;
9771                 }
9772                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9773                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9774                 }
9775                 if (containerPos.y - dy - paddingTL.y < 0) { // top
9776                         dy = containerPos.y - paddingTL.y;
9777                 }
9778
9779                 // @namespace Map
9780                 // @section Popup events
9781                 // @event autopanstart: Event
9782                 // Fired when the map starts autopanning when opening a popup.
9783                 if (dx || dy) {
9784                         map
9785                             .fire('autopanstart')
9786                             .panBy([dx, dy]);
9787                 }
9788         },
9789
9790         _onCloseButtonClick: function (e) {
9791                 this._close();
9792                 stop(e);
9793         },
9794
9795         _getAnchor: function () {
9796                 // Where should we anchor the popup on the source layer?
9797                 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9798         }
9799
9800 });
9801
9802 // @namespace Popup
9803 // @factory L.popup(options?: Popup options, source?: Layer)
9804 // 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.
9805 var popup = function (options, source) {
9806         return new Popup(options, source);
9807 };
9808
9809
9810 /* @namespace Map
9811  * @section Interaction Options
9812  * @option closePopupOnClick: Boolean = true
9813  * Set it to `false` if you don't want popups to close when user clicks the map.
9814  */
9815 Map.mergeOptions({
9816         closePopupOnClick: true
9817 });
9818
9819
9820 // @namespace Map
9821 // @section Methods for Layers and Controls
9822 Map.include({
9823         // @method openPopup(popup: Popup): this
9824         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9825         // @alternative
9826         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9827         // Creates a popup with the specified content and options and opens it in the given point on a map.
9828         openPopup: function (popup, latlng, options) {
9829                 if (!(popup instanceof Popup)) {
9830                         popup = new Popup(options).setContent(popup);
9831                 }
9832
9833                 if (latlng) {
9834                         popup.setLatLng(latlng);
9835                 }
9836
9837                 if (this.hasLayer(popup)) {
9838                         return this;
9839                 }
9840
9841                 if (this._popup && this._popup.options.autoClose) {
9842                         this.closePopup();
9843                 }
9844
9845                 this._popup = popup;
9846                 return this.addLayer(popup);
9847         },
9848
9849         // @method closePopup(popup?: Popup): this
9850         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9851         closePopup: function (popup) {
9852                 if (!popup || popup === this._popup) {
9853                         popup = this._popup;
9854                         this._popup = null;
9855                 }
9856                 if (popup) {
9857                         this.removeLayer(popup);
9858                 }
9859                 return this;
9860         }
9861 });
9862
9863 /*
9864  * @namespace Layer
9865  * @section Popup methods example
9866  *
9867  * All layers share a set of methods convenient for binding popups to it.
9868  *
9869  * ```js
9870  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9871  * layer.openPopup();
9872  * layer.closePopup();
9873  * ```
9874  *
9875  * 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.
9876  */
9877
9878 // @section Popup methods
9879 Layer.include({
9880
9881         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9882         // Binds a popup to the layer with the passed `content` and sets up the
9883         // necessary event listeners. If a `Function` is passed it will receive
9884         // the layer as the first argument and should return a `String` or `HTMLElement`.
9885         bindPopup: function (content, options) {
9886
9887                 if (content instanceof Popup) {
9888                         setOptions(content, options);
9889                         this._popup = content;
9890                         content._source = this;
9891                 } else {
9892                         if (!this._popup || options) {
9893                                 this._popup = new Popup(options, this);
9894                         }
9895                         this._popup.setContent(content);
9896                 }
9897
9898                 if (!this._popupHandlersAdded) {
9899                         this.on({
9900                                 click: this._openPopup,
9901                                 keypress: this._onKeyPress,
9902                                 remove: this.closePopup,
9903                                 move: this._movePopup
9904                         });
9905                         this._popupHandlersAdded = true;
9906                 }
9907
9908                 return this;
9909         },
9910
9911         // @method unbindPopup(): this
9912         // Removes the popup previously bound with `bindPopup`.
9913         unbindPopup: function () {
9914                 if (this._popup) {
9915                         this.off({
9916                                 click: this._openPopup,
9917                                 keypress: this._onKeyPress,
9918                                 remove: this.closePopup,
9919                                 move: this._movePopup
9920                         });
9921                         this._popupHandlersAdded = false;
9922                         this._popup = null;
9923                 }
9924                 return this;
9925         },
9926
9927         // @method openPopup(latlng?: LatLng): this
9928         // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
9929         openPopup: function (layer, latlng) {
9930                 if (!(layer instanceof Layer)) {
9931                         latlng = layer;
9932                         layer = this;
9933                 }
9934
9935                 if (layer instanceof FeatureGroup) {
9936                         for (var id in this._layers) {
9937                                 layer = this._layers[id];
9938                                 break;
9939                         }
9940                 }
9941
9942                 if (!latlng) {
9943                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9944                 }
9945
9946                 if (this._popup && this._map) {
9947                         // set popup source to this layer
9948                         this._popup._source = layer;
9949
9950                         // update the popup (content, layout, ect...)
9951                         this._popup.update();
9952
9953                         // open the popup on the map
9954                         this._map.openPopup(this._popup, latlng);
9955                 }
9956
9957                 return this;
9958         },
9959
9960         // @method closePopup(): this
9961         // Closes the popup bound to this layer if it is open.
9962         closePopup: function () {
9963                 if (this._popup) {
9964                         this._popup._close();
9965                 }
9966                 return this;
9967         },
9968
9969         // @method togglePopup(): this
9970         // Opens or closes the popup bound to this layer depending on its current state.
9971         togglePopup: function (target) {
9972                 if (this._popup) {
9973                         if (this._popup._map) {
9974                                 this.closePopup();
9975                         } else {
9976                                 this.openPopup(target);
9977                         }
9978                 }
9979                 return this;
9980         },
9981
9982         // @method isPopupOpen(): boolean
9983         // Returns `true` if the popup bound to this layer is currently open.
9984         isPopupOpen: function () {
9985                 return (this._popup ? this._popup.isOpen() : false);
9986         },
9987
9988         // @method setPopupContent(content: String|HTMLElement|Popup): this
9989         // Sets the content of the popup bound to this layer.
9990         setPopupContent: function (content) {
9991                 if (this._popup) {
9992                         this._popup.setContent(content);
9993                 }
9994                 return this;
9995         },
9996
9997         // @method getPopup(): Popup
9998         // Returns the popup bound to this layer.
9999         getPopup: function () {
10000                 return this._popup;
10001         },
10002
10003         _openPopup: function (e) {
10004                 var layer = e.layer || e.target;
10005
10006                 if (!this._popup) {
10007                         return;
10008                 }
10009
10010                 if (!this._map) {
10011                         return;
10012                 }
10013
10014                 // prevent map click
10015                 stop(e);
10016
10017                 // if this inherits from Path its a vector and we can just
10018                 // open the popup at the new location
10019                 if (layer instanceof Path) {
10020                         this.openPopup(e.layer || e.target, e.latlng);
10021                         return;
10022                 }
10023
10024                 // otherwise treat it like a marker and figure out
10025                 // if we should toggle it open/closed
10026                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10027                         this.closePopup();
10028                 } else {
10029                         this.openPopup(layer, e.latlng);
10030                 }
10031         },
10032
10033         _movePopup: function (e) {
10034                 this._popup.setLatLng(e.latlng);
10035         },
10036
10037         _onKeyPress: function (e) {
10038                 if (e.originalEvent.keyCode === 13) {
10039                         this._openPopup(e);
10040                 }
10041         }
10042 });
10043
10044 /*
10045  * @class Tooltip
10046  * @inherits DivOverlay
10047  * @aka L.Tooltip
10048  * Used to display small texts on top of map layers.
10049  *
10050  * @example
10051  *
10052  * ```js
10053  * marker.bindTooltip("my tooltip text").openTooltip();
10054  * ```
10055  * Note about tooltip offset. Leaflet takes two options in consideration
10056  * for computing tooltip offsetting:
10057  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10058  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
10059  *   move it to the bottom. Negatives will move to the left and top.
10060  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10061  *   should adapt this value if you use a custom icon.
10062  */
10063
10064
10065 // @namespace Tooltip
10066 var Tooltip = DivOverlay.extend({
10067
10068         // @section
10069         // @aka Tooltip options
10070         options: {
10071                 // @option pane: String = 'tooltipPane'
10072                 // `Map pane` where the tooltip will be added.
10073                 pane: 'tooltipPane',
10074
10075                 // @option offset: Point = Point(0, 0)
10076                 // Optional offset of the tooltip position.
10077                 offset: [0, 0],
10078
10079                 // @option direction: String = 'auto'
10080                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10081                 // `top`, `bottom`, `center`, `auto`.
10082                 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10083                 // position on the map.
10084                 direction: 'auto',
10085
10086                 // @option permanent: Boolean = false
10087                 // Whether to open the tooltip permanently or only on mouseover.
10088                 permanent: false,
10089
10090                 // @option sticky: Boolean = false
10091                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10092                 sticky: false,
10093
10094                 // @option interactive: Boolean = false
10095                 // If true, the tooltip will listen to the feature events.
10096                 interactive: false,
10097
10098                 // @option opacity: Number = 0.9
10099                 // Tooltip container opacity.
10100                 opacity: 0.9
10101         },
10102
10103         onAdd: function (map) {
10104                 DivOverlay.prototype.onAdd.call(this, map);
10105                 this.setOpacity(this.options.opacity);
10106
10107                 // @namespace Map
10108                 // @section Tooltip events
10109                 // @event tooltipopen: TooltipEvent
10110                 // Fired when a tooltip is opened in the map.
10111                 map.fire('tooltipopen', {tooltip: this});
10112
10113                 if (this._source) {
10114                         // @namespace Layer
10115                         // @section Tooltip events
10116                         // @event tooltipopen: TooltipEvent
10117                         // Fired when a tooltip bound to this layer is opened.
10118                         this._source.fire('tooltipopen', {tooltip: this}, true);
10119                 }
10120         },
10121
10122         onRemove: function (map) {
10123                 DivOverlay.prototype.onRemove.call(this, map);
10124
10125                 // @namespace Map
10126                 // @section Tooltip events
10127                 // @event tooltipclose: TooltipEvent
10128                 // Fired when a tooltip in the map is closed.
10129                 map.fire('tooltipclose', {tooltip: this});
10130
10131                 if (this._source) {
10132                         // @namespace Layer
10133                         // @section Tooltip events
10134                         // @event tooltipclose: TooltipEvent
10135                         // Fired when a tooltip bound to this layer is closed.
10136                         this._source.fire('tooltipclose', {tooltip: this}, true);
10137                 }
10138         },
10139
10140         getEvents: function () {
10141                 var events = DivOverlay.prototype.getEvents.call(this);
10142
10143                 if (touch && !this.options.permanent) {
10144                         events.preclick = this._close;
10145                 }
10146
10147                 return events;
10148         },
10149
10150         _close: function () {
10151                 if (this._map) {
10152                         this._map.closeTooltip(this);
10153                 }
10154         },
10155
10156         _initLayout: function () {
10157                 var prefix = 'leaflet-tooltip',
10158                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10159
10160                 this._contentNode = this._container = create$1('div', className);
10161         },
10162
10163         _updateLayout: function () {},
10164
10165         _adjustPan: function () {},
10166
10167         _setPosition: function (pos) {
10168                 var map = this._map,
10169                     container = this._container,
10170                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
10171                     tooltipPoint = map.layerPointToContainerPoint(pos),
10172                     direction = this.options.direction,
10173                     tooltipWidth = container.offsetWidth,
10174                     tooltipHeight = container.offsetHeight,
10175                     offset = toPoint(this.options.offset),
10176                     anchor = this._getAnchor();
10177
10178                 if (direction === 'top') {
10179                         pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10180                 } else if (direction === 'bottom') {
10181                         pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10182                 } else if (direction === 'center') {
10183                         pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10184                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10185                         direction = 'right';
10186                         pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10187                 } else {
10188                         direction = 'left';
10189                         pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10190                 }
10191
10192                 removeClass(container, 'leaflet-tooltip-right');
10193                 removeClass(container, 'leaflet-tooltip-left');
10194                 removeClass(container, 'leaflet-tooltip-top');
10195                 removeClass(container, 'leaflet-tooltip-bottom');
10196                 addClass(container, 'leaflet-tooltip-' + direction);
10197                 setPosition(container, pos);
10198         },
10199
10200         _updatePosition: function () {
10201                 var pos = this._map.latLngToLayerPoint(this._latlng);
10202                 this._setPosition(pos);
10203         },
10204
10205         setOpacity: function (opacity) {
10206                 this.options.opacity = opacity;
10207
10208                 if (this._container) {
10209                         setOpacity(this._container, opacity);
10210                 }
10211         },
10212
10213         _animateZoom: function (e) {
10214                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10215                 this._setPosition(pos);
10216         },
10217
10218         _getAnchor: function () {
10219                 // Where should we anchor the tooltip on the source layer?
10220                 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10221         }
10222
10223 });
10224
10225 // @namespace Tooltip
10226 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10227 // 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.
10228 var tooltip = function (options, source) {
10229         return new Tooltip(options, source);
10230 };
10231
10232 // @namespace Map
10233 // @section Methods for Layers and Controls
10234 Map.include({
10235
10236         // @method openTooltip(tooltip: Tooltip): this
10237         // Opens the specified tooltip.
10238         // @alternative
10239         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10240         // Creates a tooltip with the specified content and options and open it.
10241         openTooltip: function (tooltip, latlng, options) {
10242                 if (!(tooltip instanceof Tooltip)) {
10243                         tooltip = new Tooltip(options).setContent(tooltip);
10244                 }
10245
10246                 if (latlng) {
10247                         tooltip.setLatLng(latlng);
10248                 }
10249
10250                 if (this.hasLayer(tooltip)) {
10251                         return this;
10252                 }
10253
10254                 return this.addLayer(tooltip);
10255         },
10256
10257         // @method closeTooltip(tooltip?: Tooltip): this
10258         // Closes the tooltip given as parameter.
10259         closeTooltip: function (tooltip) {
10260                 if (tooltip) {
10261                         this.removeLayer(tooltip);
10262                 }
10263                 return this;
10264         }
10265
10266 });
10267
10268 /*
10269  * @namespace Layer
10270  * @section Tooltip methods example
10271  *
10272  * All layers share a set of methods convenient for binding tooltips to it.
10273  *
10274  * ```js
10275  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10276  * layer.openTooltip();
10277  * layer.closeTooltip();
10278  * ```
10279  */
10280
10281 // @section Tooltip methods
10282 Layer.include({
10283
10284         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10285         // Binds a tooltip to the layer with the passed `content` and sets up the
10286         // necessary event listeners. If a `Function` is passed it will receive
10287         // the layer as the first argument and should return a `String` or `HTMLElement`.
10288         bindTooltip: function (content, options) {
10289
10290                 if (content instanceof Tooltip) {
10291                         setOptions(content, options);
10292                         this._tooltip = content;
10293                         content._source = this;
10294                 } else {
10295                         if (!this._tooltip || options) {
10296                                 this._tooltip = new Tooltip(options, this);
10297                         }
10298                         this._tooltip.setContent(content);
10299
10300                 }
10301
10302                 this._initTooltipInteractions();
10303
10304                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10305                         this.openTooltip();
10306                 }
10307
10308                 return this;
10309         },
10310
10311         // @method unbindTooltip(): this
10312         // Removes the tooltip previously bound with `bindTooltip`.
10313         unbindTooltip: function () {
10314                 if (this._tooltip) {
10315                         this._initTooltipInteractions(true);
10316                         this.closeTooltip();
10317                         this._tooltip = null;
10318                 }
10319                 return this;
10320         },
10321
10322         _initTooltipInteractions: function (remove$$1) {
10323                 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10324                 var onOff = remove$$1 ? 'off' : 'on',
10325                     events = {
10326                         remove: this.closeTooltip,
10327                         move: this._moveTooltip
10328                     };
10329                 if (!this._tooltip.options.permanent) {
10330                         events.mouseover = this._openTooltip;
10331                         events.mouseout = this.closeTooltip;
10332                         if (this._tooltip.options.sticky) {
10333                                 events.mousemove = this._moveTooltip;
10334                         }
10335                         if (touch) {
10336                                 events.click = this._openTooltip;
10337                         }
10338                 } else {
10339                         events.add = this._openTooltip;
10340                 }
10341                 this[onOff](events);
10342                 this._tooltipHandlersAdded = !remove$$1;
10343         },
10344
10345         // @method openTooltip(latlng?: LatLng): this
10346         // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10347         openTooltip: function (layer, latlng) {
10348                 if (!(layer instanceof Layer)) {
10349                         latlng = layer;
10350                         layer = this;
10351                 }
10352
10353                 if (layer instanceof FeatureGroup) {
10354                         for (var id in this._layers) {
10355                                 layer = this._layers[id];
10356                                 break;
10357                         }
10358                 }
10359
10360                 if (!latlng) {
10361                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10362                 }
10363
10364                 if (this._tooltip && this._map) {
10365
10366                         // set tooltip source to this layer
10367                         this._tooltip._source = layer;
10368
10369                         // update the tooltip (content, layout, ect...)
10370                         this._tooltip.update();
10371
10372                         // open the tooltip on the map
10373                         this._map.openTooltip(this._tooltip, latlng);
10374
10375                         // Tooltip container may not be defined if not permanent and never
10376                         // opened.
10377                         if (this._tooltip.options.interactive && this._tooltip._container) {
10378                                 addClass(this._tooltip._container, 'leaflet-clickable');
10379                                 this.addInteractiveTarget(this._tooltip._container);
10380                         }
10381                 }
10382
10383                 return this;
10384         },
10385
10386         // @method closeTooltip(): this
10387         // Closes the tooltip bound to this layer if it is open.
10388         closeTooltip: function () {
10389                 if (this._tooltip) {
10390                         this._tooltip._close();
10391                         if (this._tooltip.options.interactive && this._tooltip._container) {
10392                                 removeClass(this._tooltip._container, 'leaflet-clickable');
10393                                 this.removeInteractiveTarget(this._tooltip._container);
10394                         }
10395                 }
10396                 return this;
10397         },
10398
10399         // @method toggleTooltip(): this
10400         // Opens or closes the tooltip bound to this layer depending on its current state.
10401         toggleTooltip: function (target) {
10402                 if (this._tooltip) {
10403                         if (this._tooltip._map) {
10404                                 this.closeTooltip();
10405                         } else {
10406                                 this.openTooltip(target);
10407                         }
10408                 }
10409                 return this;
10410         },
10411
10412         // @method isTooltipOpen(): boolean
10413         // Returns `true` if the tooltip bound to this layer is currently open.
10414         isTooltipOpen: function () {
10415                 return this._tooltip.isOpen();
10416         },
10417
10418         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10419         // Sets the content of the tooltip bound to this layer.
10420         setTooltipContent: function (content) {
10421                 if (this._tooltip) {
10422                         this._tooltip.setContent(content);
10423                 }
10424                 return this;
10425         },
10426
10427         // @method getTooltip(): Tooltip
10428         // Returns the tooltip bound to this layer.
10429         getTooltip: function () {
10430                 return this._tooltip;
10431         },
10432
10433         _openTooltip: function (e) {
10434                 var layer = e.layer || e.target;
10435
10436                 if (!this._tooltip || !this._map) {
10437                         return;
10438                 }
10439                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10440         },
10441
10442         _moveTooltip: function (e) {
10443                 var latlng = e.latlng, containerPoint, layerPoint;
10444                 if (this._tooltip.options.sticky && e.originalEvent) {
10445                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10446                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10447                         latlng = this._map.layerPointToLatLng(layerPoint);
10448                 }
10449                 this._tooltip.setLatLng(latlng);
10450         }
10451 });
10452
10453 /*
10454  * @class DivIcon
10455  * @aka L.DivIcon
10456  * @inherits Icon
10457  *
10458  * Represents a lightweight icon for markers that uses a simple `<div>`
10459  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10460  *
10461  * @example
10462  * ```js
10463  * var myIcon = L.divIcon({className: 'my-div-icon'});
10464  * // you can set .my-div-icon styles in CSS
10465  *
10466  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10467  * ```
10468  *
10469  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10470  */
10471
10472 var DivIcon = Icon.extend({
10473         options: {
10474                 // @section
10475                 // @aka DivIcon options
10476                 iconSize: [12, 12], // also can be set through CSS
10477
10478                 // iconAnchor: (Point),
10479                 // popupAnchor: (Point),
10480
10481                 // @option html: String = ''
10482                 // Custom HTML code to put inside the div element, empty by default.
10483                 html: false,
10484
10485                 // @option bgPos: Point = [0, 0]
10486                 // Optional relative position of the background, in pixels
10487                 bgPos: null,
10488
10489                 className: 'leaflet-div-icon'
10490         },
10491
10492         createIcon: function (oldIcon) {
10493                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10494                     options = this.options;
10495
10496                 div.innerHTML = options.html !== false ? options.html : '';
10497
10498                 if (options.bgPos) {
10499                         var bgPos = toPoint(options.bgPos);
10500                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10501                 }
10502                 this._setIconStyles(div, 'icon');
10503
10504                 return div;
10505         },
10506
10507         createShadow: function () {
10508                 return null;
10509         }
10510 });
10511
10512 // @factory L.divIcon(options: DivIcon options)
10513 // Creates a `DivIcon` instance with the given options.
10514 function divIcon(options) {
10515         return new DivIcon(options);
10516 }
10517
10518 Icon.Default = IconDefault;
10519
10520 /*
10521  * @class GridLayer
10522  * @inherits Layer
10523  * @aka L.GridLayer
10524  *
10525  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10526  * 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.
10527  *
10528  *
10529  * @section Synchronous usage
10530  * @example
10531  *
10532  * 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.
10533  *
10534  * ```js
10535  * var CanvasLayer = L.GridLayer.extend({
10536  *     createTile: function(coords){
10537  *         // create a <canvas> element for drawing
10538  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10539  *
10540  *         // setup tile width and height according to the options
10541  *         var size = this.getTileSize();
10542  *         tile.width = size.x;
10543  *         tile.height = size.y;
10544  *
10545  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10546  *         var ctx = tile.getContext('2d');
10547  *
10548  *         // return the tile so it can be rendered on screen
10549  *         return tile;
10550  *     }
10551  * });
10552  * ```
10553  *
10554  * @section Asynchronous usage
10555  * @example
10556  *
10557  * 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.
10558  *
10559  * ```js
10560  * var CanvasLayer = L.GridLayer.extend({
10561  *     createTile: function(coords, done){
10562  *         var error;
10563  *
10564  *         // create a <canvas> element for drawing
10565  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10566  *
10567  *         // setup tile width and height according to the options
10568  *         var size = this.getTileSize();
10569  *         tile.width = size.x;
10570  *         tile.height = size.y;
10571  *
10572  *         // draw something asynchronously and pass the tile to the done() callback
10573  *         setTimeout(function() {
10574  *             done(error, tile);
10575  *         }, 1000);
10576  *
10577  *         return tile;
10578  *     }
10579  * });
10580  * ```
10581  *
10582  * @section
10583  */
10584
10585
10586 var GridLayer = Layer.extend({
10587
10588         // @section
10589         // @aka GridLayer options
10590         options: {
10591                 // @option tileSize: Number|Point = 256
10592                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10593                 tileSize: 256,
10594
10595                 // @option opacity: Number = 1.0
10596                 // Opacity of the tiles. Can be used in the `createTile()` function.
10597                 opacity: 1,
10598
10599                 // @option updateWhenIdle: Boolean = (depends)
10600                 // Load new tiles only when panning ends.
10601                 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10602                 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10603                 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10604                 updateWhenIdle: mobile,
10605
10606                 // @option updateWhenZooming: Boolean = true
10607                 // 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.
10608                 updateWhenZooming: true,
10609
10610                 // @option updateInterval: Number = 200
10611                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10612                 updateInterval: 200,
10613
10614                 // @option zIndex: Number = 1
10615                 // The explicit zIndex of the tile layer.
10616                 zIndex: 1,
10617
10618                 // @option bounds: LatLngBounds = undefined
10619                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10620                 bounds: null,
10621
10622                 // @option minZoom: Number = 0
10623                 // The minimum zoom level down to which this layer will be displayed (inclusive).
10624                 minZoom: 0,
10625
10626                 // @option maxZoom: Number = undefined
10627                 // The maximum zoom level up to which this layer will be displayed (inclusive).
10628                 maxZoom: undefined,
10629
10630                 // @option maxNativeZoom: Number = undefined
10631                 // Maximum zoom number the tile source has available. If it is specified,
10632                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10633                 // from `maxNativeZoom` level and auto-scaled.
10634                 maxNativeZoom: undefined,
10635
10636                 // @option minNativeZoom: Number = undefined
10637                 // Minimum zoom number the tile source has available. If it is specified,
10638                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10639                 // from `minNativeZoom` level and auto-scaled.
10640                 minNativeZoom: undefined,
10641
10642                 // @option noWrap: Boolean = false
10643                 // Whether the layer is wrapped around the antimeridian. If `true`, the
10644                 // GridLayer will only be displayed once at low zoom levels. Has no
10645                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10646                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10647                 // tiles outside the CRS limits.
10648                 noWrap: false,
10649
10650                 // @option pane: String = 'tilePane'
10651                 // `Map pane` where the grid layer will be added.
10652                 pane: 'tilePane',
10653
10654                 // @option className: String = ''
10655                 // A custom class name to assign to the tile layer. Empty by default.
10656                 className: '',
10657
10658                 // @option keepBuffer: Number = 2
10659                 // When panning the map, keep this many rows and columns of tiles before unloading them.
10660                 keepBuffer: 2
10661         },
10662
10663         initialize: function (options) {
10664                 setOptions(this, options);
10665         },
10666
10667         onAdd: function () {
10668                 this._initContainer();
10669
10670                 this._levels = {};
10671                 this._tiles = {};
10672
10673                 this._resetView();
10674                 this._update();
10675         },
10676
10677         beforeAdd: function (map) {
10678                 map._addZoomLimit(this);
10679         },
10680
10681         onRemove: function (map) {
10682                 this._removeAllTiles();
10683                 remove(this._container);
10684                 map._removeZoomLimit(this);
10685                 this._container = null;
10686                 this._tileZoom = undefined;
10687         },
10688
10689         // @method bringToFront: this
10690         // Brings the tile layer to the top of all tile layers.
10691         bringToFront: function () {
10692                 if (this._map) {
10693                         toFront(this._container);
10694                         this._setAutoZIndex(Math.max);
10695                 }
10696                 return this;
10697         },
10698
10699         // @method bringToBack: this
10700         // Brings the tile layer to the bottom of all tile layers.
10701         bringToBack: function () {
10702                 if (this._map) {
10703                         toBack(this._container);
10704                         this._setAutoZIndex(Math.min);
10705                 }
10706                 return this;
10707         },
10708
10709         // @method getContainer: HTMLElement
10710         // Returns the HTML element that contains the tiles for this layer.
10711         getContainer: function () {
10712                 return this._container;
10713         },
10714
10715         // @method setOpacity(opacity: Number): this
10716         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10717         setOpacity: function (opacity) {
10718                 this.options.opacity = opacity;
10719                 this._updateOpacity();
10720                 return this;
10721         },
10722
10723         // @method setZIndex(zIndex: Number): this
10724         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10725         setZIndex: function (zIndex) {
10726                 this.options.zIndex = zIndex;
10727                 this._updateZIndex();
10728
10729                 return this;
10730         },
10731
10732         // @method isLoading: Boolean
10733         // Returns `true` if any tile in the grid layer has not finished loading.
10734         isLoading: function () {
10735                 return this._loading;
10736         },
10737
10738         // @method redraw: this
10739         // Causes the layer to clear all the tiles and request them again.
10740         redraw: function () {
10741                 if (this._map) {
10742                         this._removeAllTiles();
10743                         this._update();
10744                 }
10745                 return this;
10746         },
10747
10748         getEvents: function () {
10749                 var events = {
10750                         viewprereset: this._invalidateAll,
10751                         viewreset: this._resetView,
10752                         zoom: this._resetView,
10753                         moveend: this._onMoveEnd
10754                 };
10755
10756                 if (!this.options.updateWhenIdle) {
10757                         // update tiles on move, but not more often than once per given interval
10758                         if (!this._onMove) {
10759                                 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10760                         }
10761
10762                         events.move = this._onMove;
10763                 }
10764
10765                 if (this._zoomAnimated) {
10766                         events.zoomanim = this._animateZoom;
10767                 }
10768
10769                 return events;
10770         },
10771
10772         // @section Extension methods
10773         // Layers extending `GridLayer` shall reimplement the following method.
10774         // @method createTile(coords: Object, done?: Function): HTMLElement
10775         // Called only internally, must be overridden by classes extending `GridLayer`.
10776         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10777         // is specified, it must be called when the tile has finished loading and drawing.
10778         createTile: function () {
10779                 return document.createElement('div');
10780         },
10781
10782         // @section
10783         // @method getTileSize: Point
10784         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10785         getTileSize: function () {
10786                 var s = this.options.tileSize;
10787                 return s instanceof Point ? s : new Point(s, s);
10788         },
10789
10790         _updateZIndex: function () {
10791                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10792                         this._container.style.zIndex = this.options.zIndex;
10793                 }
10794         },
10795
10796         _setAutoZIndex: function (compare) {
10797                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10798
10799                 var layers = this.getPane().children,
10800                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10801
10802                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10803
10804                         zIndex = layers[i].style.zIndex;
10805
10806                         if (layers[i] !== this._container && zIndex) {
10807                                 edgeZIndex = compare(edgeZIndex, +zIndex);
10808                         }
10809                 }
10810
10811                 if (isFinite(edgeZIndex)) {
10812                         this.options.zIndex = edgeZIndex + compare(-1, 1);
10813                         this._updateZIndex();
10814                 }
10815         },
10816
10817         _updateOpacity: function () {
10818                 if (!this._map) { return; }
10819
10820                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10821                 if (ielt9) { return; }
10822
10823                 setOpacity(this._container, this.options.opacity);
10824
10825                 var now = +new Date(),
10826                     nextFrame = false,
10827                     willPrune = false;
10828
10829                 for (var key in this._tiles) {
10830                         var tile = this._tiles[key];
10831                         if (!tile.current || !tile.loaded) { continue; }
10832
10833                         var fade = Math.min(1, (now - tile.loaded) / 200);
10834
10835                         setOpacity(tile.el, fade);
10836                         if (fade < 1) {
10837                                 nextFrame = true;
10838                         } else {
10839                                 if (tile.active) {
10840                                         willPrune = true;
10841                                 } else {
10842                                         this._onOpaqueTile(tile);
10843                                 }
10844                                 tile.active = true;
10845                         }
10846                 }
10847
10848                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10849
10850                 if (nextFrame) {
10851                         cancelAnimFrame(this._fadeFrame);
10852                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10853                 }
10854         },
10855
10856         _onOpaqueTile: falseFn,
10857
10858         _initContainer: function () {
10859                 if (this._container) { return; }
10860
10861                 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10862                 this._updateZIndex();
10863
10864                 if (this.options.opacity < 1) {
10865                         this._updateOpacity();
10866                 }
10867
10868                 this.getPane().appendChild(this._container);
10869         },
10870
10871         _updateLevels: function () {
10872
10873                 var zoom = this._tileZoom,
10874                     maxZoom = this.options.maxZoom;
10875
10876                 if (zoom === undefined) { return undefined; }
10877
10878                 for (var z in this._levels) {
10879                         if (this._levels[z].el.children.length || z === zoom) {
10880                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10881                                 this._onUpdateLevel(z);
10882                         } else {
10883                                 remove(this._levels[z].el);
10884                                 this._removeTilesAtZoom(z);
10885                                 this._onRemoveLevel(z);
10886                                 delete this._levels[z];
10887                         }
10888                 }
10889
10890                 var level = this._levels[zoom],
10891                     map = this._map;
10892
10893                 if (!level) {
10894                         level = this._levels[zoom] = {};
10895
10896                         level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10897                         level.el.style.zIndex = maxZoom;
10898
10899                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10900                         level.zoom = zoom;
10901
10902                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
10903
10904                         // force the browser to consider the newly added element for transition
10905                         falseFn(level.el.offsetWidth);
10906
10907                         this._onCreateLevel(level);
10908                 }
10909
10910                 this._level = level;
10911
10912                 return level;
10913         },
10914
10915         _onUpdateLevel: falseFn,
10916
10917         _onRemoveLevel: falseFn,
10918
10919         _onCreateLevel: falseFn,
10920
10921         _pruneTiles: function () {
10922                 if (!this._map) {
10923                         return;
10924                 }
10925
10926                 var key, tile;
10927
10928                 var zoom = this._map.getZoom();
10929                 if (zoom > this.options.maxZoom ||
10930                         zoom < this.options.minZoom) {
10931                         this._removeAllTiles();
10932                         return;
10933                 }
10934
10935                 for (key in this._tiles) {
10936                         tile = this._tiles[key];
10937                         tile.retain = tile.current;
10938                 }
10939
10940                 for (key in this._tiles) {
10941                         tile = this._tiles[key];
10942                         if (tile.current && !tile.active) {
10943                                 var coords = tile.coords;
10944                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10945                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10946                                 }
10947                         }
10948                 }
10949
10950                 for (key in this._tiles) {
10951                         if (!this._tiles[key].retain) {
10952                                 this._removeTile(key);
10953                         }
10954                 }
10955         },
10956
10957         _removeTilesAtZoom: function (zoom) {
10958                 for (var key in this._tiles) {
10959                         if (this._tiles[key].coords.z !== zoom) {
10960                                 continue;
10961                         }
10962                         this._removeTile(key);
10963                 }
10964         },
10965
10966         _removeAllTiles: function () {
10967                 for (var key in this._tiles) {
10968                         this._removeTile(key);
10969                 }
10970         },
10971
10972         _invalidateAll: function () {
10973                 for (var z in this._levels) {
10974                         remove(this._levels[z].el);
10975                         this._onRemoveLevel(z);
10976                         delete this._levels[z];
10977                 }
10978                 this._removeAllTiles();
10979
10980                 this._tileZoom = undefined;
10981         },
10982
10983         _retainParent: function (x, y, z, minZoom) {
10984                 var x2 = Math.floor(x / 2),
10985                     y2 = Math.floor(y / 2),
10986                     z2 = z - 1,
10987                     coords2 = new Point(+x2, +y2);
10988                 coords2.z = +z2;
10989
10990                 var key = this._tileCoordsToKey(coords2),
10991                     tile = this._tiles[key];
10992
10993                 if (tile && tile.active) {
10994                         tile.retain = true;
10995                         return true;
10996
10997                 } else if (tile && tile.loaded) {
10998                         tile.retain = true;
10999                 }
11000
11001                 if (z2 > minZoom) {
11002                         return this._retainParent(x2, y2, z2, minZoom);
11003                 }
11004
11005                 return false;
11006         },
11007
11008         _retainChildren: function (x, y, z, maxZoom) {
11009
11010                 for (var i = 2 * x; i < 2 * x + 2; i++) {
11011                         for (var j = 2 * y; j < 2 * y + 2; j++) {
11012
11013                                 var coords = new Point(i, j);
11014                                 coords.z = z + 1;
11015
11016                                 var key = this._tileCoordsToKey(coords),
11017                                     tile = this._tiles[key];
11018
11019                                 if (tile && tile.active) {
11020                                         tile.retain = true;
11021                                         continue;
11022
11023                                 } else if (tile && tile.loaded) {
11024                                         tile.retain = true;
11025                                 }
11026
11027                                 if (z + 1 < maxZoom) {
11028                                         this._retainChildren(i, j, z + 1, maxZoom);
11029                                 }
11030                         }
11031                 }
11032         },
11033
11034         _resetView: function (e) {
11035                 var animating = e && (e.pinch || e.flyTo);
11036                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11037         },
11038
11039         _animateZoom: function (e) {
11040                 this._setView(e.center, e.zoom, true, e.noUpdate);
11041         },
11042
11043         _clampZoom: function (zoom) {
11044                 var options = this.options;
11045
11046                 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11047                         return options.minNativeZoom;
11048                 }
11049
11050                 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11051                         return options.maxNativeZoom;
11052                 }
11053
11054                 return zoom;
11055         },
11056
11057         _setView: function (center, zoom, noPrune, noUpdate) {
11058                 var tileZoom = this._clampZoom(Math.round(zoom));
11059                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11060                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11061                         tileZoom = undefined;
11062                 }
11063
11064                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11065
11066                 if (!noUpdate || tileZoomChanged) {
11067
11068                         this._tileZoom = tileZoom;
11069
11070                         if (this._abortLoading) {
11071                                 this._abortLoading();
11072                         }
11073
11074                         this._updateLevels();
11075                         this._resetGrid();
11076
11077                         if (tileZoom !== undefined) {
11078                                 this._update(center);
11079                         }
11080
11081                         if (!noPrune) {
11082                                 this._pruneTiles();
11083                         }
11084
11085                         // Flag to prevent _updateOpacity from pruning tiles during
11086                         // a zoom anim or a pinch gesture
11087                         this._noPrune = !!noPrune;
11088                 }
11089
11090                 this._setZoomTransforms(center, zoom);
11091         },
11092
11093         _setZoomTransforms: function (center, zoom) {
11094                 for (var i in this._levels) {
11095                         this._setZoomTransform(this._levels[i], center, zoom);
11096                 }
11097         },
11098
11099         _setZoomTransform: function (level, center, zoom) {
11100                 var scale = this._map.getZoomScale(zoom, level.zoom),
11101                     translate = level.origin.multiplyBy(scale)
11102                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11103
11104                 if (any3d) {
11105                         setTransform(level.el, translate, scale);
11106                 } else {
11107                         setPosition(level.el, translate);
11108                 }
11109         },
11110
11111         _resetGrid: function () {
11112                 var map = this._map,
11113                     crs = map.options.crs,
11114                     tileSize = this._tileSize = this.getTileSize(),
11115                     tileZoom = this._tileZoom;
11116
11117                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11118                 if (bounds) {
11119                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
11120                 }
11121
11122                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11123                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11124                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11125                 ];
11126                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11127                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11128                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11129                 ];
11130         },
11131
11132         _onMoveEnd: function () {
11133                 if (!this._map || this._map._animatingZoom) { return; }
11134
11135                 this._update();
11136         },
11137
11138         _getTiledPixelBounds: function (center) {
11139                 var map = this._map,
11140                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11141                     scale = map.getZoomScale(mapZoom, this._tileZoom),
11142                     pixelCenter = map.project(center, this._tileZoom).floor(),
11143                     halfSize = map.getSize().divideBy(scale * 2);
11144
11145                 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11146         },
11147
11148         // Private method to load tiles in the grid's active zoom level according to map bounds
11149         _update: function (center) {
11150                 var map = this._map;
11151                 if (!map) { return; }
11152                 var zoom = this._clampZoom(map.getZoom());
11153
11154                 if (center === undefined) { center = map.getCenter(); }
11155                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
11156
11157                 var pixelBounds = this._getTiledPixelBounds(center),
11158                     tileRange = this._pxBoundsToTileRange(pixelBounds),
11159                     tileCenter = tileRange.getCenter(),
11160                     queue = [],
11161                     margin = this.options.keepBuffer,
11162                     noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11163                                               tileRange.getTopRight().add([margin, -margin]));
11164
11165                 // Sanity check: panic if the tile range contains Infinity somewhere.
11166                 if (!(isFinite(tileRange.min.x) &&
11167                       isFinite(tileRange.min.y) &&
11168                       isFinite(tileRange.max.x) &&
11169                       isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11170
11171                 for (var key in this._tiles) {
11172                         var c = this._tiles[key].coords;
11173                         if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11174                                 this._tiles[key].current = false;
11175                         }
11176                 }
11177
11178                 // _update just loads more tiles. If the tile zoom level differs too much
11179                 // from the map's, let _setView reset levels and prune old tiles.
11180                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11181
11182                 // create a queue of coordinates to load tiles from
11183                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11184                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11185                                 var coords = new Point(i, j);
11186                                 coords.z = this._tileZoom;
11187
11188                                 if (!this._isValidTile(coords)) { continue; }
11189
11190                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
11191                                 if (tile) {
11192                                         tile.current = true;
11193                                 } else {
11194                                         queue.push(coords);
11195                                 }
11196                         }
11197                 }
11198
11199                 // sort tile queue to load tiles in order of their distance to center
11200                 queue.sort(function (a, b) {
11201                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11202                 });
11203
11204                 if (queue.length !== 0) {
11205                         // if it's the first batch of tiles to load
11206                         if (!this._loading) {
11207                                 this._loading = true;
11208                                 // @event loading: Event
11209                                 // Fired when the grid layer starts loading tiles.
11210                                 this.fire('loading');
11211                         }
11212
11213                         // create DOM fragment to append tiles in one batch
11214                         var fragment = document.createDocumentFragment();
11215
11216                         for (i = 0; i < queue.length; i++) {
11217                                 this._addTile(queue[i], fragment);
11218                         }
11219
11220                         this._level.el.appendChild(fragment);
11221                 }
11222         },
11223
11224         _isValidTile: function (coords) {
11225                 var crs = this._map.options.crs;
11226
11227                 if (!crs.infinite) {
11228                         // don't load tile if it's out of bounds and not wrapped
11229                         var bounds = this._globalTileRange;
11230                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11231                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11232                 }
11233
11234                 if (!this.options.bounds) { return true; }
11235
11236                 // don't load tile if it doesn't intersect the bounds in options
11237                 var tileBounds = this._tileCoordsToBounds(coords);
11238                 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11239         },
11240
11241         _keyToBounds: function (key) {
11242                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11243         },
11244
11245         _tileCoordsToNwSe: function (coords) {
11246                 var map = this._map,
11247                     tileSize = this.getTileSize(),
11248                     nwPoint = coords.scaleBy(tileSize),
11249                     sePoint = nwPoint.add(tileSize),
11250                     nw = map.unproject(nwPoint, coords.z),
11251                     se = map.unproject(sePoint, coords.z);
11252                 return [nw, se];
11253         },
11254
11255         // converts tile coordinates to its geographical bounds
11256         _tileCoordsToBounds: function (coords) {
11257                 var bp = this._tileCoordsToNwSe(coords),
11258                     bounds = new LatLngBounds(bp[0], bp[1]);
11259
11260                 if (!this.options.noWrap) {
11261                         bounds = this._map.wrapLatLngBounds(bounds);
11262                 }
11263                 return bounds;
11264         },
11265         // converts tile coordinates to key for the tile cache
11266         _tileCoordsToKey: function (coords) {
11267                 return coords.x + ':' + coords.y + ':' + coords.z;
11268         },
11269
11270         // converts tile cache key to coordinates
11271         _keyToTileCoords: function (key) {
11272                 var k = key.split(':'),
11273                     coords = new Point(+k[0], +k[1]);
11274                 coords.z = +k[2];
11275                 return coords;
11276         },
11277
11278         _removeTile: function (key) {
11279                 var tile = this._tiles[key];
11280                 if (!tile) { return; }
11281
11282                 remove(tile.el);
11283
11284                 delete this._tiles[key];
11285
11286                 // @event tileunload: TileEvent
11287                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11288                 this.fire('tileunload', {
11289                         tile: tile.el,
11290                         coords: this._keyToTileCoords(key)
11291                 });
11292         },
11293
11294         _initTile: function (tile) {
11295                 addClass(tile, 'leaflet-tile');
11296
11297                 var tileSize = this.getTileSize();
11298                 tile.style.width = tileSize.x + 'px';
11299                 tile.style.height = tileSize.y + 'px';
11300
11301                 tile.onselectstart = falseFn;
11302                 tile.onmousemove = falseFn;
11303
11304                 // update opacity on tiles in IE7-8 because of filter inheritance problems
11305                 if (ielt9 && this.options.opacity < 1) {
11306                         setOpacity(tile, this.options.opacity);
11307                 }
11308
11309                 // without this hack, tiles disappear after zoom on Chrome for Android
11310                 // https://github.com/Leaflet/Leaflet/issues/2078
11311                 if (android && !android23) {
11312                         tile.style.WebkitBackfaceVisibility = 'hidden';
11313                 }
11314         },
11315
11316         _addTile: function (coords, container) {
11317                 var tilePos = this._getTilePos(coords),
11318                     key = this._tileCoordsToKey(coords);
11319
11320                 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11321
11322                 this._initTile(tile);
11323
11324                 // if createTile is defined with a second argument ("done" callback),
11325                 // we know that tile is async and will be ready later; otherwise
11326                 if (this.createTile.length < 2) {
11327                         // mark tile as ready, but delay one frame for opacity animation to happen
11328                         requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11329                 }
11330
11331                 setPosition(tile, tilePos);
11332
11333                 // save tile in cache
11334                 this._tiles[key] = {
11335                         el: tile,
11336                         coords: coords,
11337                         current: true
11338                 };
11339
11340                 container.appendChild(tile);
11341                 // @event tileloadstart: TileEvent
11342                 // Fired when a tile is requested and starts loading.
11343                 this.fire('tileloadstart', {
11344                         tile: tile,
11345                         coords: coords
11346                 });
11347         },
11348
11349         _tileReady: function (coords, err, tile) {
11350                 if (err) {
11351                         // @event tileerror: TileErrorEvent
11352                         // Fired when there is an error loading a tile.
11353                         this.fire('tileerror', {
11354                                 error: err,
11355                                 tile: tile,
11356                                 coords: coords
11357                         });
11358                 }
11359
11360                 var key = this._tileCoordsToKey(coords);
11361
11362                 tile = this._tiles[key];
11363                 if (!tile) { return; }
11364
11365                 tile.loaded = +new Date();
11366                 if (this._map._fadeAnimated) {
11367                         setOpacity(tile.el, 0);
11368                         cancelAnimFrame(this._fadeFrame);
11369                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11370                 } else {
11371                         tile.active = true;
11372                         this._pruneTiles();
11373                 }
11374
11375                 if (!err) {
11376                         addClass(tile.el, 'leaflet-tile-loaded');
11377
11378                         // @event tileload: TileEvent
11379                         // Fired when a tile loads.
11380                         this.fire('tileload', {
11381                                 tile: tile.el,
11382                                 coords: coords
11383                         });
11384                 }
11385
11386                 if (this._noTilesToLoad()) {
11387                         this._loading = false;
11388                         // @event load: Event
11389                         // Fired when the grid layer loaded all visible tiles.
11390                         this.fire('load');
11391
11392                         if (ielt9 || !this._map._fadeAnimated) {
11393                                 requestAnimFrame(this._pruneTiles, this);
11394                         } else {
11395                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11396                                 // to trigger a pruning.
11397                                 setTimeout(bind(this._pruneTiles, this), 250);
11398                         }
11399                 }
11400         },
11401
11402         _getTilePos: function (coords) {
11403                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11404         },
11405
11406         _wrapCoords: function (coords) {
11407                 var newCoords = new Point(
11408                         this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11409                         this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11410                 newCoords.z = coords.z;
11411                 return newCoords;
11412         },
11413
11414         _pxBoundsToTileRange: function (bounds) {
11415                 var tileSize = this.getTileSize();
11416                 return new Bounds(
11417                         bounds.min.unscaleBy(tileSize).floor(),
11418                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11419         },
11420
11421         _noTilesToLoad: function () {
11422                 for (var key in this._tiles) {
11423                         if (!this._tiles[key].loaded) { return false; }
11424                 }
11425                 return true;
11426         }
11427 });
11428
11429 // @factory L.gridLayer(options?: GridLayer options)
11430 // Creates a new instance of GridLayer with the supplied options.
11431 function gridLayer(options) {
11432         return new GridLayer(options);
11433 }
11434
11435 /*
11436  * @class TileLayer
11437  * @inherits GridLayer
11438  * @aka L.TileLayer
11439  * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
11440  *
11441  * @example
11442  *
11443  * ```js
11444  * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
11445  * ```
11446  *
11447  * @section URL template
11448  * @example
11449  *
11450  * A string of the following form:
11451  *
11452  * ```
11453  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11454  * ```
11455  *
11456  * `{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.
11457  *
11458  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11459  *
11460  * ```
11461  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11462  * ```
11463  */
11464
11465
11466 var TileLayer = GridLayer.extend({
11467
11468         // @section
11469         // @aka TileLayer options
11470         options: {
11471                 // @option minZoom: Number = 0
11472                 // The minimum zoom level down to which this layer will be displayed (inclusive).
11473                 minZoom: 0,
11474
11475                 // @option maxZoom: Number = 18
11476                 // The maximum zoom level up to which this layer will be displayed (inclusive).
11477                 maxZoom: 18,
11478
11479                 // @option subdomains: String|String[] = 'abc'
11480                 // 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.
11481                 subdomains: 'abc',
11482
11483                 // @option errorTileUrl: String = ''
11484                 // URL to the tile image to show in place of the tile that failed to load.
11485                 errorTileUrl: '',
11486
11487                 // @option zoomOffset: Number = 0
11488                 // The zoom number used in tile URLs will be offset with this value.
11489                 zoomOffset: 0,
11490
11491                 // @option tms: Boolean = false
11492                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11493                 tms: false,
11494
11495                 // @option zoomReverse: Boolean = false
11496                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11497                 zoomReverse: false,
11498
11499                 // @option detectRetina: Boolean = false
11500                 // 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.
11501                 detectRetina: false,
11502
11503                 // @option crossOrigin: Boolean|String = false
11504                 // Whether the crossOrigin attribute will be added to the tiles.
11505                 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
11506                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11507                 crossOrigin: false
11508         },
11509
11510         initialize: function (url, options) {
11511
11512                 this._url = url;
11513
11514                 options = setOptions(this, options);
11515
11516                 // detecting retina displays, adjusting tileSize and zoom levels
11517                 if (options.detectRetina && retina && options.maxZoom > 0) {
11518
11519                         options.tileSize = Math.floor(options.tileSize / 2);
11520
11521                         if (!options.zoomReverse) {
11522                                 options.zoomOffset++;
11523                                 options.maxZoom--;
11524                         } else {
11525                                 options.zoomOffset--;
11526                                 options.minZoom++;
11527                         }
11528
11529                         options.minZoom = Math.max(0, options.minZoom);
11530                 }
11531
11532                 if (typeof options.subdomains === 'string') {
11533                         options.subdomains = options.subdomains.split('');
11534                 }
11535
11536                 // for https://github.com/Leaflet/Leaflet/issues/137
11537                 if (!android) {
11538                         this.on('tileunload', this._onTileRemove);
11539                 }
11540         },
11541
11542         // @method setUrl(url: String, noRedraw?: Boolean): this
11543         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11544         // If the URL does not change, the layer will not be redrawn unless
11545         // the noRedraw parameter is set to false.
11546         setUrl: function (url, noRedraw) {
11547                 if (this._url === url && noRedraw === undefined) {
11548                         noRedraw = true;
11549                 }
11550
11551                 this._url = url;
11552
11553                 if (!noRedraw) {
11554                         this.redraw();
11555                 }
11556                 return this;
11557         },
11558
11559         // @method createTile(coords: Object, done?: Function): HTMLElement
11560         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11561         // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11562         // callback is called when the tile has been loaded.
11563         createTile: function (coords, done) {
11564                 var tile = document.createElement('img');
11565
11566                 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11567                 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11568
11569                 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11570                         tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11571                 }
11572
11573                 /*
11574                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11575                  http://www.w3.org/TR/WCAG20-TECHS/H67
11576                 */
11577                 tile.alt = '';
11578
11579                 /*
11580                  Set role="presentation" to force screen readers to ignore this
11581                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11582                 */
11583                 tile.setAttribute('role', 'presentation');
11584
11585                 tile.src = this.getTileUrl(coords);
11586
11587                 return tile;
11588         },
11589
11590         // @section Extension methods
11591         // @uninheritable
11592         // Layers extending `TileLayer` might reimplement the following method.
11593         // @method getTileUrl(coords: Object): String
11594         // Called only internally, returns the URL for a tile given its coordinates.
11595         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11596         getTileUrl: function (coords) {
11597                 var data = {
11598                         r: retina ? '@2x' : '',
11599                         s: this._getSubdomain(coords),
11600                         x: coords.x,
11601                         y: coords.y,
11602                         z: this._getZoomForUrl()
11603                 };
11604                 if (this._map && !this._map.options.crs.infinite) {
11605                         var invertedY = this._globalTileRange.max.y - coords.y;
11606                         if (this.options.tms) {
11607                                 data['y'] = invertedY;
11608                         }
11609                         data['-y'] = invertedY;
11610                 }
11611
11612                 return template(this._url, extend(data, this.options));
11613         },
11614
11615         _tileOnLoad: function (done, tile) {
11616                 // For https://github.com/Leaflet/Leaflet/issues/3332
11617                 if (ielt9) {
11618                         setTimeout(bind(done, this, null, tile), 0);
11619                 } else {
11620                         done(null, tile);
11621                 }
11622         },
11623
11624         _tileOnError: function (done, tile, e) {
11625                 var errorUrl = this.options.errorTileUrl;
11626                 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11627                         tile.src = errorUrl;
11628                 }
11629                 done(e, tile);
11630         },
11631
11632         _onTileRemove: function (e) {
11633                 e.tile.onload = null;
11634         },
11635
11636         _getZoomForUrl: function () {
11637                 var zoom = this._tileZoom,
11638                 maxZoom = this.options.maxZoom,
11639                 zoomReverse = this.options.zoomReverse,
11640                 zoomOffset = this.options.zoomOffset;
11641
11642                 if (zoomReverse) {
11643                         zoom = maxZoom - zoom;
11644                 }
11645
11646                 return zoom + zoomOffset;
11647         },
11648
11649         _getSubdomain: function (tilePoint) {
11650                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11651                 return this.options.subdomains[index];
11652         },
11653
11654         // stops loading all tiles in the background layer
11655         _abortLoading: function () {
11656                 var i, tile;
11657                 for (i in this._tiles) {
11658                         if (this._tiles[i].coords.z !== this._tileZoom) {
11659                                 tile = this._tiles[i].el;
11660
11661                                 tile.onload = falseFn;
11662                                 tile.onerror = falseFn;
11663
11664                                 if (!tile.complete) {
11665                                         tile.src = emptyImageUrl;
11666                                         remove(tile);
11667                                         delete this._tiles[i];
11668                                 }
11669                         }
11670                 }
11671         },
11672
11673         _removeTile: function (key) {
11674                 var tile = this._tiles[key];
11675                 if (!tile) { return; }
11676
11677                 // Cancels any pending http requests associated with the tile
11678                 // unless we're on Android's stock browser,
11679                 // see https://github.com/Leaflet/Leaflet/issues/137
11680                 if (!androidStock) {
11681                         tile.el.setAttribute('src', emptyImageUrl);
11682                 }
11683
11684                 return GridLayer.prototype._removeTile.call(this, key);
11685         },
11686
11687         _tileReady: function (coords, err, tile) {
11688                 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11689                         return;
11690                 }
11691
11692                 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11693         }
11694 });
11695
11696
11697 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11698 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11699
11700 function tileLayer(url, options) {
11701         return new TileLayer(url, options);
11702 }
11703
11704 /*
11705  * @class TileLayer.WMS
11706  * @inherits TileLayer
11707  * @aka L.TileLayer.WMS
11708  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11709  *
11710  * @example
11711  *
11712  * ```js
11713  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11714  *      layers: 'nexrad-n0r-900913',
11715  *      format: 'image/png',
11716  *      transparent: true,
11717  *      attribution: "Weather data © 2012 IEM Nexrad"
11718  * });
11719  * ```
11720  */
11721
11722 var TileLayerWMS = TileLayer.extend({
11723
11724         // @section
11725         // @aka TileLayer.WMS options
11726         // If any custom options not documented here are used, they will be sent to the
11727         // WMS server as extra parameters in each request URL. This can be useful for
11728         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11729         defaultWmsParams: {
11730                 service: 'WMS',
11731                 request: 'GetMap',
11732
11733                 // @option layers: String = ''
11734                 // **(required)** Comma-separated list of WMS layers to show.
11735                 layers: '',
11736
11737                 // @option styles: String = ''
11738                 // Comma-separated list of WMS styles.
11739                 styles: '',
11740
11741                 // @option format: String = 'image/jpeg'
11742                 // WMS image format (use `'image/png'` for layers with transparency).
11743                 format: 'image/jpeg',
11744
11745                 // @option transparent: Boolean = false
11746                 // If `true`, the WMS service will return images with transparency.
11747                 transparent: false,
11748
11749                 // @option version: String = '1.1.1'
11750                 // Version of the WMS service to use
11751                 version: '1.1.1'
11752         },
11753
11754         options: {
11755                 // @option crs: CRS = null
11756                 // Coordinate Reference System to use for the WMS requests, defaults to
11757                 // map CRS. Don't change this if you're not sure what it means.
11758                 crs: null,
11759
11760                 // @option uppercase: Boolean = false
11761                 // If `true`, WMS request parameter keys will be uppercase.
11762                 uppercase: false
11763         },
11764
11765         initialize: function (url, options) {
11766
11767                 this._url = url;
11768
11769                 var wmsParams = extend({}, this.defaultWmsParams);
11770
11771                 // all keys that are not TileLayer options go to WMS params
11772                 for (var i in options) {
11773                         if (!(i in this.options)) {
11774                                 wmsParams[i] = options[i];
11775                         }
11776                 }
11777
11778                 options = setOptions(this, options);
11779
11780                 var realRetina = options.detectRetina && retina ? 2 : 1;
11781                 var tileSize = this.getTileSize();
11782                 wmsParams.width = tileSize.x * realRetina;
11783                 wmsParams.height = tileSize.y * realRetina;
11784
11785                 this.wmsParams = wmsParams;
11786         },
11787
11788         onAdd: function (map) {
11789
11790                 this._crs = this.options.crs || map.options.crs;
11791                 this._wmsVersion = parseFloat(this.wmsParams.version);
11792
11793                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11794                 this.wmsParams[projectionKey] = this._crs.code;
11795
11796                 TileLayer.prototype.onAdd.call(this, map);
11797         },
11798
11799         getTileUrl: function (coords) {
11800
11801                 var tileBounds = this._tileCoordsToNwSe(coords),
11802                     crs = this._crs,
11803                     bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11804                     min = bounds.min,
11805                     max = bounds.max,
11806                     bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11807                     [min.y, min.x, max.y, max.x] :
11808                     [min.x, min.y, max.x, max.y]).join(','),
11809                     url = TileLayer.prototype.getTileUrl.call(this, coords);
11810                 return url +
11811                         getParamString(this.wmsParams, url, this.options.uppercase) +
11812                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11813         },
11814
11815         // @method setParams(params: Object, noRedraw?: Boolean): this
11816         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11817         setParams: function (params, noRedraw) {
11818
11819                 extend(this.wmsParams, params);
11820
11821                 if (!noRedraw) {
11822                         this.redraw();
11823                 }
11824
11825                 return this;
11826         }
11827 });
11828
11829
11830 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11831 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11832 function tileLayerWMS(url, options) {
11833         return new TileLayerWMS(url, options);
11834 }
11835
11836 TileLayer.WMS = TileLayerWMS;
11837 tileLayer.wms = tileLayerWMS;
11838
11839 /*
11840  * @class Renderer
11841  * @inherits Layer
11842  * @aka L.Renderer
11843  *
11844  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11845  * DOM container of the renderer, its bounds, and its zoom animation.
11846  *
11847  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11848  * itself can be added or removed to the map. All paths use a renderer, which can
11849  * be implicit (the map will decide the type of renderer and use it automatically)
11850  * or explicit (using the [`renderer`](#path-renderer) option of the path).
11851  *
11852  * Do not use this class directly, use `SVG` and `Canvas` instead.
11853  *
11854  * @event update: Event
11855  * Fired when the renderer updates its bounds, center and zoom, for example when
11856  * its map has moved
11857  */
11858
11859 var Renderer = Layer.extend({
11860
11861         // @section
11862         // @aka Renderer options
11863         options: {
11864                 // @option padding: Number = 0.1
11865                 // How much to extend the clip area around the map view (relative to its size)
11866                 // e.g. 0.1 would be 10% of map view in each direction
11867                 padding: 0.1,
11868
11869                 // @option tolerance: Number = 0
11870                 // How much to extend click tolerance round a path/object on the map
11871                 tolerance : 0
11872         },
11873
11874         initialize: function (options) {
11875                 setOptions(this, options);
11876                 stamp(this);
11877                 this._layers = this._layers || {};
11878         },
11879
11880         onAdd: function () {
11881                 if (!this._container) {
11882                         this._initContainer(); // defined by renderer implementations
11883
11884                         if (this._zoomAnimated) {
11885                                 addClass(this._container, 'leaflet-zoom-animated');
11886                         }
11887                 }
11888
11889                 this.getPane().appendChild(this._container);
11890                 this._update();
11891                 this.on('update', this._updatePaths, this);
11892         },
11893
11894         onRemove: function () {
11895                 this.off('update', this._updatePaths, this);
11896                 this._destroyContainer();
11897         },
11898
11899         getEvents: function () {
11900                 var events = {
11901                         viewreset: this._reset,
11902                         zoom: this._onZoom,
11903                         moveend: this._update,
11904                         zoomend: this._onZoomEnd
11905                 };
11906                 if (this._zoomAnimated) {
11907                         events.zoomanim = this._onAnimZoom;
11908                 }
11909                 return events;
11910         },
11911
11912         _onAnimZoom: function (ev) {
11913                 this._updateTransform(ev.center, ev.zoom);
11914         },
11915
11916         _onZoom: function () {
11917                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11918         },
11919
11920         _updateTransform: function (center, zoom) {
11921                 var scale = this._map.getZoomScale(zoom, this._zoom),
11922                     position = getPosition(this._container),
11923                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11924                     currentCenterPoint = this._map.project(this._center, zoom),
11925                     destCenterPoint = this._map.project(center, zoom),
11926                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
11927
11928                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11929
11930                 if (any3d) {
11931                         setTransform(this._container, topLeftOffset, scale);
11932                 } else {
11933                         setPosition(this._container, topLeftOffset);
11934                 }
11935         },
11936
11937         _reset: function () {
11938                 this._update();
11939                 this._updateTransform(this._center, this._zoom);
11940
11941                 for (var id in this._layers) {
11942                         this._layers[id]._reset();
11943                 }
11944         },
11945
11946         _onZoomEnd: function () {
11947                 for (var id in this._layers) {
11948                         this._layers[id]._project();
11949                 }
11950         },
11951
11952         _updatePaths: function () {
11953                 for (var id in this._layers) {
11954                         this._layers[id]._update();
11955                 }
11956         },
11957
11958         _update: function () {
11959                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11960                 // Subclasses are responsible of firing the 'update' event.
11961                 var p = this.options.padding,
11962                     size = this._map.getSize(),
11963                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11964
11965                 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11966
11967                 this._center = this._map.getCenter();
11968                 this._zoom = this._map.getZoom();
11969         }
11970 });
11971
11972 /*
11973  * @class Canvas
11974  * @inherits Renderer
11975  * @aka L.Canvas
11976  *
11977  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11978  * Inherits `Renderer`.
11979  *
11980  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11981  * available in all web browsers, notably IE8, and overlapping geometries might
11982  * not display properly in some edge cases.
11983  *
11984  * @example
11985  *
11986  * Use Canvas by default for all paths in the map:
11987  *
11988  * ```js
11989  * var map = L.map('map', {
11990  *      renderer: L.canvas()
11991  * });
11992  * ```
11993  *
11994  * Use a Canvas renderer with extra padding for specific vector geometries:
11995  *
11996  * ```js
11997  * var map = L.map('map');
11998  * var myRenderer = L.canvas({ padding: 0.5 });
11999  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12000  * var circle = L.circle( center, { renderer: myRenderer } );
12001  * ```
12002  */
12003
12004 var Canvas = Renderer.extend({
12005         getEvents: function () {
12006                 var events = Renderer.prototype.getEvents.call(this);
12007                 events.viewprereset = this._onViewPreReset;
12008                 return events;
12009         },
12010
12011         _onViewPreReset: function () {
12012                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12013                 this._postponeUpdatePaths = true;
12014         },
12015
12016         onAdd: function () {
12017                 Renderer.prototype.onAdd.call(this);
12018
12019                 // Redraw vectors since canvas is cleared upon removal,
12020                 // in case of removing the renderer itself from the map.
12021                 this._draw();
12022         },
12023
12024         _initContainer: function () {
12025                 var container = this._container = document.createElement('canvas');
12026
12027                 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
12028                 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12029                 on(container, 'mouseout', this._handleMouseOut, this);
12030
12031                 this._ctx = container.getContext('2d');
12032         },
12033
12034         _destroyContainer: function () {
12035                 cancelAnimFrame(this._redrawRequest);
12036                 delete this._ctx;
12037                 remove(this._container);
12038                 off(this._container);
12039                 delete this._container;
12040         },
12041
12042         _updatePaths: function () {
12043                 if (this._postponeUpdatePaths) { return; }
12044
12045                 var layer;
12046                 this._redrawBounds = null;
12047                 for (var id in this._layers) {
12048                         layer = this._layers[id];
12049                         layer._update();
12050                 }
12051                 this._redraw();
12052         },
12053
12054         _update: function () {
12055                 if (this._map._animatingZoom && this._bounds) { return; }
12056
12057                 Renderer.prototype._update.call(this);
12058
12059                 var b = this._bounds,
12060                     container = this._container,
12061                     size = b.getSize(),
12062                     m = retina ? 2 : 1;
12063
12064                 setPosition(container, b.min);
12065
12066                 // set canvas size (also clearing it); use double size on retina
12067                 container.width = m * size.x;
12068                 container.height = m * size.y;
12069                 container.style.width = size.x + 'px';
12070                 container.style.height = size.y + 'px';
12071
12072                 if (retina) {
12073                         this._ctx.scale(2, 2);
12074                 }
12075
12076                 // translate so we use the same path coordinates after canvas element moves
12077                 this._ctx.translate(-b.min.x, -b.min.y);
12078
12079                 // Tell paths to redraw themselves
12080                 this.fire('update');
12081         },
12082
12083         _reset: function () {
12084                 Renderer.prototype._reset.call(this);
12085
12086                 if (this._postponeUpdatePaths) {
12087                         this._postponeUpdatePaths = false;
12088                         this._updatePaths();
12089                 }
12090         },
12091
12092         _initPath: function (layer) {
12093                 this._updateDashArray(layer);
12094                 this._layers[stamp(layer)] = layer;
12095
12096                 var order = layer._order = {
12097                         layer: layer,
12098                         prev: this._drawLast,
12099                         next: null
12100                 };
12101                 if (this._drawLast) { this._drawLast.next = order; }
12102                 this._drawLast = order;
12103                 this._drawFirst = this._drawFirst || this._drawLast;
12104         },
12105
12106         _addPath: function (layer) {
12107                 this._requestRedraw(layer);
12108         },
12109
12110         _removePath: function (layer) {
12111                 var order = layer._order;
12112                 var next = order.next;
12113                 var prev = order.prev;
12114
12115                 if (next) {
12116                         next.prev = prev;
12117                 } else {
12118                         this._drawLast = prev;
12119                 }
12120                 if (prev) {
12121                         prev.next = next;
12122                 } else {
12123                         this._drawFirst = next;
12124                 }
12125
12126                 delete layer._order;
12127
12128                 delete this._layers[stamp(layer)];
12129
12130                 this._requestRedraw(layer);
12131         },
12132
12133         _updatePath: function (layer) {
12134                 // Redraw the union of the layer's old pixel
12135                 // bounds and the new pixel bounds.
12136                 this._extendRedrawBounds(layer);
12137                 layer._project();
12138                 layer._update();
12139                 // The redraw will extend the redraw bounds
12140                 // with the new pixel bounds.
12141                 this._requestRedraw(layer);
12142         },
12143
12144         _updateStyle: function (layer) {
12145                 this._updateDashArray(layer);
12146                 this._requestRedraw(layer);
12147         },
12148
12149         _updateDashArray: function (layer) {
12150                 if (typeof layer.options.dashArray === 'string') {
12151                         var parts = layer.options.dashArray.split(/[, ]+/),
12152                             dashArray = [],
12153                             dashValue,
12154                             i;
12155                         for (i = 0; i < parts.length; i++) {
12156                                 dashValue = Number(parts[i]);
12157                                 // Ignore dash array containing invalid lengths
12158                                 if (isNaN(dashValue)) { return; }
12159                                 dashArray.push(dashValue);
12160                         }
12161                         layer.options._dashArray = dashArray;
12162                 } else {
12163                         layer.options._dashArray = layer.options.dashArray;
12164                 }
12165         },
12166
12167         _requestRedraw: function (layer) {
12168                 if (!this._map) { return; }
12169
12170                 this._extendRedrawBounds(layer);
12171                 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12172         },
12173
12174         _extendRedrawBounds: function (layer) {
12175                 if (layer._pxBounds) {
12176                         var padding = (layer.options.weight || 0) + 1;
12177                         this._redrawBounds = this._redrawBounds || new Bounds();
12178                         this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12179                         this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12180                 }
12181         },
12182
12183         _redraw: function () {
12184                 this._redrawRequest = null;
12185
12186                 if (this._redrawBounds) {
12187                         this._redrawBounds.min._floor();
12188                         this._redrawBounds.max._ceil();
12189                 }
12190
12191                 this._clear(); // clear layers in redraw bounds
12192                 this._draw(); // draw layers
12193
12194                 this._redrawBounds = null;
12195         },
12196
12197         _clear: function () {
12198                 var bounds = this._redrawBounds;
12199                 if (bounds) {
12200                         var size = bounds.getSize();
12201                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12202                 } else {
12203                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12204                 }
12205         },
12206
12207         _draw: function () {
12208                 var layer, bounds = this._redrawBounds;
12209                 this._ctx.save();
12210                 if (bounds) {
12211                         var size = bounds.getSize();
12212                         this._ctx.beginPath();
12213                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12214                         this._ctx.clip();
12215                 }
12216
12217                 this._drawing = true;
12218
12219                 for (var order = this._drawFirst; order; order = order.next) {
12220                         layer = order.layer;
12221                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12222                                 layer._updatePath();
12223                         }
12224                 }
12225
12226                 this._drawing = false;
12227
12228                 this._ctx.restore();  // Restore state before clipping.
12229         },
12230
12231         _updatePoly: function (layer, closed) {
12232                 if (!this._drawing) { return; }
12233
12234                 var i, j, len2, p,
12235                     parts = layer._parts,
12236                     len = parts.length,
12237                     ctx = this._ctx;
12238
12239                 if (!len) { return; }
12240
12241                 ctx.beginPath();
12242
12243                 for (i = 0; i < len; i++) {
12244                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
12245                                 p = parts[i][j];
12246                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12247                         }
12248                         if (closed) {
12249                                 ctx.closePath();
12250                         }
12251                 }
12252
12253                 this._fillStroke(ctx, layer);
12254
12255                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12256         },
12257
12258         _updateCircle: function (layer) {
12259
12260                 if (!this._drawing || layer._empty()) { return; }
12261
12262                 var p = layer._point,
12263                     ctx = this._ctx,
12264                     r = Math.max(Math.round(layer._radius), 1),
12265                     s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12266
12267                 if (s !== 1) {
12268                         ctx.save();
12269                         ctx.scale(1, s);
12270                 }
12271
12272                 ctx.beginPath();
12273                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12274
12275                 if (s !== 1) {
12276                         ctx.restore();
12277                 }
12278
12279                 this._fillStroke(ctx, layer);
12280         },
12281
12282         _fillStroke: function (ctx, layer) {
12283                 var options = layer.options;
12284
12285                 if (options.fill) {
12286                         ctx.globalAlpha = options.fillOpacity;
12287                         ctx.fillStyle = options.fillColor || options.color;
12288                         ctx.fill(options.fillRule || 'evenodd');
12289                 }
12290
12291                 if (options.stroke && options.weight !== 0) {
12292                         if (ctx.setLineDash) {
12293                                 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12294                         }
12295                         ctx.globalAlpha = options.opacity;
12296                         ctx.lineWidth = options.weight;
12297                         ctx.strokeStyle = options.color;
12298                         ctx.lineCap = options.lineCap;
12299                         ctx.lineJoin = options.lineJoin;
12300                         ctx.stroke();
12301                 }
12302         },
12303
12304         // Canvas obviously doesn't have mouse events for individual drawn objects,
12305         // so we emulate that by calculating what's under the mouse on mousemove/click manually
12306
12307         _onClick: function (e) {
12308                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12309
12310                 for (var order = this._drawFirst; order; order = order.next) {
12311                         layer = order.layer;
12312                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12313                                 clickedLayer = layer;
12314                         }
12315                 }
12316                 if (clickedLayer)  {
12317                         fakeStop(e);
12318                         this._fireEvent([clickedLayer], e);
12319                 }
12320         },
12321
12322         _onMouseMove: function (e) {
12323                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12324
12325                 var point = this._map.mouseEventToLayerPoint(e);
12326                 this._handleMouseHover(e, point);
12327         },
12328
12329
12330         _handleMouseOut: function (e) {
12331                 var layer = this._hoveredLayer;
12332                 if (layer) {
12333                         // if we're leaving the layer, fire mouseout
12334                         removeClass(this._container, 'leaflet-interactive');
12335                         this._fireEvent([layer], e, 'mouseout');
12336                         this._hoveredLayer = null;
12337                 }
12338         },
12339
12340         _handleMouseHover: function (e, point) {
12341                 var layer, candidateHoveredLayer;
12342
12343                 for (var order = this._drawFirst; order; order = order.next) {
12344                         layer = order.layer;
12345                         if (layer.options.interactive && layer._containsPoint(point)) {
12346                                 candidateHoveredLayer = layer;
12347                         }
12348                 }
12349
12350                 if (candidateHoveredLayer !== this._hoveredLayer) {
12351                         this._handleMouseOut(e);
12352
12353                         if (candidateHoveredLayer) {
12354                                 addClass(this._container, 'leaflet-interactive'); // change cursor
12355                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12356                                 this._hoveredLayer = candidateHoveredLayer;
12357                         }
12358                 }
12359
12360                 if (this._hoveredLayer) {
12361                         this._fireEvent([this._hoveredLayer], e);
12362                 }
12363         },
12364
12365         _fireEvent: function (layers, e, type) {
12366                 this._map._fireDOMEvent(e, type || e.type, layers);
12367         },
12368
12369         _bringToFront: function (layer) {
12370                 var order = layer._order;
12371
12372                 if (!order) { return; }
12373
12374                 var next = order.next;
12375                 var prev = order.prev;
12376
12377                 if (next) {
12378                         next.prev = prev;
12379                 } else {
12380                         // Already last
12381                         return;
12382                 }
12383                 if (prev) {
12384                         prev.next = next;
12385                 } else if (next) {
12386                         // Update first entry unless this is the
12387                         // single entry
12388                         this._drawFirst = next;
12389                 }
12390
12391                 order.prev = this._drawLast;
12392                 this._drawLast.next = order;
12393
12394                 order.next = null;
12395                 this._drawLast = order;
12396
12397                 this._requestRedraw(layer);
12398         },
12399
12400         _bringToBack: function (layer) {
12401                 var order = layer._order;
12402
12403                 if (!order) { return; }
12404
12405                 var next = order.next;
12406                 var prev = order.prev;
12407
12408                 if (prev) {
12409                         prev.next = next;
12410                 } else {
12411                         // Already first
12412                         return;
12413                 }
12414                 if (next) {
12415                         next.prev = prev;
12416                 } else if (prev) {
12417                         // Update last entry unless this is the
12418                         // single entry
12419                         this._drawLast = prev;
12420                 }
12421
12422                 order.prev = null;
12423
12424                 order.next = this._drawFirst;
12425                 this._drawFirst.prev = order;
12426                 this._drawFirst = order;
12427
12428                 this._requestRedraw(layer);
12429         }
12430 });
12431
12432 // @factory L.canvas(options?: Renderer options)
12433 // Creates a Canvas renderer with the given options.
12434 function canvas$1(options) {
12435         return canvas ? new Canvas(options) : null;
12436 }
12437
12438 /*
12439  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12440  */
12441
12442
12443 var vmlCreate = (function () {
12444         try {
12445                 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12446                 return function (name) {
12447                         return document.createElement('<lvml:' + name + ' class="lvml">');
12448                 };
12449         } catch (e) {
12450                 return function (name) {
12451                         return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12452                 };
12453         }
12454 })();
12455
12456
12457 /*
12458  * @class SVG
12459  *
12460  *
12461  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12462  * with old versions of Internet Explorer.
12463  */
12464
12465 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12466 var vmlMixin = {
12467
12468         _initContainer: function () {
12469                 this._container = create$1('div', 'leaflet-vml-container');
12470         },
12471
12472         _update: function () {
12473                 if (this._map._animatingZoom) { return; }
12474                 Renderer.prototype._update.call(this);
12475                 this.fire('update');
12476         },
12477
12478         _initPath: function (layer) {
12479                 var container = layer._container = vmlCreate('shape');
12480
12481                 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12482
12483                 container.coordsize = '1 1';
12484
12485                 layer._path = vmlCreate('path');
12486                 container.appendChild(layer._path);
12487
12488                 this._updateStyle(layer);
12489                 this._layers[stamp(layer)] = layer;
12490         },
12491
12492         _addPath: function (layer) {
12493                 var container = layer._container;
12494                 this._container.appendChild(container);
12495
12496                 if (layer.options.interactive) {
12497                         layer.addInteractiveTarget(container);
12498                 }
12499         },
12500
12501         _removePath: function (layer) {
12502                 var container = layer._container;
12503                 remove(container);
12504                 layer.removeInteractiveTarget(container);
12505                 delete this._layers[stamp(layer)];
12506         },
12507
12508         _updateStyle: function (layer) {
12509                 var stroke = layer._stroke,
12510                     fill = layer._fill,
12511                     options = layer.options,
12512                     container = layer._container;
12513
12514                 container.stroked = !!options.stroke;
12515                 container.filled = !!options.fill;
12516
12517                 if (options.stroke) {
12518                         if (!stroke) {
12519                                 stroke = layer._stroke = vmlCreate('stroke');
12520                         }
12521                         container.appendChild(stroke);
12522                         stroke.weight = options.weight + 'px';
12523                         stroke.color = options.color;
12524                         stroke.opacity = options.opacity;
12525
12526                         if (options.dashArray) {
12527                                 stroke.dashStyle = isArray(options.dashArray) ?
12528                                     options.dashArray.join(' ') :
12529                                     options.dashArray.replace(/( *, *)/g, ' ');
12530                         } else {
12531                                 stroke.dashStyle = '';
12532                         }
12533                         stroke.endcap = options.lineCap.replace('butt', 'flat');
12534                         stroke.joinstyle = options.lineJoin;
12535
12536                 } else if (stroke) {
12537                         container.removeChild(stroke);
12538                         layer._stroke = null;
12539                 }
12540
12541                 if (options.fill) {
12542                         if (!fill) {
12543                                 fill = layer._fill = vmlCreate('fill');
12544                         }
12545                         container.appendChild(fill);
12546                         fill.color = options.fillColor || options.color;
12547                         fill.opacity = options.fillOpacity;
12548
12549                 } else if (fill) {
12550                         container.removeChild(fill);
12551                         layer._fill = null;
12552                 }
12553         },
12554
12555         _updateCircle: function (layer) {
12556                 var p = layer._point.round(),
12557                     r = Math.round(layer._radius),
12558                     r2 = Math.round(layer._radiusY || r);
12559
12560                 this._setPath(layer, layer._empty() ? 'M0 0' :
12561                         'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12562         },
12563
12564         _setPath: function (layer, path) {
12565                 layer._path.v = path;
12566         },
12567
12568         _bringToFront: function (layer) {
12569                 toFront(layer._container);
12570         },
12571
12572         _bringToBack: function (layer) {
12573                 toBack(layer._container);
12574         }
12575 };
12576
12577 var create$2 = vml ? vmlCreate : svgCreate;
12578
12579 /*
12580  * @class SVG
12581  * @inherits Renderer
12582  * @aka L.SVG
12583  *
12584  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12585  * Inherits `Renderer`.
12586  *
12587  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12588  * available in all web browsers, notably Android 2.x and 3.x.
12589  *
12590  * Although SVG is not available on IE7 and IE8, these browsers support
12591  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12592  * (a now deprecated technology), and the SVG renderer will fall back to VML in
12593  * this case.
12594  *
12595  * @example
12596  *
12597  * Use SVG by default for all paths in the map:
12598  *
12599  * ```js
12600  * var map = L.map('map', {
12601  *      renderer: L.svg()
12602  * });
12603  * ```
12604  *
12605  * Use a SVG renderer with extra padding for specific vector geometries:
12606  *
12607  * ```js
12608  * var map = L.map('map');
12609  * var myRenderer = L.svg({ padding: 0.5 });
12610  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12611  * var circle = L.circle( center, { renderer: myRenderer } );
12612  * ```
12613  */
12614
12615 var SVG = Renderer.extend({
12616
12617         getEvents: function () {
12618                 var events = Renderer.prototype.getEvents.call(this);
12619                 events.zoomstart = this._onZoomStart;
12620                 return events;
12621         },
12622
12623         _initContainer: function () {
12624                 this._container = create$2('svg');
12625
12626                 // makes it possible to click through svg root; we'll reset it back in individual paths
12627                 this._container.setAttribute('pointer-events', 'none');
12628
12629                 this._rootGroup = create$2('g');
12630                 this._container.appendChild(this._rootGroup);
12631         },
12632
12633         _destroyContainer: function () {
12634                 remove(this._container);
12635                 off(this._container);
12636                 delete this._container;
12637                 delete this._rootGroup;
12638                 delete this._svgSize;
12639         },
12640
12641         _onZoomStart: function () {
12642                 // Drag-then-pinch interactions might mess up the center and zoom.
12643                 // In this case, the easiest way to prevent this is re-do the renderer
12644                 //   bounds and padding when the zooming starts.
12645                 this._update();
12646         },
12647
12648         _update: function () {
12649                 if (this._map._animatingZoom && this._bounds) { return; }
12650
12651                 Renderer.prototype._update.call(this);
12652
12653                 var b = this._bounds,
12654                     size = b.getSize(),
12655                     container = this._container;
12656
12657                 // set size of svg-container if changed
12658                 if (!this._svgSize || !this._svgSize.equals(size)) {
12659                         this._svgSize = size;
12660                         container.setAttribute('width', size.x);
12661                         container.setAttribute('height', size.y);
12662                 }
12663
12664                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12665                 setPosition(container, b.min);
12666                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12667
12668                 this.fire('update');
12669         },
12670
12671         // methods below are called by vector layers implementations
12672
12673         _initPath: function (layer) {
12674                 var path = layer._path = create$2('path');
12675
12676                 // @namespace Path
12677                 // @option className: String = null
12678                 // Custom class name set on an element. Only for SVG renderer.
12679                 if (layer.options.className) {
12680                         addClass(path, layer.options.className);
12681                 }
12682
12683                 if (layer.options.interactive) {
12684                         addClass(path, 'leaflet-interactive');
12685                 }
12686
12687                 this._updateStyle(layer);
12688                 this._layers[stamp(layer)] = layer;
12689         },
12690
12691         _addPath: function (layer) {
12692                 if (!this._rootGroup) { this._initContainer(); }
12693                 this._rootGroup.appendChild(layer._path);
12694                 layer.addInteractiveTarget(layer._path);
12695         },
12696
12697         _removePath: function (layer) {
12698                 remove(layer._path);
12699                 layer.removeInteractiveTarget(layer._path);
12700                 delete this._layers[stamp(layer)];
12701         },
12702
12703         _updatePath: function (layer) {
12704                 layer._project();
12705                 layer._update();
12706         },
12707
12708         _updateStyle: function (layer) {
12709                 var path = layer._path,
12710                     options = layer.options;
12711
12712                 if (!path) { return; }
12713
12714                 if (options.stroke) {
12715                         path.setAttribute('stroke', options.color);
12716                         path.setAttribute('stroke-opacity', options.opacity);
12717                         path.setAttribute('stroke-width', options.weight);
12718                         path.setAttribute('stroke-linecap', options.lineCap);
12719                         path.setAttribute('stroke-linejoin', options.lineJoin);
12720
12721                         if (options.dashArray) {
12722                                 path.setAttribute('stroke-dasharray', options.dashArray);
12723                         } else {
12724                                 path.removeAttribute('stroke-dasharray');
12725                         }
12726
12727                         if (options.dashOffset) {
12728                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
12729                         } else {
12730                                 path.removeAttribute('stroke-dashoffset');
12731                         }
12732                 } else {
12733                         path.setAttribute('stroke', 'none');
12734                 }
12735
12736                 if (options.fill) {
12737                         path.setAttribute('fill', options.fillColor || options.color);
12738                         path.setAttribute('fill-opacity', options.fillOpacity);
12739                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12740                 } else {
12741                         path.setAttribute('fill', 'none');
12742                 }
12743         },
12744
12745         _updatePoly: function (layer, closed) {
12746                 this._setPath(layer, pointsToPath(layer._parts, closed));
12747         },
12748
12749         _updateCircle: function (layer) {
12750                 var p = layer._point,
12751                     r = Math.max(Math.round(layer._radius), 1),
12752                     r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12753                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12754
12755                 // drawing a circle with two half-arcs
12756                 var d = layer._empty() ? 'M0 0' :
12757                         'M' + (p.x - r) + ',' + p.y +
12758                         arc + (r * 2) + ',0 ' +
12759                         arc + (-r * 2) + ',0 ';
12760
12761                 this._setPath(layer, d);
12762         },
12763
12764         _setPath: function (layer, path) {
12765                 layer._path.setAttribute('d', path);
12766         },
12767
12768         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12769         _bringToFront: function (layer) {
12770                 toFront(layer._path);
12771         },
12772
12773         _bringToBack: function (layer) {
12774                 toBack(layer._path);
12775         }
12776 });
12777
12778 if (vml) {
12779         SVG.include(vmlMixin);
12780 }
12781
12782 // @namespace SVG
12783 // @factory L.svg(options?: Renderer options)
12784 // Creates a SVG renderer with the given options.
12785 function svg$1(options) {
12786         return svg || vml ? new SVG(options) : null;
12787 }
12788
12789 Map.include({
12790         // @namespace Map; @method getRenderer(layer: Path): Renderer
12791         // Returns the instance of `Renderer` that should be used to render the given
12792         // `Path`. It will ensure that the `renderer` options of the map and paths
12793         // are respected, and that the renderers do exist on the map.
12794         getRenderer: function (layer) {
12795                 // @namespace Path; @option renderer: Renderer
12796                 // Use this specific instance of `Renderer` for this path. Takes
12797                 // precedence over the map's [default renderer](#map-renderer).
12798                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12799
12800                 if (!renderer) {
12801                         renderer = this._renderer = this._createRenderer();
12802                 }
12803
12804                 if (!this.hasLayer(renderer)) {
12805                         this.addLayer(renderer);
12806                 }
12807                 return renderer;
12808         },
12809
12810         _getPaneRenderer: function (name) {
12811                 if (name === 'overlayPane' || name === undefined) {
12812                         return false;
12813                 }
12814
12815                 var renderer = this._paneRenderers[name];
12816                 if (renderer === undefined) {
12817                         renderer = this._createRenderer({pane: name});
12818                         this._paneRenderers[name] = renderer;
12819                 }
12820                 return renderer;
12821         },
12822
12823         _createRenderer: function (options) {
12824                 // @namespace Map; @option preferCanvas: Boolean = false
12825                 // Whether `Path`s should be rendered on a `Canvas` renderer.
12826                 // By default, all `Path`s are rendered in a `SVG` renderer.
12827                 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12828         }
12829 });
12830
12831 /*
12832  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12833  */
12834
12835 /*
12836  * @class Rectangle
12837  * @aka L.Rectangle
12838  * @inherits Polygon
12839  *
12840  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12841  *
12842  * @example
12843  *
12844  * ```js
12845  * // define rectangle geographical bounds
12846  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12847  *
12848  * // create an orange rectangle
12849  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12850  *
12851  * // zoom the map to the rectangle bounds
12852  * map.fitBounds(bounds);
12853  * ```
12854  *
12855  */
12856
12857
12858 var Rectangle = Polygon.extend({
12859         initialize: function (latLngBounds, options) {
12860                 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12861         },
12862
12863         // @method setBounds(latLngBounds: LatLngBounds): this
12864         // Redraws the rectangle with the passed bounds.
12865         setBounds: function (latLngBounds) {
12866                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12867         },
12868
12869         _boundsToLatLngs: function (latLngBounds) {
12870                 latLngBounds = toLatLngBounds(latLngBounds);
12871                 return [
12872                         latLngBounds.getSouthWest(),
12873                         latLngBounds.getNorthWest(),
12874                         latLngBounds.getNorthEast(),
12875                         latLngBounds.getSouthEast()
12876                 ];
12877         }
12878 });
12879
12880
12881 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12882 function rectangle(latLngBounds, options) {
12883         return new Rectangle(latLngBounds, options);
12884 }
12885
12886 SVG.create = create$2;
12887 SVG.pointsToPath = pointsToPath;
12888
12889 GeoJSON.geometryToLayer = geometryToLayer;
12890 GeoJSON.coordsToLatLng = coordsToLatLng;
12891 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12892 GeoJSON.latLngToCoords = latLngToCoords;
12893 GeoJSON.latLngsToCoords = latLngsToCoords;
12894 GeoJSON.getFeature = getFeature;
12895 GeoJSON.asFeature = asFeature;
12896
12897 /*
12898  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12899  * (zoom to a selected bounding box), enabled by default.
12900  */
12901
12902 // @namespace Map
12903 // @section Interaction Options
12904 Map.mergeOptions({
12905         // @option boxZoom: Boolean = true
12906         // Whether the map can be zoomed to a rectangular area specified by
12907         // dragging the mouse while pressing the shift key.
12908         boxZoom: true
12909 });
12910
12911 var BoxZoom = Handler.extend({
12912         initialize: function (map) {
12913                 this._map = map;
12914                 this._container = map._container;
12915                 this._pane = map._panes.overlayPane;
12916                 this._resetStateTimeout = 0;
12917                 map.on('unload', this._destroy, this);
12918         },
12919
12920         addHooks: function () {
12921                 on(this._container, 'mousedown', this._onMouseDown, this);
12922         },
12923
12924         removeHooks: function () {
12925                 off(this._container, 'mousedown', this._onMouseDown, this);
12926         },
12927
12928         moved: function () {
12929                 return this._moved;
12930         },
12931
12932         _destroy: function () {
12933                 remove(this._pane);
12934                 delete this._pane;
12935         },
12936
12937         _resetState: function () {
12938                 this._resetStateTimeout = 0;
12939                 this._moved = false;
12940         },
12941
12942         _clearDeferredResetState: function () {
12943                 if (this._resetStateTimeout !== 0) {
12944                         clearTimeout(this._resetStateTimeout);
12945                         this._resetStateTimeout = 0;
12946                 }
12947         },
12948
12949         _onMouseDown: function (e) {
12950                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12951
12952                 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12953                 // will interrupt the interaction and orphan a box element in the container.
12954                 this._clearDeferredResetState();
12955                 this._resetState();
12956
12957                 disableTextSelection();
12958                 disableImageDrag();
12959
12960                 this._startPoint = this._map.mouseEventToContainerPoint(e);
12961
12962                 on(document, {
12963                         contextmenu: stop,
12964                         mousemove: this._onMouseMove,
12965                         mouseup: this._onMouseUp,
12966                         keydown: this._onKeyDown
12967                 }, this);
12968         },
12969
12970         _onMouseMove: function (e) {
12971                 if (!this._moved) {
12972                         this._moved = true;
12973
12974                         this._box = create$1('div', 'leaflet-zoom-box', this._container);
12975                         addClass(this._container, 'leaflet-crosshair');
12976
12977                         this._map.fire('boxzoomstart');
12978                 }
12979
12980                 this._point = this._map.mouseEventToContainerPoint(e);
12981
12982                 var bounds = new Bounds(this._point, this._startPoint),
12983                     size = bounds.getSize();
12984
12985                 setPosition(this._box, bounds.min);
12986
12987                 this._box.style.width  = size.x + 'px';
12988                 this._box.style.height = size.y + 'px';
12989         },
12990
12991         _finish: function () {
12992                 if (this._moved) {
12993                         remove(this._box);
12994                         removeClass(this._container, 'leaflet-crosshair');
12995                 }
12996
12997                 enableTextSelection();
12998                 enableImageDrag();
12999
13000                 off(document, {
13001                         contextmenu: stop,
13002                         mousemove: this._onMouseMove,
13003                         mouseup: this._onMouseUp,
13004                         keydown: this._onKeyDown
13005                 }, this);
13006         },
13007
13008         _onMouseUp: function (e) {
13009                 if ((e.which !== 1) && (e.button !== 1)) { return; }
13010
13011                 this._finish();
13012
13013                 if (!this._moved) { return; }
13014                 // Postpone to next JS tick so internal click event handling
13015                 // still see it as "moved".
13016                 this._clearDeferredResetState();
13017                 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13018
13019                 var bounds = new LatLngBounds(
13020                         this._map.containerPointToLatLng(this._startPoint),
13021                         this._map.containerPointToLatLng(this._point));
13022
13023                 this._map
13024                         .fitBounds(bounds)
13025                         .fire('boxzoomend', {boxZoomBounds: bounds});
13026         },
13027
13028         _onKeyDown: function (e) {
13029                 if (e.keyCode === 27) {
13030                         this._finish();
13031                 }
13032         }
13033 });
13034
13035 // @section Handlers
13036 // @property boxZoom: Handler
13037 // Box (shift-drag with mouse) zoom handler.
13038 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13039
13040 /*
13041  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13042  */
13043
13044 // @namespace Map
13045 // @section Interaction Options
13046
13047 Map.mergeOptions({
13048         // @option doubleClickZoom: Boolean|String = true
13049         // Whether the map can be zoomed in by double clicking on it and
13050         // zoomed out by double clicking while holding shift. If passed
13051         // `'center'`, double-click zoom will zoom to the center of the
13052         //  view regardless of where the mouse was.
13053         doubleClickZoom: true
13054 });
13055
13056 var DoubleClickZoom = Handler.extend({
13057         addHooks: function () {
13058                 this._map.on('dblclick', this._onDoubleClick, this);
13059         },
13060
13061         removeHooks: function () {
13062                 this._map.off('dblclick', this._onDoubleClick, this);
13063         },
13064
13065         _onDoubleClick: function (e) {
13066                 var map = this._map,
13067                     oldZoom = map.getZoom(),
13068                     delta = map.options.zoomDelta,
13069                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13070
13071                 if (map.options.doubleClickZoom === 'center') {
13072                         map.setZoom(zoom);
13073                 } else {
13074                         map.setZoomAround(e.containerPoint, zoom);
13075                 }
13076         }
13077 });
13078
13079 // @section Handlers
13080 //
13081 // Map properties include interaction handlers that allow you to control
13082 // interaction behavior in runtime, enabling or disabling certain features such
13083 // as dragging or touch zoom (see `Handler` methods). For example:
13084 //
13085 // ```js
13086 // map.doubleClickZoom.disable();
13087 // ```
13088 //
13089 // @property doubleClickZoom: Handler
13090 // Double click zoom handler.
13091 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13092
13093 /*
13094  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13095  */
13096
13097 // @namespace Map
13098 // @section Interaction Options
13099 Map.mergeOptions({
13100         // @option dragging: Boolean = true
13101         // Whether the map be draggable with mouse/touch or not.
13102         dragging: true,
13103
13104         // @section Panning Inertia Options
13105         // @option inertia: Boolean = *
13106         // If enabled, panning of the map will have an inertia effect where
13107         // the map builds momentum while dragging and continues moving in
13108         // the same direction for some time. Feels especially nice on touch
13109         // devices. Enabled by default unless running on old Android devices.
13110         inertia: !android23,
13111
13112         // @option inertiaDeceleration: Number = 3000
13113         // The rate with which the inertial movement slows down, in pixels/second².
13114         inertiaDeceleration: 3400, // px/s^2
13115
13116         // @option inertiaMaxSpeed: Number = Infinity
13117         // Max speed of the inertial movement, in pixels/second.
13118         inertiaMaxSpeed: Infinity, // px/s
13119
13120         // @option easeLinearity: Number = 0.2
13121         easeLinearity: 0.2,
13122
13123         // TODO refactor, move to CRS
13124         // @option worldCopyJump: Boolean = false
13125         // With this option enabled, the map tracks when you pan to another "copy"
13126         // of the world and seamlessly jumps to the original one so that all overlays
13127         // like markers and vector layers are still visible.
13128         worldCopyJump: false,
13129
13130         // @option maxBoundsViscosity: Number = 0.0
13131         // If `maxBounds` is set, this option will control how solid the bounds
13132         // are when dragging the map around. The default value of `0.0` allows the
13133         // user to drag outside the bounds at normal speed, higher values will
13134         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13135         // solid, preventing the user from dragging outside the bounds.
13136         maxBoundsViscosity: 0.0
13137 });
13138
13139 var Drag = Handler.extend({
13140         addHooks: function () {
13141                 if (!this._draggable) {
13142                         var map = this._map;
13143
13144                         this._draggable = new Draggable(map._mapPane, map._container);
13145
13146                         this._draggable.on({
13147                                 dragstart: this._onDragStart,
13148                                 drag: this._onDrag,
13149                                 dragend: this._onDragEnd
13150                         }, this);
13151
13152                         this._draggable.on('predrag', this._onPreDragLimit, this);
13153                         if (map.options.worldCopyJump) {
13154                                 this._draggable.on('predrag', this._onPreDragWrap, this);
13155                                 map.on('zoomend', this._onZoomEnd, this);
13156
13157                                 map.whenReady(this._onZoomEnd, this);
13158                         }
13159                 }
13160                 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13161                 this._draggable.enable();
13162                 this._positions = [];
13163                 this._times = [];
13164         },
13165
13166         removeHooks: function () {
13167                 removeClass(this._map._container, 'leaflet-grab');
13168                 removeClass(this._map._container, 'leaflet-touch-drag');
13169                 this._draggable.disable();
13170         },
13171
13172         moved: function () {
13173                 return this._draggable && this._draggable._moved;
13174         },
13175
13176         moving: function () {
13177                 return this._draggable && this._draggable._moving;
13178         },
13179
13180         _onDragStart: function () {
13181                 var map = this._map;
13182
13183                 map._stop();
13184                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13185                         var bounds = toLatLngBounds(this._map.options.maxBounds);
13186
13187                         this._offsetLimit = toBounds(
13188                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13189                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13190                                         .add(this._map.getSize()));
13191
13192                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13193                 } else {
13194                         this._offsetLimit = null;
13195                 }
13196
13197                 map
13198                     .fire('movestart')
13199                     .fire('dragstart');
13200
13201                 if (map.options.inertia) {
13202                         this._positions = [];
13203                         this._times = [];
13204                 }
13205         },
13206
13207         _onDrag: function (e) {
13208                 if (this._map.options.inertia) {
13209                         var time = this._lastTime = +new Date(),
13210                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13211
13212                         this._positions.push(pos);
13213                         this._times.push(time);
13214
13215                         this._prunePositions(time);
13216                 }
13217
13218                 this._map
13219                     .fire('move', e)
13220                     .fire('drag', e);
13221         },
13222
13223         _prunePositions: function (time) {
13224                 while (this._positions.length > 1 && time - this._times[0] > 50) {
13225                         this._positions.shift();
13226                         this._times.shift();
13227                 }
13228         },
13229
13230         _onZoomEnd: function () {
13231                 var pxCenter = this._map.getSize().divideBy(2),
13232                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13233
13234                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13235                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13236         },
13237
13238         _viscousLimit: function (value, threshold) {
13239                 return value - (value - threshold) * this._viscosity;
13240         },
13241
13242         _onPreDragLimit: function () {
13243                 if (!this._viscosity || !this._offsetLimit) { return; }
13244
13245                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13246
13247                 var limit = this._offsetLimit;
13248                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13249                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13250                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13251                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13252
13253                 this._draggable._newPos = this._draggable._startPos.add(offset);
13254         },
13255
13256         _onPreDragWrap: function () {
13257                 // TODO refactor to be able to adjust map pane position after zoom
13258                 var worldWidth = this._worldWidth,
13259                     halfWidth = Math.round(worldWidth / 2),
13260                     dx = this._initialWorldOffset,
13261                     x = this._draggable._newPos.x,
13262                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13263                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13264                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13265
13266                 this._draggable._absPos = this._draggable._newPos.clone();
13267                 this._draggable._newPos.x = newX;
13268         },
13269
13270         _onDragEnd: function (e) {
13271                 var map = this._map,
13272                     options = map.options,
13273
13274                     noInertia = !options.inertia || this._times.length < 2;
13275
13276                 map.fire('dragend', e);
13277
13278                 if (noInertia) {
13279                         map.fire('moveend');
13280
13281                 } else {
13282                         this._prunePositions(+new Date());
13283
13284                         var direction = this._lastPos.subtract(this._positions[0]),
13285                             duration = (this._lastTime - this._times[0]) / 1000,
13286                             ease = options.easeLinearity,
13287
13288                             speedVector = direction.multiplyBy(ease / duration),
13289                             speed = speedVector.distanceTo([0, 0]),
13290
13291                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13292                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13293
13294                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13295                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13296
13297                         if (!offset.x && !offset.y) {
13298                                 map.fire('moveend');
13299
13300                         } else {
13301                                 offset = map._limitOffset(offset, map.options.maxBounds);
13302
13303                                 requestAnimFrame(function () {
13304                                         map.panBy(offset, {
13305                                                 duration: decelerationDuration,
13306                                                 easeLinearity: ease,
13307                                                 noMoveStart: true,
13308                                                 animate: true
13309                                         });
13310                                 });
13311                         }
13312                 }
13313         }
13314 });
13315
13316 // @section Handlers
13317 // @property dragging: Handler
13318 // Map dragging handler (by both mouse and touch).
13319 Map.addInitHook('addHandler', 'dragging', Drag);
13320
13321 /*
13322  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13323  */
13324
13325 // @namespace Map
13326 // @section Keyboard Navigation Options
13327 Map.mergeOptions({
13328         // @option keyboard: Boolean = true
13329         // Makes the map focusable and allows users to navigate the map with keyboard
13330         // arrows and `+`/`-` keys.
13331         keyboard: true,
13332
13333         // @option keyboardPanDelta: Number = 80
13334         // Amount of pixels to pan when pressing an arrow key.
13335         keyboardPanDelta: 80
13336 });
13337
13338 var Keyboard = Handler.extend({
13339
13340         keyCodes: {
13341                 left:    [37],
13342                 right:   [39],
13343                 down:    [40],
13344                 up:      [38],
13345                 zoomIn:  [187, 107, 61, 171],
13346                 zoomOut: [189, 109, 54, 173]
13347         },
13348
13349         initialize: function (map) {
13350                 this._map = map;
13351
13352                 this._setPanDelta(map.options.keyboardPanDelta);
13353                 this._setZoomDelta(map.options.zoomDelta);
13354         },
13355
13356         addHooks: function () {
13357                 var container = this._map._container;
13358
13359                 // make the container focusable by tabbing
13360                 if (container.tabIndex <= 0) {
13361                         container.tabIndex = '0';
13362                 }
13363
13364                 on(container, {
13365                         focus: this._onFocus,
13366                         blur: this._onBlur,
13367                         mousedown: this._onMouseDown
13368                 }, this);
13369
13370                 this._map.on({
13371                         focus: this._addHooks,
13372                         blur: this._removeHooks
13373                 }, this);
13374         },
13375
13376         removeHooks: function () {
13377                 this._removeHooks();
13378
13379                 off(this._map._container, {
13380                         focus: this._onFocus,
13381                         blur: this._onBlur,
13382                         mousedown: this._onMouseDown
13383                 }, this);
13384
13385                 this._map.off({
13386                         focus: this._addHooks,
13387                         blur: this._removeHooks
13388                 }, this);
13389         },
13390
13391         _onMouseDown: function () {
13392                 if (this._focused) { return; }
13393
13394                 var body = document.body,
13395                     docEl = document.documentElement,
13396                     top = body.scrollTop || docEl.scrollTop,
13397                     left = body.scrollLeft || docEl.scrollLeft;
13398
13399                 this._map._container.focus();
13400
13401                 window.scrollTo(left, top);
13402         },
13403
13404         _onFocus: function () {
13405                 this._focused = true;
13406                 this._map.fire('focus');
13407         },
13408
13409         _onBlur: function () {
13410                 this._focused = false;
13411                 this._map.fire('blur');
13412         },
13413
13414         _setPanDelta: function (panDelta) {
13415                 var keys = this._panKeys = {},
13416                     codes = this.keyCodes,
13417                     i, len;
13418
13419                 for (i = 0, len = codes.left.length; i < len; i++) {
13420                         keys[codes.left[i]] = [-1 * panDelta, 0];
13421                 }
13422                 for (i = 0, len = codes.right.length; i < len; i++) {
13423                         keys[codes.right[i]] = [panDelta, 0];
13424                 }
13425                 for (i = 0, len = codes.down.length; i < len; i++) {
13426                         keys[codes.down[i]] = [0, panDelta];
13427                 }
13428                 for (i = 0, len = codes.up.length; i < len; i++) {
13429                         keys[codes.up[i]] = [0, -1 * panDelta];
13430                 }
13431         },
13432
13433         _setZoomDelta: function (zoomDelta) {
13434                 var keys = this._zoomKeys = {},
13435                     codes = this.keyCodes,
13436                     i, len;
13437
13438                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13439                         keys[codes.zoomIn[i]] = zoomDelta;
13440                 }
13441                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13442                         keys[codes.zoomOut[i]] = -zoomDelta;
13443                 }
13444         },
13445
13446         _addHooks: function () {
13447                 on(document, 'keydown', this._onKeyDown, this);
13448         },
13449
13450         _removeHooks: function () {
13451                 off(document, 'keydown', this._onKeyDown, this);
13452         },
13453
13454         _onKeyDown: function (e) {
13455                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13456
13457                 var key = e.keyCode,
13458                     map = this._map,
13459                     offset;
13460
13461                 if (key in this._panKeys) {
13462                         if (!map._panAnim || !map._panAnim._inProgress) {
13463                                 offset = this._panKeys[key];
13464                                 if (e.shiftKey) {
13465                                         offset = toPoint(offset).multiplyBy(3);
13466                                 }
13467
13468                                 map.panBy(offset);
13469
13470                                 if (map.options.maxBounds) {
13471                                         map.panInsideBounds(map.options.maxBounds);
13472                                 }
13473                         }
13474                 } else if (key in this._zoomKeys) {
13475                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13476
13477                 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13478                         map.closePopup();
13479
13480                 } else {
13481                         return;
13482                 }
13483
13484                 stop(e);
13485         }
13486 });
13487
13488 // @section Handlers
13489 // @section Handlers
13490 // @property keyboard: Handler
13491 // Keyboard navigation handler.
13492 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13493
13494 /*
13495  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13496  */
13497
13498 // @namespace Map
13499 // @section Interaction Options
13500 Map.mergeOptions({
13501         // @section Mousewheel options
13502         // @option scrollWheelZoom: Boolean|String = true
13503         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13504         // it will zoom to the center of the view regardless of where the mouse was.
13505         scrollWheelZoom: true,
13506
13507         // @option wheelDebounceTime: Number = 40
13508         // Limits the rate at which a wheel can fire (in milliseconds). By default
13509         // user can't zoom via wheel more often than once per 40 ms.
13510         wheelDebounceTime: 40,
13511
13512         // @option wheelPxPerZoomLevel: Number = 60
13513         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13514         // mean a change of one full zoom level. Smaller values will make wheel-zooming
13515         // faster (and vice versa).
13516         wheelPxPerZoomLevel: 60
13517 });
13518
13519 var ScrollWheelZoom = Handler.extend({
13520         addHooks: function () {
13521                 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13522
13523                 this._delta = 0;
13524         },
13525
13526         removeHooks: function () {
13527                 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13528         },
13529
13530         _onWheelScroll: function (e) {
13531                 var delta = getWheelDelta(e);
13532
13533                 var debounce = this._map.options.wheelDebounceTime;
13534
13535                 this._delta += delta;
13536                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13537
13538                 if (!this._startTime) {
13539                         this._startTime = +new Date();
13540                 }
13541
13542                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13543
13544                 clearTimeout(this._timer);
13545                 this._timer = setTimeout(bind(this._performZoom, this), left);
13546
13547                 stop(e);
13548         },
13549
13550         _performZoom: function () {
13551                 var map = this._map,
13552                     zoom = map.getZoom(),
13553                     snap = this._map.options.zoomSnap || 0;
13554
13555                 map._stop(); // stop panning and fly animations if any
13556
13557                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13558                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13559                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13560                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13561                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13562
13563                 this._delta = 0;
13564                 this._startTime = null;
13565
13566                 if (!delta) { return; }
13567
13568                 if (map.options.scrollWheelZoom === 'center') {
13569                         map.setZoom(zoom + delta);
13570                 } else {
13571                         map.setZoomAround(this._lastMousePos, zoom + delta);
13572                 }
13573         }
13574 });
13575
13576 // @section Handlers
13577 // @property scrollWheelZoom: Handler
13578 // Scroll wheel zoom handler.
13579 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13580
13581 /*
13582  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13583  */
13584
13585 // @namespace Map
13586 // @section Interaction Options
13587 Map.mergeOptions({
13588         // @section Touch interaction options
13589         // @option tap: Boolean = true
13590         // Enables mobile hacks for supporting instant taps (fixing 200ms click
13591         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13592         tap: true,
13593
13594         // @option tapTolerance: Number = 15
13595         // The max number of pixels a user can shift his finger during touch
13596         // for it to be considered a valid tap.
13597         tapTolerance: 15
13598 });
13599
13600 var Tap = Handler.extend({
13601         addHooks: function () {
13602                 on(this._map._container, 'touchstart', this._onDown, this);
13603         },
13604
13605         removeHooks: function () {
13606                 off(this._map._container, 'touchstart', this._onDown, this);
13607         },
13608
13609         _onDown: function (e) {
13610                 if (!e.touches) { return; }
13611
13612                 preventDefault(e);
13613
13614                 this._fireClick = true;
13615
13616                 // don't simulate click or track longpress if more than 1 touch
13617                 if (e.touches.length > 1) {
13618                         this._fireClick = false;
13619                         clearTimeout(this._holdTimeout);
13620                         return;
13621                 }
13622
13623                 var first = e.touches[0],
13624                     el = first.target;
13625
13626                 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13627
13628                 // if touching a link, highlight it
13629                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13630                         addClass(el, 'leaflet-active');
13631                 }
13632
13633                 // simulate long hold but setting a timeout
13634                 this._holdTimeout = setTimeout(bind(function () {
13635                         if (this._isTapValid()) {
13636                                 this._fireClick = false;
13637                                 this._onUp();
13638                                 this._simulateEvent('contextmenu', first);
13639                         }
13640                 }, this), 1000);
13641
13642                 this._simulateEvent('mousedown', first);
13643
13644                 on(document, {
13645                         touchmove: this._onMove,
13646                         touchend: this._onUp
13647                 }, this);
13648         },
13649
13650         _onUp: function (e) {
13651                 clearTimeout(this._holdTimeout);
13652
13653                 off(document, {
13654                         touchmove: this._onMove,
13655                         touchend: this._onUp
13656                 }, this);
13657
13658                 if (this._fireClick && e && e.changedTouches) {
13659
13660                         var first = e.changedTouches[0],
13661                             el = first.target;
13662
13663                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13664                                 removeClass(el, 'leaflet-active');
13665                         }
13666
13667                         this._simulateEvent('mouseup', first);
13668
13669                         // simulate click if the touch didn't move too much
13670                         if (this._isTapValid()) {
13671                                 this._simulateEvent('click', first);
13672                         }
13673                 }
13674         },
13675
13676         _isTapValid: function () {
13677                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13678         },
13679
13680         _onMove: function (e) {
13681                 var first = e.touches[0];
13682                 this._newPos = new Point(first.clientX, first.clientY);
13683                 this._simulateEvent('mousemove', first);
13684         },
13685
13686         _simulateEvent: function (type, e) {
13687                 var simulatedEvent = document.createEvent('MouseEvents');
13688
13689                 simulatedEvent._simulated = true;
13690                 e.target._simulatedClick = true;
13691
13692                 simulatedEvent.initMouseEvent(
13693                         type, true, true, window, 1,
13694                         e.screenX, e.screenY,
13695                         e.clientX, e.clientY,
13696                         false, false, false, false, 0, null);
13697
13698                 e.target.dispatchEvent(simulatedEvent);
13699         }
13700 });
13701
13702 // @section Handlers
13703 // @property tap: Handler
13704 // Mobile touch hacks (quick tap and touch hold) handler.
13705 if (touch && !pointer) {
13706         Map.addInitHook('addHandler', 'tap', Tap);
13707 }
13708
13709 /*
13710  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13711  */
13712
13713 // @namespace Map
13714 // @section Interaction Options
13715 Map.mergeOptions({
13716         // @section Touch interaction options
13717         // @option touchZoom: Boolean|String = *
13718         // Whether the map can be zoomed by touch-dragging with two fingers. If
13719         // passed `'center'`, it will zoom to the center of the view regardless of
13720         // where the touch events (fingers) were. Enabled for touch-capable web
13721         // browsers except for old Androids.
13722         touchZoom: touch && !android23,
13723
13724         // @option bounceAtZoomLimits: Boolean = true
13725         // Set it to false if you don't want the map to zoom beyond min/max zoom
13726         // and then bounce back when pinch-zooming.
13727         bounceAtZoomLimits: true
13728 });
13729
13730 var TouchZoom = Handler.extend({
13731         addHooks: function () {
13732                 addClass(this._map._container, 'leaflet-touch-zoom');
13733                 on(this._map._container, 'touchstart', this._onTouchStart, this);
13734         },
13735
13736         removeHooks: function () {
13737                 removeClass(this._map._container, 'leaflet-touch-zoom');
13738                 off(this._map._container, 'touchstart', this._onTouchStart, this);
13739         },
13740
13741         _onTouchStart: function (e) {
13742                 var map = this._map;
13743                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13744
13745                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13746                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
13747
13748                 this._centerPoint = map.getSize()._divideBy(2);
13749                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13750                 if (map.options.touchZoom !== 'center') {
13751                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13752                 }
13753
13754                 this._startDist = p1.distanceTo(p2);
13755                 this._startZoom = map.getZoom();
13756
13757                 this._moved = false;
13758                 this._zooming = true;
13759
13760                 map._stop();
13761
13762                 on(document, 'touchmove', this._onTouchMove, this);
13763                 on(document, 'touchend', this._onTouchEnd, this);
13764
13765                 preventDefault(e);
13766         },
13767
13768         _onTouchMove: function (e) {
13769                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13770
13771                 var map = this._map,
13772                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
13773                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
13774                     scale = p1.distanceTo(p2) / this._startDist;
13775
13776                 this._zoom = map.getScaleZoom(scale, this._startZoom);
13777
13778                 if (!map.options.bounceAtZoomLimits && (
13779                         (this._zoom < map.getMinZoom() && scale < 1) ||
13780                         (this._zoom > map.getMaxZoom() && scale > 1))) {
13781                         this._zoom = map._limitZoom(this._zoom);
13782                 }
13783
13784                 if (map.options.touchZoom === 'center') {
13785                         this._center = this._startLatLng;
13786                         if (scale === 1) { return; }
13787                 } else {
13788                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13789                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13790                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13791                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13792                 }
13793
13794                 if (!this._moved) {
13795                         map._moveStart(true, false);
13796                         this._moved = true;
13797                 }
13798
13799                 cancelAnimFrame(this._animRequest);
13800
13801                 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13802                 this._animRequest = requestAnimFrame(moveFn, this, true);
13803
13804                 preventDefault(e);
13805         },
13806
13807         _onTouchEnd: function () {
13808                 if (!this._moved || !this._zooming) {
13809                         this._zooming = false;
13810                         return;
13811                 }
13812
13813                 this._zooming = false;
13814                 cancelAnimFrame(this._animRequest);
13815
13816                 off(document, 'touchmove', this._onTouchMove);
13817                 off(document, 'touchend', this._onTouchEnd);
13818
13819                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13820                 if (this._map.options.zoomAnimation) {
13821                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13822                 } else {
13823                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13824                 }
13825         }
13826 });
13827
13828 // @section Handlers
13829 // @property touchZoom: Handler
13830 // Touch zoom handler.
13831 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13832
13833 Map.BoxZoom = BoxZoom;
13834 Map.DoubleClickZoom = DoubleClickZoom;
13835 Map.Drag = Drag;
13836 Map.Keyboard = Keyboard;
13837 Map.ScrollWheelZoom = ScrollWheelZoom;
13838 Map.Tap = Tap;
13839 Map.TouchZoom = TouchZoom;
13840
13841 Object.freeze = freeze;
13842
13843 exports.version = version;
13844 exports.Control = Control;
13845 exports.control = control;
13846 exports.Browser = Browser;
13847 exports.Evented = Evented;
13848 exports.Mixin = Mixin;
13849 exports.Util = Util;
13850 exports.Class = Class;
13851 exports.Handler = Handler;
13852 exports.extend = extend;
13853 exports.bind = bind;
13854 exports.stamp = stamp;
13855 exports.setOptions = setOptions;
13856 exports.DomEvent = DomEvent;
13857 exports.DomUtil = DomUtil;
13858 exports.PosAnimation = PosAnimation;
13859 exports.Draggable = Draggable;
13860 exports.LineUtil = LineUtil;
13861 exports.PolyUtil = PolyUtil;
13862 exports.Point = Point;
13863 exports.point = toPoint;
13864 exports.Bounds = Bounds;
13865 exports.bounds = toBounds;
13866 exports.Transformation = Transformation;
13867 exports.transformation = toTransformation;
13868 exports.Projection = index;
13869 exports.LatLng = LatLng;
13870 exports.latLng = toLatLng;
13871 exports.LatLngBounds = LatLngBounds;
13872 exports.latLngBounds = toLatLngBounds;
13873 exports.CRS = CRS;
13874 exports.GeoJSON = GeoJSON;
13875 exports.geoJSON = geoJSON;
13876 exports.geoJson = geoJson;
13877 exports.Layer = Layer;
13878 exports.LayerGroup = LayerGroup;
13879 exports.layerGroup = layerGroup;
13880 exports.FeatureGroup = FeatureGroup;
13881 exports.featureGroup = featureGroup;
13882 exports.ImageOverlay = ImageOverlay;
13883 exports.imageOverlay = imageOverlay;
13884 exports.VideoOverlay = VideoOverlay;
13885 exports.videoOverlay = videoOverlay;
13886 exports.DivOverlay = DivOverlay;
13887 exports.Popup = Popup;
13888 exports.popup = popup;
13889 exports.Tooltip = Tooltip;
13890 exports.tooltip = tooltip;
13891 exports.Icon = Icon;
13892 exports.icon = icon;
13893 exports.DivIcon = DivIcon;
13894 exports.divIcon = divIcon;
13895 exports.Marker = Marker;
13896 exports.marker = marker;
13897 exports.TileLayer = TileLayer;
13898 exports.tileLayer = tileLayer;
13899 exports.GridLayer = GridLayer;
13900 exports.gridLayer = gridLayer;
13901 exports.SVG = SVG;
13902 exports.svg = svg$1;
13903 exports.Renderer = Renderer;
13904 exports.Canvas = Canvas;
13905 exports.canvas = canvas$1;
13906 exports.Path = Path;
13907 exports.CircleMarker = CircleMarker;
13908 exports.circleMarker = circleMarker;
13909 exports.Circle = Circle;
13910 exports.circle = circle;
13911 exports.Polyline = Polyline;
13912 exports.polyline = polyline;
13913 exports.Polygon = Polygon;
13914 exports.polygon = polygon;
13915 exports.Rectangle = Rectangle;
13916 exports.rectangle = rectangle;
13917 exports.Map = Map;
13918 exports.map = createMap;
13919
13920 var oldL = window.L;
13921 exports.noConflict = function() {
13922         window.L = oldL;
13923         return this;
13924 }
13925
13926 // Always export us to window global (see #2364)
13927 window.L = exports;
13928
13929 })));
13930 //# sourceMappingURL=leaflet-src.js.map