sponsor alt
[wolnelektury.git] / src / wolnelektury / static / contrib / leaflet-1.9.4 / leaflet-src.js
1 /* @preserve
2  * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com
3  * (c) 2010-2023 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   (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {}));
10 })(this, (function (exports) { 'use strict';
11
12   var version = "1.9.4";
13
14   /*\r
15    * @namespace Util\r
16    *\r
17    * Various utility functions, used by Leaflet internally.\r
18    */\r
19 \r
20   // @function extend(dest: Object, src?: Object): Object\r
21   // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.\r
22   function extend(dest) {\r
23         var i, j, len, src;\r
24 \r
25         for (j = 1, len = arguments.length; j < len; j++) {\r
26                 src = arguments[j];\r
27                 for (i in src) {\r
28                         dest[i] = src[i];\r
29                 }\r
30         }\r
31         return dest;\r
32   }\r
33 \r
34   // @function create(proto: Object, properties?: Object): Object\r
35   // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)\r
36   var create$2 = Object.create || (function () {\r
37         function F() {}\r
38         return function (proto) {\r
39                 F.prototype = proto;\r
40                 return new F();\r
41         };\r
42   })();\r
43 \r
44   // @function bind(fn: Function, …): Function\r
45   // 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).\r
46   // Has a `L.bind()` shortcut.\r
47   function bind(fn, obj) {\r
48         var slice = Array.prototype.slice;\r
49 \r
50         if (fn.bind) {\r
51                 return fn.bind.apply(fn, slice.call(arguments, 1));\r
52         }\r
53 \r
54         var args = slice.call(arguments, 2);\r
55 \r
56         return function () {\r
57                 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);\r
58         };\r
59   }\r
60 \r
61   // @property lastId: Number\r
62   // Last unique ID used by [`stamp()`](#util-stamp)\r
63   var lastId = 0;\r
64 \r
65   // @function stamp(obj: Object): Number\r
66   // Returns the unique ID of an object, assigning it one if it doesn't have it.\r
67   function stamp(obj) {\r
68         if (!('_leaflet_id' in obj)) {\r
69                 obj['_leaflet_id'] = ++lastId;\r
70         }\r
71         return obj._leaflet_id;\r
72   }\r
73 \r
74   // @function throttle(fn: Function, time: Number, context: Object): Function\r
75   // Returns a function which executes function `fn` with the given scope `context`\r
76   // (so that the `this` keyword refers to `context` inside `fn`'s code). The function\r
77   // `fn` will be called no more than one time per given amount of `time`. The arguments\r
78   // received by the bound function will be any arguments passed when binding the\r
79   // function, followed by any arguments passed when invoking the bound function.\r
80   // Has an `L.throttle` shortcut.\r
81   function throttle(fn, time, context) {\r
82         var lock, args, wrapperFn, later;\r
83 \r
84         later = function () {\r
85                 // reset lock and call if queued\r
86                 lock = false;\r
87                 if (args) {\r
88                         wrapperFn.apply(context, args);\r
89                         args = false;\r
90                 }\r
91         };\r
92 \r
93         wrapperFn = function () {\r
94                 if (lock) {\r
95                         // called too soon, queue to call later\r
96                         args = arguments;\r
97 \r
98                 } else {\r
99                         // call and lock until later\r
100                         fn.apply(context, arguments);\r
101                         setTimeout(later, time);\r
102                         lock = true;\r
103                 }\r
104         };\r
105 \r
106         return wrapperFn;\r
107   }\r
108 \r
109   // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number\r
110   // Returns the number `num` modulo `range` in such a way so it lies within\r
111   // `range[0]` and `range[1]`. The returned value will be always smaller than\r
112   // `range[1]` unless `includeMax` is set to `true`.\r
113   function wrapNum(x, range, includeMax) {\r
114         var max = range[1],\r
115             min = range[0],\r
116             d = max - min;\r
117         return x === max && includeMax ? x : ((x - min) % d + d) % d + min;\r
118   }\r
119 \r
120   // @function falseFn(): Function\r
121   // Returns a function which always returns `false`.\r
122   function falseFn() { return false; }\r
123 \r
124   // @function formatNum(num: Number, precision?: Number|false): Number\r
125   // Returns the number `num` rounded with specified `precision`.\r
126   // The default `precision` value is 6 decimal places.\r
127   // `false` can be passed to skip any processing (can be useful to avoid round-off errors).\r
128   function formatNum(num, precision) {\r
129         if (precision === false) { return num; }\r
130         var pow = Math.pow(10, precision === undefined ? 6 : precision);\r
131         return Math.round(num * pow) / pow;\r
132   }\r
133 \r
134   // @function trim(str: String): String\r
135   // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)\r
136   function trim(str) {\r
137         return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');\r
138   }\r
139 \r
140   // @function splitWords(str: String): String[]\r
141   // Trims and splits the string on whitespace and returns the array of parts.\r
142   function splitWords(str) {\r
143         return trim(str).split(/\s+/);\r
144   }\r
145 \r
146   // @function setOptions(obj: Object, options: Object): Object\r
147   // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.\r
148   function setOptions(obj, options) {\r
149         if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {\r
150                 obj.options = obj.options ? create$2(obj.options) : {};\r
151         }\r
152         for (var i in options) {\r
153                 obj.options[i] = options[i];\r
154         }\r
155         return obj.options;\r
156   }\r
157 \r
158   // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String\r
159   // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`\r
160   // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will\r
161   // be appended at the end. If `uppercase` is `true`, the parameter names will\r
162   // be uppercased (e.g. `'?A=foo&B=bar'`)\r
163   function getParamString(obj, existingUrl, uppercase) {\r
164         var params = [];\r
165         for (var i in obj) {\r
166                 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));\r
167         }\r
168         return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');\r
169   }\r
170 \r
171   var templateRe = /\{ *([\w_ -]+) *\}/g;\r
172 \r
173   // @function template(str: String, data: Object): String\r
174   // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`\r
175   // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string\r
176   // `('Hello foo, bar')`. You can also specify functions instead of strings for\r
177   // data values — they will be evaluated passing `data` as an argument.\r
178   function template(str, data) {\r
179         return str.replace(templateRe, function (str, key) {\r
180                 var value = data[key];\r
181 \r
182                 if (value === undefined) {\r
183                         throw new Error('No value provided for variable ' + str);\r
184 \r
185                 } else if (typeof value === 'function') {\r
186                         value = value(data);\r
187                 }\r
188                 return value;\r
189         });\r
190   }\r
191 \r
192   // @function isArray(obj): Boolean\r
193   // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)\r
194   var isArray = Array.isArray || function (obj) {\r
195         return (Object.prototype.toString.call(obj) === '[object Array]');\r
196   };\r
197 \r
198   // @function indexOf(array: Array, el: Object): Number\r
199   // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)\r
200   function indexOf(array, el) {\r
201         for (var i = 0; i < array.length; i++) {\r
202                 if (array[i] === el) { return i; }\r
203         }\r
204         return -1;\r
205   }\r
206 \r
207   // @property emptyImageUrl: String\r
208   // Data URI string containing a base64-encoded empty GIF image.\r
209   // Used as a hack to free memory from unused images on WebKit-powered\r
210   // mobile devices (by setting image `src` to this string).\r
211   var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';\r
212 \r
213   // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/\r
214 \r
215   function getPrefixed(name) {\r
216         return window['webkit' + name] || window['moz' + name] || window['ms' + name];\r
217   }\r
218 \r
219   var lastTime = 0;\r
220 \r
221   // fallback for IE 7-8\r
222   function timeoutDefer(fn) {\r
223         var time = +new Date(),\r
224             timeToCall = Math.max(0, 16 - (time - lastTime));\r
225 \r
226         lastTime = time + timeToCall;\r
227         return window.setTimeout(fn, timeToCall);\r
228   }\r
229 \r
230   var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;\r
231   var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||\r
232                 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };\r
233 \r
234   // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number\r
235   // Schedules `fn` to be executed when the browser repaints. `fn` is bound to\r
236   // `context` if given. When `immediate` is set, `fn` is called immediately if\r
237   // the browser doesn't have native support for\r
238   // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),\r
239   // otherwise it's delayed. Returns a request ID that can be used to cancel the request.\r
240   function requestAnimFrame(fn, context, immediate) {\r
241         if (immediate && requestFn === timeoutDefer) {\r
242                 fn.call(context);\r
243         } else {\r
244                 return requestFn.call(window, bind(fn, context));\r
245         }\r
246   }\r
247 \r
248   // @function cancelAnimFrame(id: Number): undefined\r
249   // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).\r
250   function cancelAnimFrame(id) {\r
251         if (id) {\r
252                 cancelFn.call(window, id);\r
253         }\r
254   }
255
256   var Util = {
257     __proto__: null,
258     extend: extend,
259     create: create$2,
260     bind: bind,
261     get lastId () { return lastId; },
262     stamp: stamp,
263     throttle: throttle,
264     wrapNum: wrapNum,
265     falseFn: falseFn,
266     formatNum: formatNum,
267     trim: trim,
268     splitWords: splitWords,
269     setOptions: setOptions,
270     getParamString: getParamString,
271     template: template,
272     isArray: isArray,
273     indexOf: indexOf,
274     emptyImageUrl: emptyImageUrl,
275     requestFn: requestFn,
276     cancelFn: cancelFn,
277     requestAnimFrame: requestAnimFrame,
278     cancelAnimFrame: cancelAnimFrame
279   };
280
281   // @class Class\r
282   // @aka L.Class\r
283 \r
284   // @section\r
285   // @uninheritable\r
286 \r
287   // Thanks to John Resig and Dean Edwards for inspiration!\r
288 \r
289   function Class() {}\r
290 \r
291   Class.extend = function (props) {\r
292 \r
293         // @function extend(props: Object): Function\r
294         // [Extends the current class](#class-inheritance) given the properties to be included.\r
295         // Returns a Javascript function that is a class constructor (to be called with `new`).\r
296         var NewClass = function () {\r
297 \r
298                 setOptions(this);\r
299 \r
300                 // call the constructor\r
301                 if (this.initialize) {\r
302                         this.initialize.apply(this, arguments);\r
303                 }\r
304 \r
305                 // call all constructor hooks\r
306                 this.callInitHooks();\r
307         };\r
308 \r
309         var parentProto = NewClass.__super__ = this.prototype;\r
310 \r
311         var proto = create$2(parentProto);\r
312         proto.constructor = NewClass;\r
313 \r
314         NewClass.prototype = proto;\r
315 \r
316         // inherit parent's statics\r
317         for (var i in this) {\r
318                 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {\r
319                         NewClass[i] = this[i];\r
320                 }\r
321         }\r
322 \r
323         // mix static properties into the class\r
324         if (props.statics) {\r
325                 extend(NewClass, props.statics);\r
326         }\r
327 \r
328         // mix includes into the prototype\r
329         if (props.includes) {\r
330                 checkDeprecatedMixinEvents(props.includes);\r
331                 extend.apply(null, [proto].concat(props.includes));\r
332         }\r
333 \r
334         // mix given properties into the prototype\r
335         extend(proto, props);\r
336         delete proto.statics;\r
337         delete proto.includes;\r
338 \r
339         // merge options\r
340         if (proto.options) {\r
341                 proto.options = parentProto.options ? create$2(parentProto.options) : {};\r
342                 extend(proto.options, props.options);\r
343         }\r
344 \r
345         proto._initHooks = [];\r
346 \r
347         // add method for calling all hooks\r
348         proto.callInitHooks = function () {\r
349 \r
350                 if (this._initHooksCalled) { return; }\r
351 \r
352                 if (parentProto.callInitHooks) {\r
353                         parentProto.callInitHooks.call(this);\r
354                 }\r
355 \r
356                 this._initHooksCalled = true;\r
357 \r
358                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {\r
359                         proto._initHooks[i].call(this);\r
360                 }\r
361         };\r
362 \r
363         return NewClass;\r
364   };\r
365 \r
366 \r
367   // @function include(properties: Object): this\r
368   // [Includes a mixin](#class-includes) into the current class.\r
369   Class.include = function (props) {\r
370         var parentOptions = this.prototype.options;\r
371         extend(this.prototype, props);\r
372         if (props.options) {\r
373                 this.prototype.options = parentOptions;\r
374                 this.mergeOptions(props.options);\r
375         }\r
376         return this;\r
377   };\r
378 \r
379   // @function mergeOptions(options: Object): this\r
380   // [Merges `options`](#class-options) into the defaults of the class.\r
381   Class.mergeOptions = function (options) {\r
382         extend(this.prototype.options, options);\r
383         return this;\r
384   };\r
385 \r
386   // @function addInitHook(fn: Function): this\r
387   // Adds a [constructor hook](#class-constructor-hooks) to the class.\r
388   Class.addInitHook = function (fn) { // (Function) || (String, args...)\r
389         var args = Array.prototype.slice.call(arguments, 1);\r
390 \r
391         var init = typeof fn === 'function' ? fn : function () {\r
392                 this[fn].apply(this, args);\r
393         };\r
394 \r
395         this.prototype._initHooks = this.prototype._initHooks || [];\r
396         this.prototype._initHooks.push(init);\r
397         return this;\r
398   };\r
399 \r
400   function checkDeprecatedMixinEvents(includes) {\r
401         /* global L: true */\r
402         if (typeof L === 'undefined' || !L || !L.Mixin) { return; }\r
403 \r
404         includes = isArray(includes) ? includes : [includes];\r
405 \r
406         for (var i = 0; i < includes.length; i++) {\r
407                 if (includes[i] === L.Mixin.Events) {\r
408                         console.warn('Deprecated include of L.Mixin.Events: ' +\r
409                                 'this property will be removed in future releases, ' +\r
410                                 'please inherit from L.Evented instead.', new Error().stack);\r
411                 }\r
412         }\r
413   }
414
415   /*\r
416    * @class Evented\r
417    * @aka L.Evented\r
418    * @inherits Class\r
419    *\r
420    * 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).\r
421    *\r
422    * @example\r
423    *\r
424    * ```js\r
425    * map.on('click', function(e) {\r
426    *    alert(e.latlng);\r
427    * } );\r
428    * ```\r
429    *\r
430    * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:\r
431    *\r
432    * ```js\r
433    * function onClick(e) { ... }\r
434    *\r
435    * map.on('click', onClick);\r
436    * map.off('click', onClick);\r
437    * ```\r
438    */\r
439 \r
440   var Events = {\r
441         /* @method on(type: String, fn: Function, context?: Object): this\r
442          * 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'`).\r
443          *\r
444          * @alternative\r
445          * @method on(eventMap: Object): this\r
446          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`\r
447          */\r
448         on: function (types, fn, context) {\r
449 \r
450                 // types can be a map of types/handlers\r
451                 if (typeof types === 'object') {\r
452                         for (var type in types) {\r
453                                 // we don't process space-separated events here for performance;\r
454                                 // it's a hot path since Layer uses the on(obj) syntax\r
455                                 this._on(type, types[type], fn);\r
456                         }\r
457 \r
458                 } else {\r
459                         // types can be a string of space-separated words\r
460                         types = splitWords(types);\r
461 \r
462                         for (var i = 0, len = types.length; i < len; i++) {\r
463                                 this._on(types[i], fn, context);\r
464                         }\r
465                 }\r
466 \r
467                 return this;\r
468         },\r
469 \r
470         /* @method off(type: String, fn?: Function, context?: Object): this\r
471          * 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.\r
472          *\r
473          * @alternative\r
474          * @method off(eventMap: Object): this\r
475          * Removes a set of type/listener pairs.\r
476          *\r
477          * @alternative\r
478          * @method off: this\r
479          * Removes all listeners to all events on the object. This includes implicitly attached events.\r
480          */\r
481         off: function (types, fn, context) {\r
482 \r
483                 if (!arguments.length) {\r
484                         // clear all listeners if called without arguments\r
485                         delete this._events;\r
486 \r
487                 } else if (typeof types === 'object') {\r
488                         for (var type in types) {\r
489                                 this._off(type, types[type], fn);\r
490                         }\r
491 \r
492                 } else {\r
493                         types = splitWords(types);\r
494 \r
495                         var removeAll = arguments.length === 1;\r
496                         for (var i = 0, len = types.length; i < len; i++) {\r
497                                 if (removeAll) {\r
498                                         this._off(types[i]);\r
499                                 } else {\r
500                                         this._off(types[i], fn, context);\r
501                                 }\r
502                         }\r
503                 }\r
504 \r
505                 return this;\r
506         },\r
507 \r
508         // attach listener (without syntactic sugar now)\r
509         _on: function (type, fn, context, _once) {\r
510                 if (typeof fn !== 'function') {\r
511                         console.warn('wrong listener type: ' + typeof fn);\r
512                         return;\r
513                 }\r
514 \r
515                 // check if fn already there\r
516                 if (this._listens(type, fn, context) !== false) {\r
517                         return;\r
518                 }\r
519 \r
520                 if (context === this) {\r
521                         // Less memory footprint.\r
522                         context = undefined;\r
523                 }\r
524 \r
525                 var newListener = {fn: fn, ctx: context};\r
526                 if (_once) {\r
527                         newListener.once = true;\r
528                 }\r
529 \r
530                 this._events = this._events || {};\r
531                 this._events[type] = this._events[type] || [];\r
532                 this._events[type].push(newListener);\r
533         },\r
534 \r
535         _off: function (type, fn, context) {\r
536                 var listeners,\r
537                     i,\r
538                     len;\r
539 \r
540                 if (!this._events) {\r
541                         return;\r
542                 }\r
543 \r
544                 listeners = this._events[type];\r
545                 if (!listeners) {\r
546                         return;\r
547                 }\r
548 \r
549                 if (arguments.length === 1) { // remove all\r
550                         if (this._firingCount) {\r
551                                 // Set all removed listeners to noop\r
552                                 // so they are not called if remove happens in fire\r
553                                 for (i = 0, len = listeners.length; i < len; i++) {\r
554                                         listeners[i].fn = falseFn;\r
555                                 }\r
556                         }\r
557                         // clear all listeners for a type if function isn't specified\r
558                         delete this._events[type];\r
559                         return;\r
560                 }\r
561 \r
562                 if (typeof fn !== 'function') {\r
563                         console.warn('wrong listener type: ' + typeof fn);\r
564                         return;\r
565                 }\r
566 \r
567                 // find fn and remove it\r
568                 var index = this._listens(type, fn, context);\r
569                 if (index !== false) {\r
570                         var listener = listeners[index];\r
571                         if (this._firingCount) {\r
572                                 // set the removed listener to noop so that's not called if remove happens in fire\r
573                                 listener.fn = falseFn;\r
574 \r
575                                 /* copy array in case events are being fired */\r
576                                 this._events[type] = listeners = listeners.slice();\r
577                         }\r
578                         listeners.splice(index, 1);\r
579                 }\r
580         },\r
581 \r
582         // @method fire(type: String, data?: Object, propagate?: Boolean): this\r
583         // Fires an event of the specified type. You can optionally provide a data\r
584         // object — the first argument of the listener function will contain its\r
585         // properties. The event can optionally be propagated to event parents.\r
586         fire: function (type, data, propagate) {\r
587                 if (!this.listens(type, propagate)) { return this; }\r
588 \r
589                 var event = extend({}, data, {\r
590                         type: type,\r
591                         target: this,\r
592                         sourceTarget: data && data.sourceTarget || this\r
593                 });\r
594 \r
595                 if (this._events) {\r
596                         var listeners = this._events[type];\r
597                         if (listeners) {\r
598                                 this._firingCount = (this._firingCount + 1) || 1;\r
599                                 for (var i = 0, len = listeners.length; i < len; i++) {\r
600                                         var l = listeners[i];\r
601                                         // off overwrites l.fn, so we need to copy fn to a var\r
602                                         var fn = l.fn;\r
603                                         if (l.once) {\r
604                                                 this.off(type, fn, l.ctx);\r
605                                         }\r
606                                         fn.call(l.ctx || this, event);\r
607                                 }\r
608 \r
609                                 this._firingCount--;\r
610                         }\r
611                 }\r
612 \r
613                 if (propagate) {\r
614                         // propagate the event to parents (set with addEventParent)\r
615                         this._propagateEvent(event);\r
616                 }\r
617 \r
618                 return this;\r
619         },\r
620 \r
621         // @method listens(type: String, propagate?: Boolean): Boolean\r
622         // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean\r
623         // Returns `true` if a particular event type has any listeners attached to it.\r
624         // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.\r
625         listens: function (type, fn, context, propagate) {\r
626                 if (typeof type !== 'string') {\r
627                         console.warn('"string" type argument expected');\r
628                 }\r
629 \r
630                 // we don't overwrite the input `fn` value, because we need to use it for propagation\r
631                 var _fn = fn;\r
632                 if (typeof fn !== 'function') {\r
633                         propagate = !!fn;\r
634                         _fn = undefined;\r
635                         context = undefined;\r
636                 }\r
637 \r
638                 var listeners = this._events && this._events[type];\r
639                 if (listeners && listeners.length) {\r
640                         if (this._listens(type, _fn, context) !== false) {\r
641                                 return true;\r
642                         }\r
643                 }\r
644 \r
645                 if (propagate) {\r
646                         // also check parents for listeners if event propagates\r
647                         for (var id in this._eventParents) {\r
648                                 if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }\r
649                         }\r
650                 }\r
651                 return false;\r
652         },\r
653 \r
654         // returns the index (number) or false\r
655         _listens: function (type, fn, context) {\r
656                 if (!this._events) {\r
657                         return false;\r
658                 }\r
659 \r
660                 var listeners = this._events[type] || [];\r
661                 if (!fn) {\r
662                         return !!listeners.length;\r
663                 }\r
664 \r
665                 if (context === this) {\r
666                         // Less memory footprint.\r
667                         context = undefined;\r
668                 }\r
669 \r
670                 for (var i = 0, len = listeners.length; i < len; i++) {\r
671                         if (listeners[i].fn === fn && listeners[i].ctx === context) {\r
672                                 return i;\r
673                         }\r
674                 }\r
675                 return false;\r
676 \r
677         },\r
678 \r
679         // @method once(…): this\r
680         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.\r
681         once: function (types, fn, context) {\r
682 \r
683                 // types can be a map of types/handlers\r
684                 if (typeof types === 'object') {\r
685                         for (var type in types) {\r
686                                 // we don't process space-separated events here for performance;\r
687                                 // it's a hot path since Layer uses the on(obj) syntax\r
688                                 this._on(type, types[type], fn, true);\r
689                         }\r
690 \r
691                 } else {\r
692                         // types can be a string of space-separated words\r
693                         types = splitWords(types);\r
694 \r
695                         for (var i = 0, len = types.length; i < len; i++) {\r
696                                 this._on(types[i], fn, context, true);\r
697                         }\r
698                 }\r
699 \r
700                 return this;\r
701         },\r
702 \r
703         // @method addEventParent(obj: Evented): this\r
704         // Adds an event parent - an `Evented` that will receive propagated events\r
705         addEventParent: function (obj) {\r
706                 this._eventParents = this._eventParents || {};\r
707                 this._eventParents[stamp(obj)] = obj;\r
708                 return this;\r
709         },\r
710 \r
711         // @method removeEventParent(obj: Evented): this\r
712         // Removes an event parent, so it will stop receiving propagated events\r
713         removeEventParent: function (obj) {\r
714                 if (this._eventParents) {\r
715                         delete this._eventParents[stamp(obj)];\r
716                 }\r
717                 return this;\r
718         },\r
719 \r
720         _propagateEvent: function (e) {\r
721                 for (var id in this._eventParents) {\r
722                         this._eventParents[id].fire(e.type, extend({\r
723                                 layer: e.target,\r
724                                 propagatedFrom: e.target\r
725                         }, e), true);\r
726                 }\r
727         }\r
728   };\r
729 \r
730   // aliases; we should ditch those eventually\r
731 \r
732   // @method addEventListener(…): this\r
733   // Alias to [`on(…)`](#evented-on)\r
734   Events.addEventListener = Events.on;\r
735 \r
736   // @method removeEventListener(…): this\r
737   // Alias to [`off(…)`](#evented-off)\r
738 \r
739   // @method clearAllEventListeners(…): this\r
740   // Alias to [`off()`](#evented-off)\r
741   Events.removeEventListener = Events.clearAllEventListeners = Events.off;\r
742 \r
743   // @method addOneTimeEventListener(…): this\r
744   // Alias to [`once(…)`](#evented-once)\r
745   Events.addOneTimeEventListener = Events.once;\r
746 \r
747   // @method fireEvent(…): this\r
748   // Alias to [`fire(…)`](#evented-fire)\r
749   Events.fireEvent = Events.fire;\r
750 \r
751   // @method hasEventListeners(…): Boolean\r
752   // Alias to [`listens(…)`](#evented-listens)\r
753   Events.hasEventListeners = Events.listens;\r
754 \r
755   var Evented = Class.extend(Events);
756
757   /*\r
758    * @class Point\r
759    * @aka L.Point\r
760    *\r
761    * Represents a point with `x` and `y` coordinates in pixels.\r
762    *\r
763    * @example\r
764    *\r
765    * ```js\r
766    * var point = L.point(200, 300);\r
767    * ```\r
768    *\r
769    * 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:\r
770    *\r
771    * ```js\r
772    * map.panBy([200, 300]);\r
773    * map.panBy(L.point(200, 300));\r
774    * ```\r
775    *\r
776    * Note that `Point` does not inherit from Leaflet's `Class` object,\r
777    * which means new classes can't inherit from it, and new methods\r
778    * can't be added to it with the `include` function.\r
779    */\r
780 \r
781   function Point(x, y, round) {\r
782         // @property x: Number; The `x` coordinate of the point\r
783         this.x = (round ? Math.round(x) : x);\r
784         // @property y: Number; The `y` coordinate of the point\r
785         this.y = (round ? Math.round(y) : y);\r
786   }\r
787 \r
788   var trunc = Math.trunc || function (v) {\r
789         return v > 0 ? Math.floor(v) : Math.ceil(v);\r
790   };\r
791 \r
792   Point.prototype = {\r
793 \r
794         // @method clone(): Point\r
795         // Returns a copy of the current point.\r
796         clone: function () {\r
797                 return new Point(this.x, this.y);\r
798         },\r
799 \r
800         // @method add(otherPoint: Point): Point\r
801         // Returns the result of addition of the current and the given points.\r
802         add: function (point) {\r
803                 // non-destructive, returns a new point\r
804                 return this.clone()._add(toPoint(point));\r
805         },\r
806 \r
807         _add: function (point) {\r
808                 // destructive, used directly for performance in situations where it's safe to modify existing point\r
809                 this.x += point.x;\r
810                 this.y += point.y;\r
811                 return this;\r
812         },\r
813 \r
814         // @method subtract(otherPoint: Point): Point\r
815         // Returns the result of subtraction of the given point from the current.\r
816         subtract: function (point) {\r
817                 return this.clone()._subtract(toPoint(point));\r
818         },\r
819 \r
820         _subtract: function (point) {\r
821                 this.x -= point.x;\r
822                 this.y -= point.y;\r
823                 return this;\r
824         },\r
825 \r
826         // @method divideBy(num: Number): Point\r
827         // Returns the result of division of the current point by the given number.\r
828         divideBy: function (num) {\r
829                 return this.clone()._divideBy(num);\r
830         },\r
831 \r
832         _divideBy: function (num) {\r
833                 this.x /= num;\r
834                 this.y /= num;\r
835                 return this;\r
836         },\r
837 \r
838         // @method multiplyBy(num: Number): Point\r
839         // Returns the result of multiplication of the current point by the given number.\r
840         multiplyBy: function (num) {\r
841                 return this.clone()._multiplyBy(num);\r
842         },\r
843 \r
844         _multiplyBy: function (num) {\r
845                 this.x *= num;\r
846                 this.y *= num;\r
847                 return this;\r
848         },\r
849 \r
850         // @method scaleBy(scale: Point): Point\r
851         // Multiply each coordinate of the current point by each coordinate of\r
852         // `scale`. In linear algebra terms, multiply the point by the\r
853         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)\r
854         // defined by `scale`.\r
855         scaleBy: function (point) {\r
856                 return new Point(this.x * point.x, this.y * point.y);\r
857         },\r
858 \r
859         // @method unscaleBy(scale: Point): Point\r
860         // Inverse of `scaleBy`. Divide each coordinate of the current point by\r
861         // each coordinate of `scale`.\r
862         unscaleBy: function (point) {\r
863                 return new Point(this.x / point.x, this.y / point.y);\r
864         },\r
865 \r
866         // @method round(): Point\r
867         // Returns a copy of the current point with rounded coordinates.\r
868         round: function () {\r
869                 return this.clone()._round();\r
870         },\r
871 \r
872         _round: function () {\r
873                 this.x = Math.round(this.x);\r
874                 this.y = Math.round(this.y);\r
875                 return this;\r
876         },\r
877 \r
878         // @method floor(): Point\r
879         // Returns a copy of the current point with floored coordinates (rounded down).\r
880         floor: function () {\r
881                 return this.clone()._floor();\r
882         },\r
883 \r
884         _floor: function () {\r
885                 this.x = Math.floor(this.x);\r
886                 this.y = Math.floor(this.y);\r
887                 return this;\r
888         },\r
889 \r
890         // @method ceil(): Point\r
891         // Returns a copy of the current point with ceiled coordinates (rounded up).\r
892         ceil: function () {\r
893                 return this.clone()._ceil();\r
894         },\r
895 \r
896         _ceil: function () {\r
897                 this.x = Math.ceil(this.x);\r
898                 this.y = Math.ceil(this.y);\r
899                 return this;\r
900         },\r
901 \r
902         // @method trunc(): Point\r
903         // Returns a copy of the current point with truncated coordinates (rounded towards zero).\r
904         trunc: function () {\r
905                 return this.clone()._trunc();\r
906         },\r
907 \r
908         _trunc: function () {\r
909                 this.x = trunc(this.x);\r
910                 this.y = trunc(this.y);\r
911                 return this;\r
912         },\r
913 \r
914         // @method distanceTo(otherPoint: Point): Number\r
915         // Returns the cartesian distance between the current and the given points.\r
916         distanceTo: function (point) {\r
917                 point = toPoint(point);\r
918 \r
919                 var x = point.x - this.x,\r
920                     y = point.y - this.y;\r
921 \r
922                 return Math.sqrt(x * x + y * y);\r
923         },\r
924 \r
925         // @method equals(otherPoint: Point): Boolean\r
926         // Returns `true` if the given point has the same coordinates.\r
927         equals: function (point) {\r
928                 point = toPoint(point);\r
929 \r
930                 return point.x === this.x &&\r
931                        point.y === this.y;\r
932         },\r
933 \r
934         // @method contains(otherPoint: Point): Boolean\r
935         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).\r
936         contains: function (point) {\r
937                 point = toPoint(point);\r
938 \r
939                 return Math.abs(point.x) <= Math.abs(this.x) &&\r
940                        Math.abs(point.y) <= Math.abs(this.y);\r
941         },\r
942 \r
943         // @method toString(): String\r
944         // Returns a string representation of the point for debugging purposes.\r
945         toString: function () {\r
946                 return 'Point(' +\r
947                         formatNum(this.x) + ', ' +\r
948                         formatNum(this.y) + ')';\r
949         }\r
950   };\r
951 \r
952   // @factory L.point(x: Number, y: Number, round?: Boolean)\r
953   // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.\r
954 \r
955   // @alternative\r
956   // @factory L.point(coords: Number[])\r
957   // Expects an array of the form `[x, y]` instead.\r
958 \r
959   // @alternative\r
960   // @factory L.point(coords: Object)\r
961   // Expects a plain object of the form `{x: Number, y: Number}` instead.\r
962   function toPoint(x, y, round) {\r
963         if (x instanceof Point) {\r
964                 return x;\r
965         }\r
966         if (isArray(x)) {\r
967                 return new Point(x[0], x[1]);\r
968         }\r
969         if (x === undefined || x === null) {\r
970                 return x;\r
971         }\r
972         if (typeof x === 'object' && 'x' in x && 'y' in x) {\r
973                 return new Point(x.x, x.y);\r
974         }\r
975         return new Point(x, y, round);\r
976   }
977
978   /*\r
979    * @class Bounds\r
980    * @aka L.Bounds\r
981    *\r
982    * Represents a rectangular area in pixel coordinates.\r
983    *\r
984    * @example\r
985    *\r
986    * ```js\r
987    * var p1 = L.point(10, 10),\r
988    * p2 = L.point(40, 60),\r
989    * bounds = L.bounds(p1, p2);\r
990    * ```\r
991    *\r
992    * 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:\r
993    *\r
994    * ```js\r
995    * otherBounds.intersects([[10, 10], [40, 60]]);\r
996    * ```\r
997    *\r
998    * Note that `Bounds` does not inherit from Leaflet's `Class` object,\r
999    * which means new classes can't inherit from it, and new methods\r
1000    * can't be added to it with the `include` function.\r
1001    */\r
1002 \r
1003   function Bounds(a, b) {\r
1004         if (!a) { return; }\r
1005 \r
1006         var points = b ? [a, b] : a;\r
1007 \r
1008         for (var i = 0, len = points.length; i < len; i++) {\r
1009                 this.extend(points[i]);\r
1010         }\r
1011   }\r
1012 \r
1013   Bounds.prototype = {\r
1014         // @method extend(point: Point): this\r
1015         // Extends the bounds to contain the given point.\r
1016 \r
1017         // @alternative\r
1018         // @method extend(otherBounds: Bounds): this\r
1019         // Extend the bounds to contain the given bounds\r
1020         extend: function (obj) {\r
1021                 var min2, max2;\r
1022                 if (!obj) { return this; }\r
1023 \r
1024                 if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {\r
1025                         min2 = max2 = toPoint(obj);\r
1026                 } else {\r
1027                         obj = toBounds(obj);\r
1028                         min2 = obj.min;\r
1029                         max2 = obj.max;\r
1030 \r
1031                         if (!min2 || !max2) { return this; }\r
1032                 }\r
1033 \r
1034                 // @property min: Point\r
1035                 // The top left corner of the rectangle.\r
1036                 // @property max: Point\r
1037                 // The bottom right corner of the rectangle.\r
1038                 if (!this.min && !this.max) {\r
1039                         this.min = min2.clone();\r
1040                         this.max = max2.clone();\r
1041                 } else {\r
1042                         this.min.x = Math.min(min2.x, this.min.x);\r
1043                         this.max.x = Math.max(max2.x, this.max.x);\r
1044                         this.min.y = Math.min(min2.y, this.min.y);\r
1045                         this.max.y = Math.max(max2.y, this.max.y);\r
1046                 }\r
1047                 return this;\r
1048         },\r
1049 \r
1050         // @method getCenter(round?: Boolean): Point\r
1051         // Returns the center point of the bounds.\r
1052         getCenter: function (round) {\r
1053                 return toPoint(\r
1054                         (this.min.x + this.max.x) / 2,\r
1055                         (this.min.y + this.max.y) / 2, round);\r
1056         },\r
1057 \r
1058         // @method getBottomLeft(): Point\r
1059         // Returns the bottom-left point of the bounds.\r
1060         getBottomLeft: function () {\r
1061                 return toPoint(this.min.x, this.max.y);\r
1062         },\r
1063 \r
1064         // @method getTopRight(): Point\r
1065         // Returns the top-right point of the bounds.\r
1066         getTopRight: function () { // -> Point\r
1067                 return toPoint(this.max.x, this.min.y);\r
1068         },\r
1069 \r
1070         // @method getTopLeft(): Point\r
1071         // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).\r
1072         getTopLeft: function () {\r
1073                 return this.min; // left, top\r
1074         },\r
1075 \r
1076         // @method getBottomRight(): Point\r
1077         // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).\r
1078         getBottomRight: function () {\r
1079                 return this.max; // right, bottom\r
1080         },\r
1081 \r
1082         // @method getSize(): Point\r
1083         // Returns the size of the given bounds\r
1084         getSize: function () {\r
1085                 return this.max.subtract(this.min);\r
1086         },\r
1087 \r
1088         // @method contains(otherBounds: Bounds): Boolean\r
1089         // Returns `true` if the rectangle contains the given one.\r
1090         // @alternative\r
1091         // @method contains(point: Point): Boolean\r
1092         // Returns `true` if the rectangle contains the given point.\r
1093         contains: function (obj) {\r
1094                 var min, max;\r
1095 \r
1096                 if (typeof obj[0] === 'number' || obj instanceof Point) {\r
1097                         obj = toPoint(obj);\r
1098                 } else {\r
1099                         obj = toBounds(obj);\r
1100                 }\r
1101 \r
1102                 if (obj instanceof Bounds) {\r
1103                         min = obj.min;\r
1104                         max = obj.max;\r
1105                 } else {\r
1106                         min = max = obj;\r
1107                 }\r
1108 \r
1109                 return (min.x >= this.min.x) &&\r
1110                        (max.x <= this.max.x) &&\r
1111                        (min.y >= this.min.y) &&\r
1112                        (max.y <= this.max.y);\r
1113         },\r
1114 \r
1115         // @method intersects(otherBounds: Bounds): Boolean\r
1116         // Returns `true` if the rectangle intersects the given bounds. Two bounds\r
1117         // intersect if they have at least one point in common.\r
1118         intersects: function (bounds) { // (Bounds) -> Boolean\r
1119                 bounds = toBounds(bounds);\r
1120 \r
1121                 var min = this.min,\r
1122                     max = this.max,\r
1123                     min2 = bounds.min,\r
1124                     max2 = bounds.max,\r
1125                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),\r
1126                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);\r
1127 \r
1128                 return xIntersects && yIntersects;\r
1129         },\r
1130 \r
1131         // @method overlaps(otherBounds: Bounds): Boolean\r
1132         // Returns `true` if the rectangle overlaps the given bounds. Two bounds\r
1133         // overlap if their intersection is an area.\r
1134         overlaps: function (bounds) { // (Bounds) -> Boolean\r
1135                 bounds = toBounds(bounds);\r
1136 \r
1137                 var min = this.min,\r
1138                     max = this.max,\r
1139                     min2 = bounds.min,\r
1140                     max2 = bounds.max,\r
1141                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),\r
1142                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);\r
1143 \r
1144                 return xOverlaps && yOverlaps;\r
1145         },\r
1146 \r
1147         // @method isValid(): Boolean\r
1148         // Returns `true` if the bounds are properly initialized.\r
1149         isValid: function () {\r
1150                 return !!(this.min && this.max);\r
1151         },\r
1152 \r
1153 \r
1154         // @method pad(bufferRatio: Number): Bounds\r
1155         // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.\r
1156         // For example, a ratio of 0.5 extends the bounds by 50% in each direction.\r
1157         // Negative values will retract the bounds.\r
1158         pad: function (bufferRatio) {\r
1159                 var min = this.min,\r
1160                 max = this.max,\r
1161                 heightBuffer = Math.abs(min.x - max.x) * bufferRatio,\r
1162                 widthBuffer = Math.abs(min.y - max.y) * bufferRatio;\r
1163 \r
1164 \r
1165                 return toBounds(\r
1166                         toPoint(min.x - heightBuffer, min.y - widthBuffer),\r
1167                         toPoint(max.x + heightBuffer, max.y + widthBuffer));\r
1168         },\r
1169 \r
1170 \r
1171         // @method equals(otherBounds: Bounds): Boolean\r
1172         // Returns `true` if the rectangle is equivalent to the given bounds.\r
1173         equals: function (bounds) {\r
1174                 if (!bounds) { return false; }\r
1175 \r
1176                 bounds = toBounds(bounds);\r
1177 \r
1178                 return this.min.equals(bounds.getTopLeft()) &&\r
1179                         this.max.equals(bounds.getBottomRight());\r
1180         },\r
1181   };\r
1182 \r
1183 \r
1184   // @factory L.bounds(corner1: Point, corner2: Point)\r
1185   // Creates a Bounds object from two corners coordinate pairs.\r
1186   // @alternative\r
1187   // @factory L.bounds(points: Point[])\r
1188   // Creates a Bounds object from the given array of points.\r
1189   function toBounds(a, b) {\r
1190         if (!a || a instanceof Bounds) {\r
1191                 return a;\r
1192         }\r
1193         return new Bounds(a, b);\r
1194   }
1195
1196   /*\r
1197    * @class LatLngBounds\r
1198    * @aka L.LatLngBounds\r
1199    *\r
1200    * Represents a rectangular geographical area on a map.\r
1201    *\r
1202    * @example\r
1203    *\r
1204    * ```js\r
1205    * var corner1 = L.latLng(40.712, -74.227),\r
1206    * corner2 = L.latLng(40.774, -74.125),\r
1207    * bounds = L.latLngBounds(corner1, corner2);\r
1208    * ```\r
1209    *\r
1210    * 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:\r
1211    *\r
1212    * ```js\r
1213    * map.fitBounds([\r
1214    *    [40.712, -74.227],\r
1215    *    [40.774, -74.125]\r
1216    * ]);\r
1217    * ```\r
1218    *\r
1219    * 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.\r
1220    *\r
1221    * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,\r
1222    * which means new classes can't inherit from it, and new methods\r
1223    * can't be added to it with the `include` function.\r
1224    */\r
1225 \r
1226   function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])\r
1227         if (!corner1) { return; }\r
1228 \r
1229         var latlngs = corner2 ? [corner1, corner2] : corner1;\r
1230 \r
1231         for (var i = 0, len = latlngs.length; i < len; i++) {\r
1232                 this.extend(latlngs[i]);\r
1233         }\r
1234   }\r
1235 \r
1236   LatLngBounds.prototype = {\r
1237 \r
1238         // @method extend(latlng: LatLng): this\r
1239         // Extend the bounds to contain the given point\r
1240 \r
1241         // @alternative\r
1242         // @method extend(otherBounds: LatLngBounds): this\r
1243         // Extend the bounds to contain the given bounds\r
1244         extend: function (obj) {\r
1245                 var sw = this._southWest,\r
1246                     ne = this._northEast,\r
1247                     sw2, ne2;\r
1248 \r
1249                 if (obj instanceof LatLng) {\r
1250                         sw2 = obj;\r
1251                         ne2 = obj;\r
1252 \r
1253                 } else if (obj instanceof LatLngBounds) {\r
1254                         sw2 = obj._southWest;\r
1255                         ne2 = obj._northEast;\r
1256 \r
1257                         if (!sw2 || !ne2) { return this; }\r
1258 \r
1259                 } else {\r
1260                         return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;\r
1261                 }\r
1262 \r
1263                 if (!sw && !ne) {\r
1264                         this._southWest = new LatLng(sw2.lat, sw2.lng);\r
1265                         this._northEast = new LatLng(ne2.lat, ne2.lng);\r
1266                 } else {\r
1267                         sw.lat = Math.min(sw2.lat, sw.lat);\r
1268                         sw.lng = Math.min(sw2.lng, sw.lng);\r
1269                         ne.lat = Math.max(ne2.lat, ne.lat);\r
1270                         ne.lng = Math.max(ne2.lng, ne.lng);\r
1271                 }\r
1272 \r
1273                 return this;\r
1274         },\r
1275 \r
1276         // @method pad(bufferRatio: Number): LatLngBounds\r
1277         // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.\r
1278         // For example, a ratio of 0.5 extends the bounds by 50% in each direction.\r
1279         // Negative values will retract the bounds.\r
1280         pad: function (bufferRatio) {\r
1281                 var sw = this._southWest,\r
1282                     ne = this._northEast,\r
1283                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,\r
1284                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;\r
1285 \r
1286                 return new LatLngBounds(\r
1287                         new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),\r
1288                         new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));\r
1289         },\r
1290 \r
1291         // @method getCenter(): LatLng\r
1292         // Returns the center point of the bounds.\r
1293         getCenter: function () {\r
1294                 return new LatLng(\r
1295                         (this._southWest.lat + this._northEast.lat) / 2,\r
1296                         (this._southWest.lng + this._northEast.lng) / 2);\r
1297         },\r
1298 \r
1299         // @method getSouthWest(): LatLng\r
1300         // Returns the south-west point of the bounds.\r
1301         getSouthWest: function () {\r
1302                 return this._southWest;\r
1303         },\r
1304 \r
1305         // @method getNorthEast(): LatLng\r
1306         // Returns the north-east point of the bounds.\r
1307         getNorthEast: function () {\r
1308                 return this._northEast;\r
1309         },\r
1310 \r
1311         // @method getNorthWest(): LatLng\r
1312         // Returns the north-west point of the bounds.\r
1313         getNorthWest: function () {\r
1314                 return new LatLng(this.getNorth(), this.getWest());\r
1315         },\r
1316 \r
1317         // @method getSouthEast(): LatLng\r
1318         // Returns the south-east point of the bounds.\r
1319         getSouthEast: function () {\r
1320                 return new LatLng(this.getSouth(), this.getEast());\r
1321         },\r
1322 \r
1323         // @method getWest(): Number\r
1324         // Returns the west longitude of the bounds\r
1325         getWest: function () {\r
1326                 return this._southWest.lng;\r
1327         },\r
1328 \r
1329         // @method getSouth(): Number\r
1330         // Returns the south latitude of the bounds\r
1331         getSouth: function () {\r
1332                 return this._southWest.lat;\r
1333         },\r
1334 \r
1335         // @method getEast(): Number\r
1336         // Returns the east longitude of the bounds\r
1337         getEast: function () {\r
1338                 return this._northEast.lng;\r
1339         },\r
1340 \r
1341         // @method getNorth(): Number\r
1342         // Returns the north latitude of the bounds\r
1343         getNorth: function () {\r
1344                 return this._northEast.lat;\r
1345         },\r
1346 \r
1347         // @method contains(otherBounds: LatLngBounds): Boolean\r
1348         // Returns `true` if the rectangle contains the given one.\r
1349 \r
1350         // @alternative\r
1351         // @method contains (latlng: LatLng): Boolean\r
1352         // Returns `true` if the rectangle contains the given point.\r
1353         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean\r
1354                 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {\r
1355                         obj = toLatLng(obj);\r
1356                 } else {\r
1357                         obj = toLatLngBounds(obj);\r
1358                 }\r
1359 \r
1360                 var sw = this._southWest,\r
1361                     ne = this._northEast,\r
1362                     sw2, ne2;\r
1363 \r
1364                 if (obj instanceof LatLngBounds) {\r
1365                         sw2 = obj.getSouthWest();\r
1366                         ne2 = obj.getNorthEast();\r
1367                 } else {\r
1368                         sw2 = ne2 = obj;\r
1369                 }\r
1370 \r
1371                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&\r
1372                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);\r
1373         },\r
1374 \r
1375         // @method intersects(otherBounds: LatLngBounds): Boolean\r
1376         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.\r
1377         intersects: function (bounds) {\r
1378                 bounds = toLatLngBounds(bounds);\r
1379 \r
1380                 var sw = this._southWest,\r
1381                     ne = this._northEast,\r
1382                     sw2 = bounds.getSouthWest(),\r
1383                     ne2 = bounds.getNorthEast(),\r
1384 \r
1385                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),\r
1386                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);\r
1387 \r
1388                 return latIntersects && lngIntersects;\r
1389         },\r
1390 \r
1391         // @method overlaps(otherBounds: LatLngBounds): Boolean\r
1392         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.\r
1393         overlaps: function (bounds) {\r
1394                 bounds = toLatLngBounds(bounds);\r
1395 \r
1396                 var sw = this._southWest,\r
1397                     ne = this._northEast,\r
1398                     sw2 = bounds.getSouthWest(),\r
1399                     ne2 = bounds.getNorthEast(),\r
1400 \r
1401                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),\r
1402                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);\r
1403 \r
1404                 return latOverlaps && lngOverlaps;\r
1405         },\r
1406 \r
1407         // @method toBBoxString(): String\r
1408         // 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.\r
1409         toBBoxString: function () {\r
1410                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');\r
1411         },\r
1412 \r
1413         // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean\r
1414         // 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.\r
1415         equals: function (bounds, maxMargin) {\r
1416                 if (!bounds) { return false; }\r
1417 \r
1418                 bounds = toLatLngBounds(bounds);\r
1419 \r
1420                 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&\r
1421                        this._northEast.equals(bounds.getNorthEast(), maxMargin);\r
1422         },\r
1423 \r
1424         // @method isValid(): Boolean\r
1425         // Returns `true` if the bounds are properly initialized.\r
1426         isValid: function () {\r
1427                 return !!(this._southWest && this._northEast);\r
1428         }\r
1429   };\r
1430 \r
1431   // TODO International date line?\r
1432 \r
1433   // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)\r
1434   // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.\r
1435 \r
1436   // @alternative\r
1437   // @factory L.latLngBounds(latlngs: LatLng[])\r
1438   // 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).\r
1439   function toLatLngBounds(a, b) {\r
1440         if (a instanceof LatLngBounds) {\r
1441                 return a;\r
1442         }\r
1443         return new LatLngBounds(a, b);\r
1444   }
1445
1446   /* @class LatLng\r
1447    * @aka L.LatLng\r
1448    *\r
1449    * Represents a geographical point with a certain latitude and longitude.\r
1450    *\r
1451    * @example\r
1452    *\r
1453    * ```\r
1454    * var latlng = L.latLng(50.5, 30.5);\r
1455    * ```\r
1456    *\r
1457    * 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:\r
1458    *\r
1459    * ```\r
1460    * map.panTo([50, 30]);\r
1461    * map.panTo({lon: 30, lat: 50});\r
1462    * map.panTo({lat: 50, lng: 30});\r
1463    * map.panTo(L.latLng(50, 30));\r
1464    * ```\r
1465    *\r
1466    * Note that `LatLng` does not inherit from Leaflet's `Class` object,\r
1467    * which means new classes can't inherit from it, and new methods\r
1468    * can't be added to it with the `include` function.\r
1469    */\r
1470 \r
1471   function LatLng(lat, lng, alt) {\r
1472         if (isNaN(lat) || isNaN(lng)) {\r
1473                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');\r
1474         }\r
1475 \r
1476         // @property lat: Number\r
1477         // Latitude in degrees\r
1478         this.lat = +lat;\r
1479 \r
1480         // @property lng: Number\r
1481         // Longitude in degrees\r
1482         this.lng = +lng;\r
1483 \r
1484         // @property alt: Number\r
1485         // Altitude in meters (optional)\r
1486         if (alt !== undefined) {\r
1487                 this.alt = +alt;\r
1488         }\r
1489   }\r
1490 \r
1491   LatLng.prototype = {\r
1492         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean\r
1493         // 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.\r
1494         equals: function (obj, maxMargin) {\r
1495                 if (!obj) { return false; }\r
1496 \r
1497                 obj = toLatLng(obj);\r
1498 \r
1499                 var margin = Math.max(\r
1500                         Math.abs(this.lat - obj.lat),\r
1501                         Math.abs(this.lng - obj.lng));\r
1502 \r
1503                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);\r
1504         },\r
1505 \r
1506         // @method toString(): String\r
1507         // Returns a string representation of the point (for debugging purposes).\r
1508         toString: function (precision) {\r
1509                 return 'LatLng(' +\r
1510                         formatNum(this.lat, precision) + ', ' +\r
1511                         formatNum(this.lng, precision) + ')';\r
1512         },\r
1513 \r
1514         // @method distanceTo(otherLatLng: LatLng): Number\r
1515         // 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).\r
1516         distanceTo: function (other) {\r
1517                 return Earth.distance(this, toLatLng(other));\r
1518         },\r
1519 \r
1520         // @method wrap(): LatLng\r
1521         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.\r
1522         wrap: function () {\r
1523                 return Earth.wrapLatLng(this);\r
1524         },\r
1525 \r
1526         // @method toBounds(sizeInMeters: Number): LatLngBounds\r
1527         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.\r
1528         toBounds: function (sizeInMeters) {\r
1529                 var latAccuracy = 180 * sizeInMeters / 40075017,\r
1530                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);\r
1531 \r
1532                 return toLatLngBounds(\r
1533                         [this.lat - latAccuracy, this.lng - lngAccuracy],\r
1534                         [this.lat + latAccuracy, this.lng + lngAccuracy]);\r
1535         },\r
1536 \r
1537         clone: function () {\r
1538                 return new LatLng(this.lat, this.lng, this.alt);\r
1539         }\r
1540   };\r
1541 \r
1542 \r
1543 \r
1544   // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng\r
1545   // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).\r
1546 \r
1547   // @alternative\r
1548   // @factory L.latLng(coords: Array): LatLng\r
1549   // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.\r
1550 \r
1551   // @alternative\r
1552   // @factory L.latLng(coords: Object): LatLng\r
1553   // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.\r
1554 \r
1555   function toLatLng(a, b, c) {\r
1556         if (a instanceof LatLng) {\r
1557                 return a;\r
1558         }\r
1559         if (isArray(a) && typeof a[0] !== 'object') {\r
1560                 if (a.length === 3) {\r
1561                         return new LatLng(a[0], a[1], a[2]);\r
1562                 }\r
1563                 if (a.length === 2) {\r
1564                         return new LatLng(a[0], a[1]);\r
1565                 }\r
1566                 return null;\r
1567         }\r
1568         if (a === undefined || a === null) {\r
1569                 return a;\r
1570         }\r
1571         if (typeof a === 'object' && 'lat' in a) {\r
1572                 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);\r
1573         }\r
1574         if (b === undefined) {\r
1575                 return null;\r
1576         }\r
1577         return new LatLng(a, b, c);\r
1578   }
1579
1580   /*\r
1581    * @namespace CRS\r
1582    * @crs L.CRS.Base\r
1583    * Object that defines coordinate reference systems for projecting\r
1584    * geographical points into pixel (screen) coordinates and back (and to\r
1585    * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See\r
1586    * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).\r
1587    *\r
1588    * Leaflet defines the most usual CRSs by default. If you want to use a\r
1589    * CRS not defined by default, take a look at the\r
1590    * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.\r
1591    *\r
1592    * Note that the CRS instances do not inherit from Leaflet's `Class` object,\r
1593    * and can't be instantiated. Also, new classes can't inherit from them,\r
1594    * and methods can't be added to them with the `include` function.\r
1595    */\r
1596 \r
1597   var CRS = {\r
1598         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point\r
1599         // Projects geographical coordinates into pixel coordinates for a given zoom.\r
1600         latLngToPoint: function (latlng, zoom) {\r
1601                 var projectedPoint = this.projection.project(latlng),\r
1602                     scale = this.scale(zoom);\r
1603 \r
1604                 return this.transformation._transform(projectedPoint, scale);\r
1605         },\r
1606 \r
1607         // @method pointToLatLng(point: Point, zoom: Number): LatLng\r
1608         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given\r
1609         // zoom into geographical coordinates.\r
1610         pointToLatLng: function (point, zoom) {\r
1611                 var scale = this.scale(zoom),\r
1612                     untransformedPoint = this.transformation.untransform(point, scale);\r
1613 \r
1614                 return this.projection.unproject(untransformedPoint);\r
1615         },\r
1616 \r
1617         // @method project(latlng: LatLng): Point\r
1618         // Projects geographical coordinates into coordinates in units accepted for\r
1619         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).\r
1620         project: function (latlng) {\r
1621                 return this.projection.project(latlng);\r
1622         },\r
1623 \r
1624         // @method unproject(point: Point): LatLng\r
1625         // Given a projected coordinate returns the corresponding LatLng.\r
1626         // The inverse of `project`.\r
1627         unproject: function (point) {\r
1628                 return this.projection.unproject(point);\r
1629         },\r
1630 \r
1631         // @method scale(zoom: Number): Number\r
1632         // Returns the scale used when transforming projected coordinates into\r
1633         // pixel coordinates for a particular zoom. For example, it returns\r
1634         // `256 * 2^zoom` for Mercator-based CRS.\r
1635         scale: function (zoom) {\r
1636                 return 256 * Math.pow(2, zoom);\r
1637         },\r
1638 \r
1639         // @method zoom(scale: Number): Number\r
1640         // Inverse of `scale()`, returns the zoom level corresponding to a scale\r
1641         // factor of `scale`.\r
1642         zoom: function (scale) {\r
1643                 return Math.log(scale / 256) / Math.LN2;\r
1644         },\r
1645 \r
1646         // @method getProjectedBounds(zoom: Number): Bounds\r
1647         // Returns the projection's bounds scaled and transformed for the provided `zoom`.\r
1648         getProjectedBounds: function (zoom) {\r
1649                 if (this.infinite) { return null; }\r
1650 \r
1651                 var b = this.projection.bounds,\r
1652                     s = this.scale(zoom),\r
1653                     min = this.transformation.transform(b.min, s),\r
1654                     max = this.transformation.transform(b.max, s);\r
1655 \r
1656                 return new Bounds(min, max);\r
1657         },\r
1658 \r
1659         // @method distance(latlng1: LatLng, latlng2: LatLng): Number\r
1660         // Returns the distance between two geographical coordinates.\r
1661 \r
1662         // @property code: String\r
1663         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)\r
1664         //\r
1665         // @property wrapLng: Number[]\r
1666         // An array of two numbers defining whether the longitude (horizontal) coordinate\r
1667         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most\r
1668         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.\r
1669         //\r
1670         // @property wrapLat: Number[]\r
1671         // Like `wrapLng`, but for the latitude (vertical) axis.\r
1672 \r
1673         // wrapLng: [min, max],\r
1674         // wrapLat: [min, max],\r
1675 \r
1676         // @property infinite: Boolean\r
1677         // If true, the coordinate space will be unbounded (infinite in both axes)\r
1678         infinite: false,\r
1679 \r
1680         // @method wrapLatLng(latlng: LatLng): LatLng\r
1681         // Returns a `LatLng` where lat and lng has been wrapped according to the\r
1682         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.\r
1683         wrapLatLng: function (latlng) {\r
1684                 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,\r
1685                     lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,\r
1686                     alt = latlng.alt;\r
1687 \r
1688                 return new LatLng(lat, lng, alt);\r
1689         },\r
1690 \r
1691         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds\r
1692         // Returns a `LatLngBounds` with the same size as the given one, ensuring\r
1693         // that its center is within the CRS's bounds.\r
1694         // Only accepts actual `L.LatLngBounds` instances, not arrays.\r
1695         wrapLatLngBounds: function (bounds) {\r
1696                 var center = bounds.getCenter(),\r
1697                     newCenter = this.wrapLatLng(center),\r
1698                     latShift = center.lat - newCenter.lat,\r
1699                     lngShift = center.lng - newCenter.lng;\r
1700 \r
1701                 if (latShift === 0 && lngShift === 0) {\r
1702                         return bounds;\r
1703                 }\r
1704 \r
1705                 var sw = bounds.getSouthWest(),\r
1706                     ne = bounds.getNorthEast(),\r
1707                     newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),\r
1708                     newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);\r
1709 \r
1710                 return new LatLngBounds(newSw, newNe);\r
1711         }\r
1712   };
1713
1714   /*
1715    * @namespace CRS
1716    * @crs L.CRS.Earth
1717    *
1718    * Serves as the base for CRS that are global such that they cover the earth.
1719    * Can only be used as the base for other CRS and cannot be used directly,
1720    * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1721    * meters.
1722    */
1723
1724   var Earth = extend({}, CRS, {
1725         wrapLng: [-180, 180],
1726
1727         // Mean Earth Radius, as recommended for use by
1728         // the International Union of Geodesy and Geophysics,
1729         // see https://rosettacode.org/wiki/Haversine_formula
1730         R: 6371000,
1731
1732         // distance between two geographical points using spherical law of cosines approximation
1733         distance: function (latlng1, latlng2) {
1734                 var rad = Math.PI / 180,
1735                     lat1 = latlng1.lat * rad,
1736                     lat2 = latlng2.lat * rad,
1737                     sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1738                     sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1739                     a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1740                     c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1741                 return this.R * c;
1742         }
1743   });
1744
1745   /*\r
1746    * @namespace Projection\r
1747    * @projection L.Projection.SphericalMercator\r
1748    *\r
1749    * Spherical Mercator projection — the most common projection for online maps,\r
1750    * used by almost all free and commercial tile providers. Assumes that Earth is\r
1751    * a sphere. Used by the `EPSG:3857` CRS.\r
1752    */\r
1753 \r
1754   var earthRadius = 6378137;\r
1755 \r
1756   var SphericalMercator = {\r
1757 \r
1758         R: earthRadius,\r
1759         MAX_LATITUDE: 85.0511287798,\r
1760 \r
1761         project: function (latlng) {\r
1762                 var d = Math.PI / 180,\r
1763                     max = this.MAX_LATITUDE,\r
1764                     lat = Math.max(Math.min(max, latlng.lat), -max),\r
1765                     sin = Math.sin(lat * d);\r
1766 \r
1767                 return new Point(\r
1768                         this.R * latlng.lng * d,\r
1769                         this.R * Math.log((1 + sin) / (1 - sin)) / 2);\r
1770         },\r
1771 \r
1772         unproject: function (point) {\r
1773                 var d = 180 / Math.PI;\r
1774 \r
1775                 return new LatLng(\r
1776                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,\r
1777                         point.x * d / this.R);\r
1778         },\r
1779 \r
1780         bounds: (function () {\r
1781                 var d = earthRadius * Math.PI;\r
1782                 return new Bounds([-d, -d], [d, d]);\r
1783         })()\r
1784   };
1785
1786   /*\r
1787    * @class Transformation\r
1788    * @aka L.Transformation\r
1789    *\r
1790    * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`\r
1791    * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing\r
1792    * the reverse. Used by Leaflet in its projections code.\r
1793    *\r
1794    * @example\r
1795    *\r
1796    * ```js\r
1797    * var transformation = L.transformation(2, 5, -1, 10),\r
1798    *    p = L.point(1, 2),\r
1799    *    p2 = transformation.transform(p), //  L.point(7, 8)\r
1800    *    p3 = transformation.untransform(p2); //  L.point(1, 2)\r
1801    * ```\r
1802    */\r
1803 \r
1804 \r
1805   // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)\r
1806   // Creates a `Transformation` object with the given coefficients.\r
1807   function Transformation(a, b, c, d) {\r
1808         if (isArray(a)) {\r
1809                 // use array properties\r
1810                 this._a = a[0];\r
1811                 this._b = a[1];\r
1812                 this._c = a[2];\r
1813                 this._d = a[3];\r
1814                 return;\r
1815         }\r
1816         this._a = a;\r
1817         this._b = b;\r
1818         this._c = c;\r
1819         this._d = d;\r
1820   }\r
1821 \r
1822   Transformation.prototype = {\r
1823         // @method transform(point: Point, scale?: Number): Point\r
1824         // Returns a transformed point, optionally multiplied by the given scale.\r
1825         // Only accepts actual `L.Point` instances, not arrays.\r
1826         transform: function (point, scale) { // (Point, Number) -> Point\r
1827                 return this._transform(point.clone(), scale);\r
1828         },\r
1829 \r
1830         // destructive transform (faster)\r
1831         _transform: function (point, scale) {\r
1832                 scale = scale || 1;\r
1833                 point.x = scale * (this._a * point.x + this._b);\r
1834                 point.y = scale * (this._c * point.y + this._d);\r
1835                 return point;\r
1836         },\r
1837 \r
1838         // @method untransform(point: Point, scale?: Number): Point\r
1839         // Returns the reverse transformation of the given point, optionally divided\r
1840         // by the given scale. Only accepts actual `L.Point` instances, not arrays.\r
1841         untransform: function (point, scale) {\r
1842                 scale = scale || 1;\r
1843                 return new Point(\r
1844                         (point.x / scale - this._b) / this._a,\r
1845                         (point.y / scale - this._d) / this._c);\r
1846         }\r
1847   };\r
1848 \r
1849   // factory L.transformation(a: Number, b: Number, c: Number, d: Number)\r
1850 \r
1851   // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)\r
1852   // Instantiates a Transformation object with the given coefficients.\r
1853 \r
1854   // @alternative\r
1855   // @factory L.transformation(coefficients: Array): Transformation\r
1856   // Expects an coefficients array of the form\r
1857   // `[a: Number, b: Number, c: Number, d: Number]`.\r
1858 \r
1859   function toTransformation(a, b, c, d) {\r
1860         return new Transformation(a, b, c, d);\r
1861   }
1862
1863   /*\r
1864    * @namespace CRS\r
1865    * @crs L.CRS.EPSG3857\r
1866    *\r
1867    * The most common CRS for online maps, used by almost all free and commercial\r
1868    * tile providers. Uses Spherical Mercator projection. Set in by default in\r
1869    * Map's `crs` option.\r
1870    */\r
1871 \r
1872   var EPSG3857 = extend({}, Earth, {\r
1873         code: 'EPSG:3857',\r
1874         projection: SphericalMercator,\r
1875 \r
1876         transformation: (function () {\r
1877                 var scale = 0.5 / (Math.PI * SphericalMercator.R);\r
1878                 return toTransformation(scale, 0.5, -scale, 0.5);\r
1879         }())\r
1880   });\r
1881 \r
1882   var EPSG900913 = extend({}, EPSG3857, {\r
1883         code: 'EPSG:900913'\r
1884   });
1885
1886   // @namespace SVG; @section
1887   // There are several static functions which can be called without instantiating L.SVG:
1888
1889   // @function create(name: String): SVGElement
1890   // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1891   // corresponding to the class name passed. For example, using 'line' will return
1892   // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1893   function svgCreate(name) {
1894         return document.createElementNS('http://www.w3.org/2000/svg', name);
1895   }
1896
1897   // @function pointsToPath(rings: Point[], closed: Boolean): String
1898   // Generates a SVG path string for multiple rings, with each ring turning
1899   // into "M..L..L.." instructions
1900   function pointsToPath(rings, closed) {
1901         var str = '',
1902         i, j, len, len2, points, p;
1903
1904         for (i = 0, len = rings.length; i < len; i++) {
1905                 points = rings[i];
1906
1907                 for (j = 0, len2 = points.length; j < len2; j++) {
1908                         p = points[j];
1909                         str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1910                 }
1911
1912                 // closes the ring for polygons; "x" is VML syntax
1913                 str += closed ? (Browser.svg ? 'z' : 'x') : '';
1914         }
1915
1916         // SVG complains about empty path strings
1917         return str || 'M0 0';
1918   }
1919
1920   /*\r
1921    * @namespace Browser\r
1922    * @aka L.Browser\r
1923    *\r
1924    * A namespace with static properties for browser/feature detection used by Leaflet internally.\r
1925    *\r
1926    * @example\r
1927    *\r
1928    * ```js\r
1929    * if (L.Browser.ielt9) {\r
1930    *   alert('Upgrade your browser, dude!');\r
1931    * }\r
1932    * ```\r
1933    */\r
1934 \r
1935   var style = document.documentElement.style;\r
1936 \r
1937   // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).\r
1938   var ie = 'ActiveXObject' in window;\r
1939 \r
1940   // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.\r
1941   var ielt9 = ie && !document.addEventListener;\r
1942 \r
1943   // @property edge: Boolean; `true` for the Edge web browser.\r
1944   var edge = 'msLaunchUri' in navigator && !('documentMode' in document);\r
1945 \r
1946   // @property webkit: Boolean;\r
1947   // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).\r
1948   var webkit = userAgentContains('webkit');\r
1949 \r
1950   // @property android: Boolean\r
1951   // **Deprecated.** `true` for any browser running on an Android platform.\r
1952   var android = userAgentContains('android');\r
1953 \r
1954   // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.\r
1955   var android23 = userAgentContains('android 2') || userAgentContains('android 3');\r
1956 \r
1957   /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */\r
1958   var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit\r
1959   // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)\r
1960   var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);\r
1961 \r
1962   // @property opera: Boolean; `true` for the Opera browser\r
1963   var opera = !!window.opera;\r
1964 \r
1965   // @property chrome: Boolean; `true` for the Chrome browser.\r
1966   var chrome = !edge && userAgentContains('chrome');\r
1967 \r
1968   // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.\r
1969   var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;\r
1970 \r
1971   // @property safari: Boolean; `true` for the Safari browser.\r
1972   var safari = !chrome && userAgentContains('safari');\r
1973 \r
1974   var phantom = userAgentContains('phantom');\r
1975 \r
1976   // @property opera12: Boolean\r
1977   // `true` for the Opera browser supporting CSS transforms (version 12 or later).\r
1978   var opera12 = 'OTransition' in style;\r
1979 \r
1980   // @property win: Boolean; `true` when the browser is running in a Windows platform\r
1981   var win = navigator.platform.indexOf('Win') === 0;\r
1982 \r
1983   // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.\r
1984   var ie3d = ie && ('transition' in style);\r
1985 \r
1986   // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.\r
1987   var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;\r
1988 \r
1989   // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.\r
1990   var gecko3d = 'MozPerspective' in style;\r
1991 \r
1992   // @property any3d: Boolean\r
1993   // `true` for all browsers supporting CSS transforms.\r
1994   var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;\r
1995 \r
1996   // @property mobile: Boolean; `true` for all browsers running in a mobile device.\r
1997   var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');\r
1998 \r
1999   // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.\r
2000   var mobileWebkit = mobile && webkit;\r
2001 \r
2002   // @property mobileWebkit3d: Boolean\r
2003   // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.\r
2004   var mobileWebkit3d = mobile && webkit3d;\r
2005 \r
2006   // @property msPointer: Boolean\r
2007   // `true` for browsers implementing the Microsoft touch events model (notably IE10).\r
2008   var msPointer = !window.PointerEvent && window.MSPointerEvent;\r
2009 \r
2010   // @property pointer: Boolean\r
2011   // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).\r
2012   var pointer = !!(window.PointerEvent || msPointer);\r
2013 \r
2014   // @property touchNative: Boolean\r
2015   // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).\r
2016   // **This does not necessarily mean** that the browser is running in a computer with\r
2017   // a touchscreen, it only means that the browser is capable of understanding\r
2018   // touch events.\r
2019   var touchNative = 'ontouchstart' in window || !!window.TouchEvent;\r
2020 \r
2021   // @property touch: Boolean\r
2022   // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.\r
2023   // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.\r
2024   var touch = !window.L_NO_TOUCH && (touchNative || pointer);\r
2025 \r
2026   // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.\r
2027   var mobileOpera = mobile && opera;\r
2028 \r
2029   // @property mobileGecko: Boolean\r
2030   // `true` for gecko-based browsers running in a mobile device.\r
2031   var mobileGecko = mobile && gecko;\r
2032 \r
2033   // @property retina: Boolean\r
2034   // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.\r
2035   var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;\r
2036 \r
2037   // @property passiveEvents: Boolean\r
2038   // `true` for browsers that support passive events.\r
2039   var passiveEvents = (function () {\r
2040         var supportsPassiveOption = false;\r
2041         try {\r
2042                 var opts = Object.defineProperty({}, 'passive', {\r
2043                         get: function () { // eslint-disable-line getter-return\r
2044                                 supportsPassiveOption = true;\r
2045                         }\r
2046                 });\r
2047                 window.addEventListener('testPassiveEventSupport', falseFn, opts);\r
2048                 window.removeEventListener('testPassiveEventSupport', falseFn, opts);\r
2049         } catch (e) {\r
2050                 // Errors can safely be ignored since this is only a browser support test.\r
2051         }\r
2052         return supportsPassiveOption;\r
2053   }());\r
2054 \r
2055   // @property canvas: Boolean\r
2056   // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).\r
2057   var canvas$1 = (function () {\r
2058         return !!document.createElement('canvas').getContext;\r
2059   }());\r
2060 \r
2061   // @property svg: Boolean\r
2062   // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).\r
2063   var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);\r
2064 \r
2065   var inlineSvg = !!svg$1 && (function () {\r
2066         var div = document.createElement('div');\r
2067         div.innerHTML = '<svg/>';\r
2068         return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';\r
2069   })();\r
2070 \r
2071   // @property vml: Boolean\r
2072   // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).\r
2073   var vml = !svg$1 && (function () {\r
2074         try {\r
2075                 var div = document.createElement('div');\r
2076                 div.innerHTML = '<v:shape adj="1"/>';\r
2077 \r
2078                 var shape = div.firstChild;\r
2079                 shape.style.behavior = 'url(#default#VML)';\r
2080 \r
2081                 return shape && (typeof shape.adj === 'object');\r
2082 \r
2083         } catch (e) {\r
2084                 return false;\r
2085         }\r
2086   }());\r
2087 \r
2088 \r
2089   // @property mac: Boolean; `true` when the browser is running in a Mac platform\r
2090   var mac = navigator.platform.indexOf('Mac') === 0;\r
2091 \r
2092   // @property mac: Boolean; `true` when the browser is running in a Linux platform\r
2093   var linux = navigator.platform.indexOf('Linux') === 0;\r
2094 \r
2095   function userAgentContains(str) {\r
2096         return navigator.userAgent.toLowerCase().indexOf(str) >= 0;\r
2097   }\r
2098 \r
2099 \r
2100   var Browser = {\r
2101         ie: ie,\r
2102         ielt9: ielt9,\r
2103         edge: edge,\r
2104         webkit: webkit,\r
2105         android: android,\r
2106         android23: android23,\r
2107         androidStock: androidStock,\r
2108         opera: opera,\r
2109         chrome: chrome,\r
2110         gecko: gecko,\r
2111         safari: safari,\r
2112         phantom: phantom,\r
2113         opera12: opera12,\r
2114         win: win,\r
2115         ie3d: ie3d,\r
2116         webkit3d: webkit3d,\r
2117         gecko3d: gecko3d,\r
2118         any3d: any3d,\r
2119         mobile: mobile,\r
2120         mobileWebkit: mobileWebkit,\r
2121         mobileWebkit3d: mobileWebkit3d,\r
2122         msPointer: msPointer,\r
2123         pointer: pointer,\r
2124         touch: touch,\r
2125         touchNative: touchNative,\r
2126         mobileOpera: mobileOpera,\r
2127         mobileGecko: mobileGecko,\r
2128         retina: retina,\r
2129         passiveEvents: passiveEvents,\r
2130         canvas: canvas$1,\r
2131         svg: svg$1,\r
2132         vml: vml,\r
2133         inlineSvg: inlineSvg,\r
2134         mac: mac,\r
2135         linux: linux\r
2136   };
2137
2138   /*
2139    * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2140    */
2141
2142   var POINTER_DOWN =   Browser.msPointer ? 'MSPointerDown'   : 'pointerdown';
2143   var POINTER_MOVE =   Browser.msPointer ? 'MSPointerMove'   : 'pointermove';
2144   var POINTER_UP =     Browser.msPointer ? 'MSPointerUp'     : 'pointerup';
2145   var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
2146   var pEvent = {
2147         touchstart  : POINTER_DOWN,
2148         touchmove   : POINTER_MOVE,
2149         touchend    : POINTER_UP,
2150         touchcancel : POINTER_CANCEL
2151   };
2152   var handle = {
2153         touchstart  : _onPointerStart,
2154         touchmove   : _handlePointer,
2155         touchend    : _handlePointer,
2156         touchcancel : _handlePointer
2157   };
2158   var _pointers = {};
2159   var _pointerDocListener = false;
2160
2161   // Provides a touch events wrapper for (ms)pointer events.
2162   // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2163
2164   function addPointerListener(obj, type, handler) {
2165         if (type === 'touchstart') {
2166                 _addPointerDocListener();
2167         }
2168         if (!handle[type]) {
2169                 console.warn('wrong event specified:', type);
2170                 return falseFn;
2171         }
2172         handler = handle[type].bind(this, handler);
2173         obj.addEventListener(pEvent[type], handler, false);
2174         return handler;
2175   }
2176
2177   function removePointerListener(obj, type, handler) {
2178         if (!pEvent[type]) {
2179                 console.warn('wrong event specified:', type);
2180                 return;
2181         }
2182         obj.removeEventListener(pEvent[type], handler, false);
2183   }
2184
2185   function _globalPointerDown(e) {
2186         _pointers[e.pointerId] = e;
2187   }
2188
2189   function _globalPointerMove(e) {
2190         if (_pointers[e.pointerId]) {
2191                 _pointers[e.pointerId] = e;
2192         }
2193   }
2194
2195   function _globalPointerUp(e) {
2196         delete _pointers[e.pointerId];
2197   }
2198
2199   function _addPointerDocListener() {
2200         // need to keep track of what pointers and how many are active to provide e.touches emulation
2201         if (!_pointerDocListener) {
2202                 // we listen document as any drags that end by moving the touch off the screen get fired there
2203                 document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2204                 document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2205                 document.addEventListener(POINTER_UP, _globalPointerUp, true);
2206                 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2207
2208                 _pointerDocListener = true;
2209         }
2210   }
2211
2212   function _handlePointer(handler, e) {
2213         if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }
2214
2215         e.touches = [];
2216         for (var i in _pointers) {
2217                 e.touches.push(_pointers[i]);
2218         }
2219         e.changedTouches = [e];
2220
2221         handler(e);
2222   }
2223
2224   function _onPointerStart(handler, e) {
2225         // IE10 specific: MsTouch needs preventDefault. See #2000
2226         if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
2227                 preventDefault(e);
2228         }
2229         _handlePointer(handler, e);
2230   }
2231
2232   /*\r
2233    * Extends the event handling code with double tap support for mobile browsers.\r
2234    *\r
2235    * Note: currently most browsers fire native dblclick, with only a few exceptions\r
2236    * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)\r
2237    */\r
2238 \r
2239   function makeDblclick(event) {\r
2240         // in modern browsers `type` cannot be just overridden:\r
2241         // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only\r
2242         var newEvent = {},\r
2243             prop, i;\r
2244         for (i in event) {\r
2245                 prop = event[i];\r
2246                 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;\r
2247         }\r
2248         event = newEvent;\r
2249         newEvent.type = 'dblclick';\r
2250         newEvent.detail = 2;\r
2251         newEvent.isTrusted = false;\r
2252         newEvent._simulated = true; // for debug purposes\r
2253         return newEvent;\r
2254   }\r
2255 \r
2256   var delay = 200;\r
2257   function addDoubleTapListener(obj, handler) {\r
2258         // Most browsers handle double tap natively\r
2259         obj.addEventListener('dblclick', handler);\r
2260 \r
2261         // On some platforms the browser doesn't fire native dblclicks for touch events.\r
2262         // It seems that in all such cases `detail` property of `click` event is always `1`.\r
2263         // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.\r
2264         var last = 0,\r
2265             detail;\r
2266         function simDblclick(e) {\r
2267                 if (e.detail !== 1) {\r
2268                         detail = e.detail; // keep in sync to avoid false dblclick in some cases\r
2269                         return;\r
2270                 }\r
2271 \r
2272                 if (e.pointerType === 'mouse' ||\r
2273                         (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {\r
2274 \r
2275                         return;\r
2276                 }\r
2277 \r
2278                 // When clicking on an <input>, the browser generates a click on its\r
2279                 // <label> (and vice versa) triggering two clicks in quick succession.\r
2280                 // This ignores clicks on elements which are a label with a 'for'\r
2281                 // attribute (or children of such a label), but not children of\r
2282                 // a <input>.\r
2283                 var path = getPropagationPath(e);\r
2284                 if (path.some(function (el) {\r
2285                         return el instanceof HTMLLabelElement && el.attributes.for;\r
2286                 }) &&\r
2287                         !path.some(function (el) {\r
2288                                 return (\r
2289                                         el instanceof HTMLInputElement ||\r
2290                                         el instanceof HTMLSelectElement\r
2291                                 );\r
2292                         })\r
2293                 ) {\r
2294                         return;\r
2295                 }\r
2296 \r
2297                 var now = Date.now();\r
2298                 if (now - last <= delay) {\r
2299                         detail++;\r
2300                         if (detail === 2) {\r
2301                                 handler(makeDblclick(e));\r
2302                         }\r
2303                 } else {\r
2304                         detail = 1;\r
2305                 }\r
2306                 last = now;\r
2307         }\r
2308 \r
2309         obj.addEventListener('click', simDblclick);\r
2310 \r
2311         return {\r
2312                 dblclick: handler,\r
2313                 simDblclick: simDblclick\r
2314         };\r
2315   }\r
2316 \r
2317   function removeDoubleTapListener(obj, handlers) {\r
2318         obj.removeEventListener('dblclick', handlers.dblclick);\r
2319         obj.removeEventListener('click', handlers.simDblclick);\r
2320   }
2321
2322   /*\r
2323    * @namespace DomUtil\r
2324    *\r
2325    * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)\r
2326    * tree, used by Leaflet internally.\r
2327    *\r
2328    * Most functions expecting or returning a `HTMLElement` also work for\r
2329    * SVG elements. The only difference is that classes refer to CSS classes\r
2330    * in HTML and SVG classes in SVG.\r
2331    */\r
2332 \r
2333 \r
2334   // @property TRANSFORM: String\r
2335   // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).\r
2336   var TRANSFORM = testProp(\r
2337         ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);\r
2338 \r
2339   // webkitTransition comes first because some browser versions that drop vendor prefix don't do\r
2340   // the same for the transitionend event, in particular the Android 4.1 stock browser\r
2341 \r
2342   // @property TRANSITION: String\r
2343   // Vendor-prefixed transition style name.\r
2344   var TRANSITION = testProp(\r
2345         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);\r
2346 \r
2347   // @property TRANSITION_END: String\r
2348   // Vendor-prefixed transitionend event name.\r
2349   var TRANSITION_END =\r
2350         TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';\r
2351 \r
2352 \r
2353   // @function get(id: String|HTMLElement): HTMLElement\r
2354   // Returns an element given its DOM id, or returns the element itself\r
2355   // if it was passed directly.\r
2356   function get(id) {\r
2357         return typeof id === 'string' ? document.getElementById(id) : id;\r
2358   }\r
2359 \r
2360   // @function getStyle(el: HTMLElement, styleAttrib: String): String\r
2361   // Returns the value for a certain style attribute on an element,\r
2362   // including computed values or values set through CSS.\r
2363   function getStyle(el, style) {\r
2364         var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);\r
2365 \r
2366         if ((!value || value === 'auto') && document.defaultView) {\r
2367                 var css = document.defaultView.getComputedStyle(el, null);\r
2368                 value = css ? css[style] : null;\r
2369         }\r
2370         return value === 'auto' ? null : value;\r
2371   }\r
2372 \r
2373   // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement\r
2374   // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.\r
2375   function create$1(tagName, className, container) {\r
2376         var el = document.createElement(tagName);\r
2377         el.className = className || '';\r
2378 \r
2379         if (container) {\r
2380                 container.appendChild(el);\r
2381         }\r
2382         return el;\r
2383   }\r
2384 \r
2385   // @function remove(el: HTMLElement)\r
2386   // Removes `el` from its parent element\r
2387   function remove(el) {\r
2388         var parent = el.parentNode;\r
2389         if (parent) {\r
2390                 parent.removeChild(el);\r
2391         }\r
2392   }\r
2393 \r
2394   // @function empty(el: HTMLElement)\r
2395   // Removes all of `el`'s children elements from `el`\r
2396   function empty(el) {\r
2397         while (el.firstChild) {\r
2398                 el.removeChild(el.firstChild);\r
2399         }\r
2400   }\r
2401 \r
2402   // @function toFront(el: HTMLElement)\r
2403   // Makes `el` the last child of its parent, so it renders in front of the other children.\r
2404   function toFront(el) {\r
2405         var parent = el.parentNode;\r
2406         if (parent && parent.lastChild !== el) {\r
2407                 parent.appendChild(el);\r
2408         }\r
2409   }\r
2410 \r
2411   // @function toBack(el: HTMLElement)\r
2412   // Makes `el` the first child of its parent, so it renders behind the other children.\r
2413   function toBack(el) {\r
2414         var parent = el.parentNode;\r
2415         if (parent && parent.firstChild !== el) {\r
2416                 parent.insertBefore(el, parent.firstChild);\r
2417         }\r
2418   }\r
2419 \r
2420   // @function hasClass(el: HTMLElement, name: String): Boolean\r
2421   // Returns `true` if the element's class attribute contains `name`.\r
2422   function hasClass(el, name) {\r
2423         if (el.classList !== undefined) {\r
2424                 return el.classList.contains(name);\r
2425         }\r
2426         var className = getClass(el);\r
2427         return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);\r
2428   }\r
2429 \r
2430   // @function addClass(el: HTMLElement, name: String)\r
2431   // Adds `name` to the element's class attribute.\r
2432   function addClass(el, name) {\r
2433         if (el.classList !== undefined) {\r
2434                 var classes = splitWords(name);\r
2435                 for (var i = 0, len = classes.length; i < len; i++) {\r
2436                         el.classList.add(classes[i]);\r
2437                 }\r
2438         } else if (!hasClass(el, name)) {\r
2439                 var className = getClass(el);\r
2440                 setClass(el, (className ? className + ' ' : '') + name);\r
2441         }\r
2442   }\r
2443 \r
2444   // @function removeClass(el: HTMLElement, name: String)\r
2445   // Removes `name` from the element's class attribute.\r
2446   function removeClass(el, name) {\r
2447         if (el.classList !== undefined) {\r
2448                 el.classList.remove(name);\r
2449         } else {\r
2450                 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));\r
2451         }\r
2452   }\r
2453 \r
2454   // @function setClass(el: HTMLElement, name: String)\r
2455   // Sets the element's class.\r
2456   function setClass(el, name) {\r
2457         if (el.className.baseVal === undefined) {\r
2458                 el.className = name;\r
2459         } else {\r
2460                 // in case of SVG element\r
2461                 el.className.baseVal = name;\r
2462         }\r
2463   }\r
2464 \r
2465   // @function getClass(el: HTMLElement): String\r
2466   // Returns the element's class.\r
2467   function getClass(el) {\r
2468         // Check if the element is an SVGElementInstance and use the correspondingElement instead\r
2469         // (Required for linked SVG elements in IE11.)\r
2470         if (el.correspondingElement) {\r
2471                 el = el.correspondingElement;\r
2472         }\r
2473         return el.className.baseVal === undefined ? el.className : el.className.baseVal;\r
2474   }\r
2475 \r
2476   // @function setOpacity(el: HTMLElement, opacity: Number)\r
2477   // Set the opacity of an element (including old IE support).\r
2478   // `opacity` must be a number from `0` to `1`.\r
2479   function setOpacity(el, value) {\r
2480         if ('opacity' in el.style) {\r
2481                 el.style.opacity = value;\r
2482         } else if ('filter' in el.style) {\r
2483                 _setOpacityIE(el, value);\r
2484         }\r
2485   }\r
2486 \r
2487   function _setOpacityIE(el, value) {\r
2488         var filter = false,\r
2489             filterName = 'DXImageTransform.Microsoft.Alpha';\r
2490 \r
2491         // filters collection throws an error if we try to retrieve a filter that doesn't exist\r
2492         try {\r
2493                 filter = el.filters.item(filterName);\r
2494         } catch (e) {\r
2495                 // don't set opacity to 1 if we haven't already set an opacity,\r
2496                 // it isn't needed and breaks transparent pngs.\r
2497                 if (value === 1) { return; }\r
2498         }\r
2499 \r
2500         value = Math.round(value * 100);\r
2501 \r
2502         if (filter) {\r
2503                 filter.Enabled = (value !== 100);\r
2504                 filter.Opacity = value;\r
2505         } else {\r
2506                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';\r
2507         }\r
2508   }\r
2509 \r
2510   // @function testProp(props: String[]): String|false\r
2511   // Goes through the array of style names and returns the first name\r
2512   // that is a valid style name for an element. If no such name is found,\r
2513   // it returns false. Useful for vendor-prefixed styles like `transform`.\r
2514   function testProp(props) {\r
2515         var style = document.documentElement.style;\r
2516 \r
2517         for (var i = 0; i < props.length; i++) {\r
2518                 if (props[i] in style) {\r
2519                         return props[i];\r
2520                 }\r
2521         }\r
2522         return false;\r
2523   }\r
2524 \r
2525   // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)\r
2526   // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels\r
2527   // and optionally scaled by `scale`. Does not have an effect if the\r
2528   // browser doesn't support 3D CSS transforms.\r
2529   function setTransform(el, offset, scale) {\r
2530         var pos = offset || new Point(0, 0);\r
2531 \r
2532         el.style[TRANSFORM] =\r
2533                 (Browser.ie3d ?\r
2534                         'translate(' + pos.x + 'px,' + pos.y + 'px)' :\r
2535                         'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +\r
2536                 (scale ? ' scale(' + scale + ')' : '');\r
2537   }\r
2538 \r
2539   // @function setPosition(el: HTMLElement, position: Point)\r
2540   // Sets the position of `el` to coordinates specified by `position`,\r
2541   // using CSS translate or top/left positioning depending on the browser\r
2542   // (used by Leaflet internally to position its layers).\r
2543   function setPosition(el, point) {\r
2544 \r
2545         /*eslint-disable */\r
2546         el._leaflet_pos = point;\r
2547         /* eslint-enable */\r
2548 \r
2549         if (Browser.any3d) {\r
2550                 setTransform(el, point);\r
2551         } else {\r
2552                 el.style.left = point.x + 'px';\r
2553                 el.style.top = point.y + 'px';\r
2554         }\r
2555   }\r
2556 \r
2557   // @function getPosition(el: HTMLElement): Point\r
2558   // Returns the coordinates of an element previously positioned with setPosition.\r
2559   function getPosition(el) {\r
2560         // this method is only used for elements previously positioned using setPosition,\r
2561         // so it's safe to cache the position for performance\r
2562 \r
2563         return el._leaflet_pos || new Point(0, 0);\r
2564   }\r
2565 \r
2566   // @function disableTextSelection()\r
2567   // Prevents the user from generating `selectstart` DOM events, usually generated\r
2568   // when the user drags the mouse through a page with text. Used internally\r
2569   // by Leaflet to override the behaviour of any click-and-drag interaction on\r
2570   // the map. Affects drag interactions on the whole document.\r
2571 \r
2572   // @function enableTextSelection()\r
2573   // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).\r
2574   var disableTextSelection;\r
2575   var enableTextSelection;\r
2576   var _userSelect;\r
2577   if ('onselectstart' in document) {\r
2578         disableTextSelection = function () {\r
2579                 on(window, 'selectstart', preventDefault);\r
2580         };\r
2581         enableTextSelection = function () {\r
2582                 off(window, 'selectstart', preventDefault);\r
2583         };\r
2584   } else {\r
2585         var userSelectProperty = testProp(\r
2586                 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);\r
2587 \r
2588         disableTextSelection = function () {\r
2589                 if (userSelectProperty) {\r
2590                         var style = document.documentElement.style;\r
2591                         _userSelect = style[userSelectProperty];\r
2592                         style[userSelectProperty] = 'none';\r
2593                 }\r
2594         };\r
2595         enableTextSelection = function () {\r
2596                 if (userSelectProperty) {\r
2597                         document.documentElement.style[userSelectProperty] = _userSelect;\r
2598                         _userSelect = undefined;\r
2599                 }\r
2600         };\r
2601   }\r
2602 \r
2603   // @function disableImageDrag()\r
2604   // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but\r
2605   // for `dragstart` DOM events, usually generated when the user drags an image.\r
2606   function disableImageDrag() {\r
2607         on(window, 'dragstart', preventDefault);\r
2608   }\r
2609 \r
2610   // @function enableImageDrag()\r
2611   // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).\r
2612   function enableImageDrag() {\r
2613         off(window, 'dragstart', preventDefault);\r
2614   }\r
2615 \r
2616   var _outlineElement, _outlineStyle;\r
2617   // @function preventOutline(el: HTMLElement)\r
2618   // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)\r
2619   // of the element `el` invisible. Used internally by Leaflet to prevent\r
2620   // focusable elements from displaying an outline when the user performs a\r
2621   // drag interaction on them.\r
2622   function preventOutline(element) {\r
2623         while (element.tabIndex === -1) {\r
2624                 element = element.parentNode;\r
2625         }\r
2626         if (!element.style) { return; }\r
2627         restoreOutline();\r
2628         _outlineElement = element;\r
2629         _outlineStyle = element.style.outlineStyle;\r
2630         element.style.outlineStyle = 'none';\r
2631         on(window, 'keydown', restoreOutline);\r
2632   }\r
2633 \r
2634   // @function restoreOutline()\r
2635   // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().\r
2636   function restoreOutline() {\r
2637         if (!_outlineElement) { return; }\r
2638         _outlineElement.style.outlineStyle = _outlineStyle;\r
2639         _outlineElement = undefined;\r
2640         _outlineStyle = undefined;\r
2641         off(window, 'keydown', restoreOutline);\r
2642   }\r
2643 \r
2644   // @function getSizedParentNode(el: HTMLElement): HTMLElement\r
2645   // Finds the closest parent node which size (width and height) is not null.\r
2646   function getSizedParentNode(element) {\r
2647         do {\r
2648                 element = element.parentNode;\r
2649         } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);\r
2650         return element;\r
2651   }\r
2652 \r
2653   // @function getScale(el: HTMLElement): Object\r
2654   // Computes the CSS scale currently applied on the element.\r
2655   // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,\r
2656   // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).\r
2657   function getScale(element) {\r
2658         var rect = element.getBoundingClientRect(); // Read-only in old browsers.\r
2659 \r
2660         return {\r
2661                 x: rect.width / element.offsetWidth || 1,\r
2662                 y: rect.height / element.offsetHeight || 1,\r
2663                 boundingClientRect: rect\r
2664         };\r
2665   }
2666
2667   var DomUtil = {
2668     __proto__: null,
2669     TRANSFORM: TRANSFORM,
2670     TRANSITION: TRANSITION,
2671     TRANSITION_END: TRANSITION_END,
2672     get: get,
2673     getStyle: getStyle,
2674     create: create$1,
2675     remove: remove,
2676     empty: empty,
2677     toFront: toFront,
2678     toBack: toBack,
2679     hasClass: hasClass,
2680     addClass: addClass,
2681     removeClass: removeClass,
2682     setClass: setClass,
2683     getClass: getClass,
2684     setOpacity: setOpacity,
2685     testProp: testProp,
2686     setTransform: setTransform,
2687     setPosition: setPosition,
2688     getPosition: getPosition,
2689     get disableTextSelection () { return disableTextSelection; },
2690     get enableTextSelection () { return enableTextSelection; },
2691     disableImageDrag: disableImageDrag,
2692     enableImageDrag: enableImageDrag,
2693     preventOutline: preventOutline,
2694     restoreOutline: restoreOutline,
2695     getSizedParentNode: getSizedParentNode,
2696     getScale: getScale
2697   };
2698
2699   /*\r
2700    * @namespace DomEvent\r
2701    * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.\r
2702    */\r
2703 \r
2704   // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.\r
2705 \r
2706   // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this\r
2707   // Adds a listener function (`fn`) to a particular DOM event type of the\r
2708   // element `el`. You can optionally specify the context of the listener\r
2709   // (object the `this` keyword will point to). You can also pass several\r
2710   // space-separated types (e.g. `'click dblclick'`).\r
2711 \r
2712   // @alternative\r
2713   // @function on(el: HTMLElement, eventMap: Object, context?: Object): this\r
2714   // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`\r
2715   function on(obj, types, fn, context) {\r
2716 \r
2717         if (types && typeof types === 'object') {\r
2718                 for (var type in types) {\r
2719                         addOne(obj, type, types[type], fn);\r
2720                 }\r
2721         } else {\r
2722                 types = splitWords(types);\r
2723 \r
2724                 for (var i = 0, len = types.length; i < len; i++) {\r
2725                         addOne(obj, types[i], fn, context);\r
2726                 }\r
2727         }\r
2728 \r
2729         return this;\r
2730   }\r
2731 \r
2732   var eventsKey = '_leaflet_events';\r
2733 \r
2734   // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this\r
2735   // Removes a previously added listener function.\r
2736   // Note that if you passed a custom context to on, you must pass the same\r
2737   // context to `off` in order to remove the listener.\r
2738 \r
2739   // @alternative\r
2740   // @function off(el: HTMLElement, eventMap: Object, context?: Object): this\r
2741   // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`\r
2742 \r
2743   // @alternative\r
2744   // @function off(el: HTMLElement, types: String): this\r
2745   // Removes all previously added listeners of given types.\r
2746 \r
2747   // @alternative\r
2748   // @function off(el: HTMLElement): this\r
2749   // Removes all previously added listeners from given HTMLElement\r
2750   function off(obj, types, fn, context) {\r
2751 \r
2752         if (arguments.length === 1) {\r
2753                 batchRemove(obj);\r
2754                 delete obj[eventsKey];\r
2755 \r
2756         } else if (types && typeof types === 'object') {\r
2757                 for (var type in types) {\r
2758                         removeOne(obj, type, types[type], fn);\r
2759                 }\r
2760 \r
2761         } else {\r
2762                 types = splitWords(types);\r
2763 \r
2764                 if (arguments.length === 2) {\r
2765                         batchRemove(obj, function (type) {\r
2766                                 return indexOf(types, type) !== -1;\r
2767                         });\r
2768                 } else {\r
2769                         for (var i = 0, len = types.length; i < len; i++) {\r
2770                                 removeOne(obj, types[i], fn, context);\r
2771                         }\r
2772                 }\r
2773         }\r
2774 \r
2775         return this;\r
2776   }\r
2777 \r
2778   function batchRemove(obj, filterFn) {\r
2779         for (var id in obj[eventsKey]) {\r
2780                 var type = id.split(/\d/)[0];\r
2781                 if (!filterFn || filterFn(type)) {\r
2782                         removeOne(obj, type, null, null, id);\r
2783                 }\r
2784         }\r
2785   }\r
2786 \r
2787   var mouseSubst = {\r
2788         mouseenter: 'mouseover',\r
2789         mouseleave: 'mouseout',\r
2790         wheel: !('onwheel' in window) && 'mousewheel'\r
2791   };\r
2792 \r
2793   function addOne(obj, type, fn, context) {\r
2794         var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');\r
2795 \r
2796         if (obj[eventsKey] && obj[eventsKey][id]) { return this; }\r
2797 \r
2798         var handler = function (e) {\r
2799                 return fn.call(context || obj, e || window.event);\r
2800         };\r
2801 \r
2802         var originalHandler = handler;\r
2803 \r
2804         if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {\r
2805                 // Needs DomEvent.Pointer.js\r
2806                 handler = addPointerListener(obj, type, handler);\r
2807 \r
2808         } else if (Browser.touch && (type === 'dblclick')) {\r
2809                 handler = addDoubleTapListener(obj, handler);\r
2810 \r
2811         } else if ('addEventListener' in obj) {\r
2812 \r
2813                 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' ||  type === 'mousewheel') {\r
2814                         obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);\r
2815 \r
2816                 } else if (type === 'mouseenter' || type === 'mouseleave') {\r
2817                         handler = function (e) {\r
2818                                 e = e || window.event;\r
2819                                 if (isExternalTarget(obj, e)) {\r
2820                                         originalHandler(e);\r
2821                                 }\r
2822                         };\r
2823                         obj.addEventListener(mouseSubst[type], handler, false);\r
2824 \r
2825                 } else {\r
2826                         obj.addEventListener(type, originalHandler, false);\r
2827                 }\r
2828 \r
2829         } else {\r
2830                 obj.attachEvent('on' + type, handler);\r
2831         }\r
2832 \r
2833         obj[eventsKey] = obj[eventsKey] || {};\r
2834         obj[eventsKey][id] = handler;\r
2835   }\r
2836 \r
2837   function removeOne(obj, type, fn, context, id) {\r
2838         id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');\r
2839         var handler = obj[eventsKey] && obj[eventsKey][id];\r
2840 \r
2841         if (!handler) { return this; }\r
2842 \r
2843         if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {\r
2844                 removePointerListener(obj, type, handler);\r
2845 \r
2846         } else if (Browser.touch && (type === 'dblclick')) {\r
2847                 removeDoubleTapListener(obj, handler);\r
2848 \r
2849         } else if ('removeEventListener' in obj) {\r
2850 \r
2851                 obj.removeEventListener(mouseSubst[type] || type, handler, false);\r
2852 \r
2853         } else {\r
2854                 obj.detachEvent('on' + type, handler);\r
2855         }\r
2856 \r
2857         obj[eventsKey][id] = null;\r
2858   }\r
2859 \r
2860   // @function stopPropagation(ev: DOMEvent): this\r
2861   // Stop the given event from propagation to parent elements. Used inside the listener functions:\r
2862   // ```js\r
2863   // L.DomEvent.on(div, 'click', function (ev) {\r
2864   //    L.DomEvent.stopPropagation(ev);\r
2865   // });\r
2866   // ```\r
2867   function stopPropagation(e) {\r
2868 \r
2869         if (e.stopPropagation) {\r
2870                 e.stopPropagation();\r
2871         } else if (e.originalEvent) {  // In case of Leaflet event.\r
2872                 e.originalEvent._stopped = true;\r
2873         } else {\r
2874                 e.cancelBubble = true;\r
2875         }\r
2876 \r
2877         return this;\r
2878   }\r
2879 \r
2880   // @function disableScrollPropagation(el: HTMLElement): this\r
2881   // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).\r
2882   function disableScrollPropagation(el) {\r
2883         addOne(el, 'wheel', stopPropagation);\r
2884         return this;\r
2885   }\r
2886 \r
2887   // @function disableClickPropagation(el: HTMLElement): this\r
2888   // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,\r
2889   // `'mousedown'` and `'touchstart'` events (plus browser variants).\r
2890   function disableClickPropagation(el) {\r
2891         on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);\r
2892         el['_leaflet_disable_click'] = true;\r
2893         return this;\r
2894   }\r
2895 \r
2896   // @function preventDefault(ev: DOMEvent): this\r
2897   // Prevents the default action of the DOM Event `ev` from happening (such as\r
2898   // following a link in the href of the a element, or doing a POST request\r
2899   // with page reload when a `<form>` is submitted).\r
2900   // Use it inside listener functions.\r
2901   function preventDefault(e) {\r
2902         if (e.preventDefault) {\r
2903                 e.preventDefault();\r
2904         } else {\r
2905                 e.returnValue = false;\r
2906         }\r
2907         return this;\r
2908   }\r
2909 \r
2910   // @function stop(ev: DOMEvent): this\r
2911   // Does `stopPropagation` and `preventDefault` at the same time.\r
2912   function stop(e) {\r
2913         preventDefault(e);\r
2914         stopPropagation(e);\r
2915         return this;\r
2916   }\r
2917 \r
2918   // @function getPropagationPath(ev: DOMEvent): Array\r
2919   // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath).\r
2920   // Returns an array containing the `HTMLElement`s that the given DOM event\r
2921   // should propagate to (if not stopped).\r
2922   function getPropagationPath(ev) {\r
2923         if (ev.composedPath) {\r
2924                 return ev.composedPath();\r
2925         }\r
2926 \r
2927         var path = [];\r
2928         var el = ev.target;\r
2929 \r
2930         while (el) {\r
2931                 path.push(el);\r
2932                 el = el.parentNode;\r
2933         }\r
2934         return path;\r
2935   }\r
2936 \r
2937 \r
2938   // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point\r
2939   // Gets normalized mouse position from a DOM event relative to the\r
2940   // `container` (border excluded) or to the whole page if not specified.\r
2941   function getMousePosition(e, container) {\r
2942         if (!container) {\r
2943                 return new Point(e.clientX, e.clientY);\r
2944         }\r
2945 \r
2946         var scale = getScale(container),\r
2947             offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)\r
2948 \r
2949         return new Point(\r
2950                 // offset.left/top values are in page scale (like clientX/Y),\r
2951                 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).\r
2952                 (e.clientX - offset.left) / scale.x - container.clientLeft,\r
2953                 (e.clientY - offset.top) / scale.y - container.clientTop\r
2954         );\r
2955   }\r
2956 \r
2957 \r
2958   //  except , Safari and\r
2959   // We need double the scroll pixels (see #7403 and #4538) for all Browsers\r
2960   // except OSX (Mac) -> 3x, Chrome running on Linux 1x\r
2961 \r
2962   var wheelPxFactor =\r
2963         (Browser.linux && Browser.chrome) ? window.devicePixelRatio :\r
2964         Browser.mac ? window.devicePixelRatio * 3 :\r
2965         window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1;\r
2966   // @function getWheelDelta(ev: DOMEvent): Number\r
2967   // Gets normalized wheel delta from a wheel DOM event, in vertical\r
2968   // pixels scrolled (negative if scrolling down).\r
2969   // Events from pointing devices without precise scrolling are mapped to\r
2970   // a best guess of 60 pixels.\r
2971   function getWheelDelta(e) {\r
2972         return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta\r
2973                (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels\r
2974                (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines\r
2975                (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages\r
2976                (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events\r
2977                e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels\r
2978                (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines\r
2979                e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages\r
2980                0;\r
2981   }\r
2982 \r
2983   // check if element really left/entered the event target (for mouseenter/mouseleave)\r
2984   function isExternalTarget(el, e) {\r
2985 \r
2986         var related = e.relatedTarget;\r
2987 \r
2988         if (!related) { return true; }\r
2989 \r
2990         try {\r
2991                 while (related && (related !== el)) {\r
2992                         related = related.parentNode;\r
2993                 }\r
2994         } catch (err) {\r
2995                 return false;\r
2996         }\r
2997         return (related !== el);\r
2998   }
2999
3000   var DomEvent = {
3001     __proto__: null,
3002     on: on,
3003     off: off,
3004     stopPropagation: stopPropagation,
3005     disableScrollPropagation: disableScrollPropagation,
3006     disableClickPropagation: disableClickPropagation,
3007     preventDefault: preventDefault,
3008     stop: stop,
3009     getPropagationPath: getPropagationPath,
3010     getMousePosition: getMousePosition,
3011     getWheelDelta: getWheelDelta,
3012     isExternalTarget: isExternalTarget,
3013     addListener: on,
3014     removeListener: off
3015   };
3016
3017   /*
3018    * @class PosAnimation
3019    * @aka L.PosAnimation
3020    * @inherits Evented
3021    * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
3022    *
3023    * @example
3024    * ```js
3025    * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map);
3026    *
3027    * myPositionMarker.on("click", function() {
3028    *    var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng());
3029    *    pos.y -= 25;
3030    *    var fx = new L.PosAnimation();
3031    *
3032    *    fx.once('end',function() {
3033    *            pos.y += 25;
3034    *            fx.run(myPositionMarker._icon, pos, 0.8);
3035    *    });
3036    *
3037    *    fx.run(myPositionMarker._icon, pos, 0.3);
3038    * });
3039    *
3040    * ```
3041    *
3042    * @constructor L.PosAnimation()
3043    * Creates a `PosAnimation` object.
3044    *
3045    */
3046
3047   var PosAnimation = Evented.extend({
3048
3049         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
3050         // Run an animation of a given element to a new position, optionally setting
3051         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
3052         // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
3053         // `0.5` by default).
3054         run: function (el, newPos, duration, easeLinearity) {
3055                 this.stop();
3056
3057                 this._el = el;
3058                 this._inProgress = true;
3059                 this._duration = duration || 0.25;
3060                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
3061
3062                 this._startPos = getPosition(el);
3063                 this._offset = newPos.subtract(this._startPos);
3064                 this._startTime = +new Date();
3065
3066                 // @event start: Event
3067                 // Fired when the animation starts
3068                 this.fire('start');
3069
3070                 this._animate();
3071         },
3072
3073         // @method stop()
3074         // Stops the animation (if currently running).
3075         stop: function () {
3076                 if (!this._inProgress) { return; }
3077
3078                 this._step(true);
3079                 this._complete();
3080         },
3081
3082         _animate: function () {
3083                 // animation loop
3084                 this._animId = requestAnimFrame(this._animate, this);
3085                 this._step();
3086         },
3087
3088         _step: function (round) {
3089                 var elapsed = (+new Date()) - this._startTime,
3090                     duration = this._duration * 1000;
3091
3092                 if (elapsed < duration) {
3093                         this._runFrame(this._easeOut(elapsed / duration), round);
3094                 } else {
3095                         this._runFrame(1);
3096                         this._complete();
3097                 }
3098         },
3099
3100         _runFrame: function (progress, round) {
3101                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
3102                 if (round) {
3103                         pos._round();
3104                 }
3105                 setPosition(this._el, pos);
3106
3107                 // @event step: Event
3108                 // Fired continuously during the animation.
3109                 this.fire('step');
3110         },
3111
3112         _complete: function () {
3113                 cancelAnimFrame(this._animId);
3114
3115                 this._inProgress = false;
3116                 // @event end: Event
3117                 // Fired when the animation ends.
3118                 this.fire('end');
3119         },
3120
3121         _easeOut: function (t) {
3122                 return 1 - Math.pow(1 - t, this._easeOutPower);
3123         }
3124   });
3125
3126   /*\r
3127    * @class Map\r
3128    * @aka L.Map\r
3129    * @inherits Evented\r
3130    *\r
3131    * The central class of the API — it is used to create a map on a page and manipulate it.\r
3132    *\r
3133    * @example\r
3134    *\r
3135    * ```js\r
3136    * // initialize the map on the "map" div with a given center and zoom\r
3137    * var map = L.map('map', {\r
3138    *    center: [51.505, -0.09],\r
3139    *    zoom: 13\r
3140    * });\r
3141    * ```\r
3142    *\r
3143    */\r
3144 \r
3145   var Map = Evented.extend({\r
3146 \r
3147         options: {\r
3148                 // @section Map State Options\r
3149                 // @option crs: CRS = L.CRS.EPSG3857\r
3150                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not\r
3151                 // sure what it means.\r
3152                 crs: EPSG3857,\r
3153 \r
3154                 // @option center: LatLng = undefined\r
3155                 // Initial geographic center of the map\r
3156                 center: undefined,\r
3157 \r
3158                 // @option zoom: Number = undefined\r
3159                 // Initial map zoom level\r
3160                 zoom: undefined,\r
3161 \r
3162                 // @option minZoom: Number = *\r
3163                 // Minimum zoom level of the map.\r
3164                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,\r
3165                 // the lowest of their `minZoom` options will be used instead.\r
3166                 minZoom: undefined,\r
3167 \r
3168                 // @option maxZoom: Number = *\r
3169                 // Maximum zoom level of the map.\r
3170                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,\r
3171                 // the highest of their `maxZoom` options will be used instead.\r
3172                 maxZoom: undefined,\r
3173 \r
3174                 // @option layers: Layer[] = []\r
3175                 // Array of layers that will be added to the map initially\r
3176                 layers: [],\r
3177 \r
3178                 // @option maxBounds: LatLngBounds = null\r
3179                 // When this option is set, the map restricts the view to the given\r
3180                 // geographical bounds, bouncing the user back if the user tries to pan\r
3181                 // outside the view. To set the restriction dynamically, use\r
3182                 // [`setMaxBounds`](#map-setmaxbounds) method.\r
3183                 maxBounds: undefined,\r
3184 \r
3185                 // @option renderer: Renderer = *\r
3186                 // The default method for drawing vector layers on the map. `L.SVG`\r
3187                 // or `L.Canvas` by default depending on browser support.\r
3188                 renderer: undefined,\r
3189 \r
3190 \r
3191                 // @section Animation Options\r
3192                 // @option zoomAnimation: Boolean = true\r
3193                 // Whether the map zoom animation is enabled. By default it's enabled\r
3194                 // in all browsers that support CSS3 Transitions except Android.\r
3195                 zoomAnimation: true,\r
3196 \r
3197                 // @option zoomAnimationThreshold: Number = 4\r
3198                 // Won't animate zoom if the zoom difference exceeds this value.\r
3199                 zoomAnimationThreshold: 4,\r
3200 \r
3201                 // @option fadeAnimation: Boolean = true\r
3202                 // Whether the tile fade animation is enabled. By default it's enabled\r
3203                 // in all browsers that support CSS3 Transitions except Android.\r
3204                 fadeAnimation: true,\r
3205 \r
3206                 // @option markerZoomAnimation: Boolean = true\r
3207                 // Whether markers animate their zoom with the zoom animation, if disabled\r
3208                 // they will disappear for the length of the animation. By default it's\r
3209                 // enabled in all browsers that support CSS3 Transitions except Android.\r
3210                 markerZoomAnimation: true,\r
3211 \r
3212                 // @option transform3DLimit: Number = 2^23\r
3213                 // Defines the maximum size of a CSS translation transform. The default\r
3214                 // value should not be changed unless a web browser positions layers in\r
3215                 // the wrong place after doing a large `panBy`.\r
3216                 transform3DLimit: 8388608, // Precision limit of a 32-bit float\r
3217 \r
3218                 // @section Interaction Options\r
3219                 // @option zoomSnap: Number = 1\r
3220                 // Forces the map's zoom level to always be a multiple of this, particularly\r
3221                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.\r
3222                 // By default, the zoom level snaps to the nearest integer; lower values\r
3223                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`\r
3224                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.\r
3225                 zoomSnap: 1,\r
3226 \r
3227                 // @option zoomDelta: Number = 1\r
3228                 // Controls how much the map's zoom level will change after a\r
3229                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`\r
3230                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).\r
3231                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.\r
3232                 zoomDelta: 1,\r
3233 \r
3234                 // @option trackResize: Boolean = true\r
3235                 // Whether the map automatically handles browser window resize to update itself.\r
3236                 trackResize: true\r
3237         },\r
3238 \r
3239         initialize: function (id, options) { // (HTMLElement or String, Object)\r
3240                 options = setOptions(this, options);\r
3241 \r
3242                 // Make sure to assign internal flags at the beginning,\r
3243                 // to avoid inconsistent state in some edge cases.\r
3244                 this._handlers = [];\r
3245                 this._layers = {};\r
3246                 this._zoomBoundLayers = {};\r
3247                 this._sizeChanged = true;\r
3248 \r
3249                 this._initContainer(id);\r
3250                 this._initLayout();\r
3251 \r
3252                 // hack for https://github.com/Leaflet/Leaflet/issues/1980\r
3253                 this._onResize = bind(this._onResize, this);\r
3254 \r
3255                 this._initEvents();\r
3256 \r
3257                 if (options.maxBounds) {\r
3258                         this.setMaxBounds(options.maxBounds);\r
3259                 }\r
3260 \r
3261                 if (options.zoom !== undefined) {\r
3262                         this._zoom = this._limitZoom(options.zoom);\r
3263                 }\r
3264 \r
3265                 if (options.center && options.zoom !== undefined) {\r
3266                         this.setView(toLatLng(options.center), options.zoom, {reset: true});\r
3267                 }\r
3268 \r
3269                 this.callInitHooks();\r
3270 \r
3271                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera\r
3272                 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&\r
3273                                 this.options.zoomAnimation;\r
3274 \r
3275                 // zoom transitions run with the same duration for all layers, so if one of transitionend events\r
3276                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally\r
3277                 if (this._zoomAnimated) {\r
3278                         this._createAnimProxy();\r
3279                         on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);\r
3280                 }\r
3281 \r
3282                 this._addLayers(this.options.layers);\r
3283         },\r
3284 \r
3285 \r
3286         // @section Methods for modifying map state\r
3287 \r
3288         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this\r
3289         // Sets the view of the map (geographical center and zoom) with the given\r
3290         // animation options.\r
3291         setView: function (center, zoom, options) {\r
3292 \r
3293                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);\r
3294                 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);\r
3295                 options = options || {};\r
3296 \r
3297                 this._stop();\r
3298 \r
3299                 if (this._loaded && !options.reset && options !== true) {\r
3300 \r
3301                         if (options.animate !== undefined) {\r
3302                                 options.zoom = extend({animate: options.animate}, options.zoom);\r
3303                                 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);\r
3304                         }\r
3305 \r
3306                         // try animating pan or zoom\r
3307                         var moved = (this._zoom !== zoom) ?\r
3308                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :\r
3309                                 this._tryAnimatedPan(center, options.pan);\r
3310 \r
3311                         if (moved) {\r
3312                                 // prevent resize handler call, the view will refresh after animation anyway\r
3313                                 clearTimeout(this._sizeTimer);\r
3314                                 return this;\r
3315                         }\r
3316                 }\r
3317 \r
3318                 // animation didn't start, just reset the map view\r
3319                 this._resetView(center, zoom, options.pan && options.pan.noMoveStart);\r
3320 \r
3321                 return this;\r
3322         },\r
3323 \r
3324         // @method setZoom(zoom: Number, options?: Zoom/pan options): this\r
3325         // Sets the zoom of the map.\r
3326         setZoom: function (zoom, options) {\r
3327                 if (!this._loaded) {\r
3328                         this._zoom = zoom;\r
3329                         return this;\r
3330                 }\r
3331                 return this.setView(this.getCenter(), zoom, {zoom: options});\r
3332         },\r
3333 \r
3334         // @method zoomIn(delta?: Number, options?: Zoom options): this\r
3335         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).\r
3336         zoomIn: function (delta, options) {\r
3337                 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);\r
3338                 return this.setZoom(this._zoom + delta, options);\r
3339         },\r
3340 \r
3341         // @method zoomOut(delta?: Number, options?: Zoom options): this\r
3342         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).\r
3343         zoomOut: function (delta, options) {\r
3344                 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);\r
3345                 return this.setZoom(this._zoom - delta, options);\r
3346         },\r
3347 \r
3348         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this\r
3349         // Zooms the map while keeping a specified geographical point on the map\r
3350         // stationary (e.g. used internally for scroll zoom and double-click zoom).\r
3351         // @alternative\r
3352         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this\r
3353         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.\r
3354         setZoomAround: function (latlng, zoom, options) {\r
3355                 var scale = this.getZoomScale(zoom),\r
3356                     viewHalf = this.getSize().divideBy(2),\r
3357                     containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),\r
3358 \r
3359                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),\r
3360                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));\r
3361 \r
3362                 return this.setView(newCenter, zoom, {zoom: options});\r
3363         },\r
3364 \r
3365         _getBoundsCenterZoom: function (bounds, options) {\r
3366 \r
3367                 options = options || {};\r
3368                 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);\r
3369 \r
3370                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),\r
3371                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),\r
3372 \r
3373                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));\r
3374 \r
3375                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;\r
3376 \r
3377                 if (zoom === Infinity) {\r
3378                         return {\r
3379                                 center: bounds.getCenter(),\r
3380                                 zoom: zoom\r
3381                         };\r
3382                 }\r
3383 \r
3384                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),\r
3385 \r
3386                     swPoint = this.project(bounds.getSouthWest(), zoom),\r
3387                     nePoint = this.project(bounds.getNorthEast(), zoom),\r
3388                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);\r
3389 \r
3390                 return {\r
3391                         center: center,\r
3392                         zoom: zoom\r
3393                 };\r
3394         },\r
3395 \r
3396         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this\r
3397         // Sets a map view that contains the given geographical bounds with the\r
3398         // maximum zoom level possible.\r
3399         fitBounds: function (bounds, options) {\r
3400 \r
3401                 bounds = toLatLngBounds(bounds);\r
3402 \r
3403                 if (!bounds.isValid()) {\r
3404                         throw new Error('Bounds are not valid.');\r
3405                 }\r
3406 \r
3407                 var target = this._getBoundsCenterZoom(bounds, options);\r
3408                 return this.setView(target.center, target.zoom, options);\r
3409         },\r
3410 \r
3411         // @method fitWorld(options?: fitBounds options): this\r
3412         // Sets a map view that mostly contains the whole world with the maximum\r
3413         // zoom level possible.\r
3414         fitWorld: function (options) {\r
3415                 return this.fitBounds([[-90, -180], [90, 180]], options);\r
3416         },\r
3417 \r
3418         // @method panTo(latlng: LatLng, options?: Pan options): this\r
3419         // Pans the map to a given center.\r
3420         panTo: function (center, options) { // (LatLng)\r
3421                 return this.setView(center, this._zoom, {pan: options});\r
3422         },\r
3423 \r
3424         // @method panBy(offset: Point, options?: Pan options): this\r
3425         // Pans the map by a given number of pixels (animated).\r
3426         panBy: function (offset, options) {\r
3427                 offset = toPoint(offset).round();\r
3428                 options = options || {};\r
3429 \r
3430                 if (!offset.x && !offset.y) {\r
3431                         return this.fire('moveend');\r
3432                 }\r
3433                 // If we pan too far, Chrome gets issues with tiles\r
3434                 // and makes them disappear or appear in the wrong place (slightly offset) #2602\r
3435                 if (options.animate !== true && !this.getSize().contains(offset)) {\r
3436                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());\r
3437                         return this;\r
3438                 }\r
3439 \r
3440                 if (!this._panAnim) {\r
3441                         this._panAnim = new PosAnimation();\r
3442 \r
3443                         this._panAnim.on({\r
3444                                 'step': this._onPanTransitionStep,\r
3445                                 'end': this._onPanTransitionEnd\r
3446                         }, this);\r
3447                 }\r
3448 \r
3449                 // don't fire movestart if animating inertia\r
3450                 if (!options.noMoveStart) {\r
3451                         this.fire('movestart');\r
3452                 }\r
3453 \r
3454                 // animate pan unless animate: false specified\r
3455                 if (options.animate !== false) {\r
3456                         addClass(this._mapPane, 'leaflet-pan-anim');\r
3457 \r
3458                         var newPos = this._getMapPanePos().subtract(offset).round();\r
3459                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);\r
3460                 } else {\r
3461                         this._rawPanBy(offset);\r
3462                         this.fire('move').fire('moveend');\r
3463                 }\r
3464 \r
3465                 return this;\r
3466         },\r
3467 \r
3468         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this\r
3469         // Sets the view of the map (geographical center and zoom) performing a smooth\r
3470         // pan-zoom animation.\r
3471         flyTo: function (targetCenter, targetZoom, options) {\r
3472 \r
3473                 options = options || {};\r
3474                 if (options.animate === false || !Browser.any3d) {\r
3475                         return this.setView(targetCenter, targetZoom, options);\r
3476                 }\r
3477 \r
3478                 this._stop();\r
3479 \r
3480                 var from = this.project(this.getCenter()),\r
3481                     to = this.project(targetCenter),\r
3482                     size = this.getSize(),\r
3483                     startZoom = this._zoom;\r
3484 \r
3485                 targetCenter = toLatLng(targetCenter);\r
3486                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;\r
3487 \r
3488                 var w0 = Math.max(size.x, size.y),\r
3489                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),\r
3490                     u1 = (to.distanceTo(from)) || 1,\r
3491                     rho = 1.42,\r
3492                     rho2 = rho * rho;\r
3493 \r
3494                 function r(i) {\r
3495                         var s1 = i ? -1 : 1,\r
3496                             s2 = i ? w1 : w0,\r
3497                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,\r
3498                             b1 = 2 * s2 * rho2 * u1,\r
3499                             b = t1 / b1,\r
3500                             sq = Math.sqrt(b * b + 1) - b;\r
3501 \r
3502                             // workaround for floating point precision bug when sq = 0, log = -Infinite,\r
3503                             // thus triggering an infinite loop in flyTo\r
3504                             var log = sq < 0.000000001 ? -18 : Math.log(sq);\r
3505 \r
3506                         return log;\r
3507                 }\r
3508 \r
3509                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }\r
3510                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }\r
3511                 function tanh(n) { return sinh(n) / cosh(n); }\r
3512 \r
3513                 var r0 = r(0);\r
3514 \r
3515                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }\r
3516                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }\r
3517 \r
3518                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }\r
3519 \r
3520                 var start = Date.now(),\r
3521                     S = (r(1) - r0) / rho,\r
3522                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;\r
3523 \r
3524                 function frame() {\r
3525                         var t = (Date.now() - start) / duration,\r
3526                             s = easeOut(t) * S;\r
3527 \r
3528                         if (t <= 1) {\r
3529                                 this._flyToFrame = requestAnimFrame(frame, this);\r
3530 \r
3531                                 this._move(\r
3532                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),\r
3533                                         this.getScaleZoom(w0 / w(s), startZoom),\r
3534                                         {flyTo: true});\r
3535 \r
3536                         } else {\r
3537                                 this\r
3538                                         ._move(targetCenter, targetZoom)\r
3539                                         ._moveEnd(true);\r
3540                         }\r
3541                 }\r
3542 \r
3543                 this._moveStart(true, options.noMoveStart);\r
3544 \r
3545                 frame.call(this);\r
3546                 return this;\r
3547         },\r
3548 \r
3549         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this\r
3550         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),\r
3551         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).\r
3552         flyToBounds: function (bounds, options) {\r
3553                 var target = this._getBoundsCenterZoom(bounds, options);\r
3554                 return this.flyTo(target.center, target.zoom, options);\r
3555         },\r
3556 \r
3557         // @method setMaxBounds(bounds: LatLngBounds): this\r
3558         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).\r
3559         setMaxBounds: function (bounds) {\r
3560                 bounds = toLatLngBounds(bounds);\r
3561 \r
3562                 if (this.listens('moveend', this._panInsideMaxBounds)) {\r
3563                         this.off('moveend', this._panInsideMaxBounds);\r
3564                 }\r
3565 \r
3566                 if (!bounds.isValid()) {\r
3567                         this.options.maxBounds = null;\r
3568                         return this;\r
3569                 }\r
3570 \r
3571                 this.options.maxBounds = bounds;\r
3572 \r
3573                 if (this._loaded) {\r
3574                         this._panInsideMaxBounds();\r
3575                 }\r
3576 \r
3577                 return this.on('moveend', this._panInsideMaxBounds);\r
3578         },\r
3579 \r
3580         // @method setMinZoom(zoom: Number): this\r
3581         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).\r
3582         setMinZoom: function (zoom) {\r
3583                 var oldZoom = this.options.minZoom;\r
3584                 this.options.minZoom = zoom;\r
3585 \r
3586                 if (this._loaded && oldZoom !== zoom) {\r
3587                         this.fire('zoomlevelschange');\r
3588 \r
3589                         if (this.getZoom() < this.options.minZoom) {\r
3590                                 return this.setZoom(zoom);\r
3591                         }\r
3592                 }\r
3593 \r
3594                 return this;\r
3595         },\r
3596 \r
3597         // @method setMaxZoom(zoom: Number): this\r
3598         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).\r
3599         setMaxZoom: function (zoom) {\r
3600                 var oldZoom = this.options.maxZoom;\r
3601                 this.options.maxZoom = zoom;\r
3602 \r
3603                 if (this._loaded && oldZoom !== zoom) {\r
3604                         this.fire('zoomlevelschange');\r
3605 \r
3606                         if (this.getZoom() > this.options.maxZoom) {\r
3607                                 return this.setZoom(zoom);\r
3608                         }\r
3609                 }\r
3610 \r
3611                 return this;\r
3612         },\r
3613 \r
3614         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this\r
3615         // 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.\r
3616         panInsideBounds: function (bounds, options) {\r
3617                 this._enforcingBounds = true;\r
3618                 var center = this.getCenter(),\r
3619                     newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));\r
3620 \r
3621                 if (!center.equals(newCenter)) {\r
3622                         this.panTo(newCenter, options);\r
3623                 }\r
3624 \r
3625                 this._enforcingBounds = false;\r
3626                 return this;\r
3627         },\r
3628 \r
3629         // @method panInside(latlng: LatLng, options?: padding options): this\r
3630         // Pans the map the minimum amount to make the `latlng` visible. Use\r
3631         // padding options to fit the display to more restricted bounds.\r
3632         // If `latlng` is already within the (optionally padded) display bounds,\r
3633         // the map will not be panned.\r
3634         panInside: function (latlng, options) {\r
3635                 options = options || {};\r
3636 \r
3637                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),\r
3638                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),\r
3639                     pixelCenter = this.project(this.getCenter()),\r
3640                     pixelPoint = this.project(latlng),\r
3641                     pixelBounds = this.getPixelBounds(),\r
3642                     paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),\r
3643                     paddedSize = paddedBounds.getSize();\r
3644 \r
3645                 if (!paddedBounds.contains(pixelPoint)) {\r
3646                         this._enforcingBounds = true;\r
3647                         var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());\r
3648                         var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);\r
3649                         pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;\r
3650                         pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;\r
3651                         this.panTo(this.unproject(pixelCenter), options);\r
3652                         this._enforcingBounds = false;\r
3653                 }\r
3654                 return this;\r
3655         },\r
3656 \r
3657         // @method invalidateSize(options: Zoom/pan options): this\r
3658         // Checks if the map container size changed and updates the map if so —\r
3659         // call it after you've changed the map size dynamically, also animating\r
3660         // pan by default. If `options.pan` is `false`, panning will not occur.\r
3661         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so\r
3662         // that it doesn't happen often even if the method is called many\r
3663         // times in a row.\r
3664 \r
3665         // @alternative\r
3666         // @method invalidateSize(animate: Boolean): this\r
3667         // Checks if the map container size changed and updates the map if so —\r
3668         // call it after you've changed the map size dynamically, also animating\r
3669         // pan by default.\r
3670         invalidateSize: function (options) {\r
3671                 if (!this._loaded) { return this; }\r
3672 \r
3673                 options = extend({\r
3674                         animate: false,\r
3675                         pan: true\r
3676                 }, options === true ? {animate: true} : options);\r
3677 \r
3678                 var oldSize = this.getSize();\r
3679                 this._sizeChanged = true;\r
3680                 this._lastCenter = null;\r
3681 \r
3682                 var newSize = this.getSize(),\r
3683                     oldCenter = oldSize.divideBy(2).round(),\r
3684                     newCenter = newSize.divideBy(2).round(),\r
3685                     offset = oldCenter.subtract(newCenter);\r
3686 \r
3687                 if (!offset.x && !offset.y) { return this; }\r
3688 \r
3689                 if (options.animate && options.pan) {\r
3690                         this.panBy(offset);\r
3691 \r
3692                 } else {\r
3693                         if (options.pan) {\r
3694                                 this._rawPanBy(offset);\r
3695                         }\r
3696 \r
3697                         this.fire('move');\r
3698 \r
3699                         if (options.debounceMoveend) {\r
3700                                 clearTimeout(this._sizeTimer);\r
3701                                 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);\r
3702                         } else {\r
3703                                 this.fire('moveend');\r
3704                         }\r
3705                 }\r
3706 \r
3707                 // @section Map state change events\r
3708                 // @event resize: ResizeEvent\r
3709                 // Fired when the map is resized.\r
3710                 return this.fire('resize', {\r
3711                         oldSize: oldSize,\r
3712                         newSize: newSize\r
3713                 });\r
3714         },\r
3715 \r
3716         // @section Methods for modifying map state\r
3717         // @method stop(): this\r
3718         // Stops the currently running `panTo` or `flyTo` animation, if any.\r
3719         stop: function () {\r
3720                 this.setZoom(this._limitZoom(this._zoom));\r
3721                 if (!this.options.zoomSnap) {\r
3722                         this.fire('viewreset');\r
3723                 }\r
3724                 return this._stop();\r
3725         },\r
3726 \r
3727         // @section Geolocation methods\r
3728         // @method locate(options?: Locate options): this\r
3729         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)\r
3730         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,\r
3731         // and optionally sets the map view to the user's location with respect to\r
3732         // detection accuracy (or to the world view if geolocation failed).\r
3733         // Note that, if your page doesn't use HTTPS, this method will fail in\r
3734         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))\r
3735         // See `Locate options` for more details.\r
3736         locate: function (options) {\r
3737 \r
3738                 options = this._locateOptions = extend({\r
3739                         timeout: 10000,\r
3740                         watch: false\r
3741                         // setView: false\r
3742                         // maxZoom: <Number>\r
3743                         // maximumAge: 0\r
3744                         // enableHighAccuracy: false\r
3745                 }, options);\r
3746 \r
3747                 if (!('geolocation' in navigator)) {\r
3748                         this._handleGeolocationError({\r
3749                                 code: 0,\r
3750                                 message: 'Geolocation not supported.'\r
3751                         });\r
3752                         return this;\r
3753                 }\r
3754 \r
3755                 var onResponse = bind(this._handleGeolocationResponse, this),\r
3756                     onError = bind(this._handleGeolocationError, this);\r
3757 \r
3758                 if (options.watch) {\r
3759                         this._locationWatchId =\r
3760                                 navigator.geolocation.watchPosition(onResponse, onError, options);\r
3761                 } else {\r
3762                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);\r
3763                 }\r
3764                 return this;\r
3765         },\r
3766 \r
3767         // @method stopLocate(): this\r
3768         // Stops watching location previously initiated by `map.locate({watch: true})`\r
3769         // and aborts resetting the map view if map.locate was called with\r
3770         // `{setView: true}`.\r
3771         stopLocate: function () {\r
3772                 if (navigator.geolocation && navigator.geolocation.clearWatch) {\r
3773                         navigator.geolocation.clearWatch(this._locationWatchId);\r
3774                 }\r
3775                 if (this._locateOptions) {\r
3776                         this._locateOptions.setView = false;\r
3777                 }\r
3778                 return this;\r
3779         },\r
3780 \r
3781         _handleGeolocationError: function (error) {\r
3782                 if (!this._container._leaflet_id) { return; }\r
3783 \r
3784                 var c = error.code,\r
3785                     message = error.message ||\r
3786                             (c === 1 ? 'permission denied' :\r
3787                             (c === 2 ? 'position unavailable' : 'timeout'));\r
3788 \r
3789                 if (this._locateOptions.setView && !this._loaded) {\r
3790                         this.fitWorld();\r
3791                 }\r
3792 \r
3793                 // @section Location events\r
3794                 // @event locationerror: ErrorEvent\r
3795                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.\r
3796                 this.fire('locationerror', {\r
3797                         code: c,\r
3798                         message: 'Geolocation error: ' + message + '.'\r
3799                 });\r
3800         },\r
3801 \r
3802         _handleGeolocationResponse: function (pos) {\r
3803                 if (!this._container._leaflet_id) { return; }\r
3804 \r
3805                 var lat = pos.coords.latitude,\r
3806                     lng = pos.coords.longitude,\r
3807                     latlng = new LatLng(lat, lng),\r
3808                     bounds = latlng.toBounds(pos.coords.accuracy * 2),\r
3809                     options = this._locateOptions;\r
3810 \r
3811                 if (options.setView) {\r
3812                         var zoom = this.getBoundsZoom(bounds);\r
3813                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);\r
3814                 }\r
3815 \r
3816                 var data = {\r
3817                         latlng: latlng,\r
3818                         bounds: bounds,\r
3819                         timestamp: pos.timestamp\r
3820                 };\r
3821 \r
3822                 for (var i in pos.coords) {\r
3823                         if (typeof pos.coords[i] === 'number') {\r
3824                                 data[i] = pos.coords[i];\r
3825                         }\r
3826                 }\r
3827 \r
3828                 // @event locationfound: LocationEvent\r
3829                 // Fired when geolocation (using the [`locate`](#map-locate) method)\r
3830                 // went successfully.\r
3831                 this.fire('locationfound', data);\r
3832         },\r
3833 \r
3834         // TODO Appropriate docs section?\r
3835         // @section Other Methods\r
3836         // @method addHandler(name: String, HandlerClass: Function): this\r
3837         // Adds a new `Handler` to the map, given its name and constructor function.\r
3838         addHandler: function (name, HandlerClass) {\r
3839                 if (!HandlerClass) { return this; }\r
3840 \r
3841                 var handler = this[name] = new HandlerClass(this);\r
3842 \r
3843                 this._handlers.push(handler);\r
3844 \r
3845                 if (this.options[name]) {\r
3846                         handler.enable();\r
3847                 }\r
3848 \r
3849                 return this;\r
3850         },\r
3851 \r
3852         // @method remove(): this\r
3853         // Destroys the map and clears all related event listeners.\r
3854         remove: function () {\r
3855 \r
3856                 this._initEvents(true);\r
3857                 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }\r
3858 \r
3859                 if (this._containerId !== this._container._leaflet_id) {\r
3860                         throw new Error('Map container is being reused by another instance');\r
3861                 }\r
3862 \r
3863                 try {\r
3864                         // throws error in IE6-8\r
3865                         delete this._container._leaflet_id;\r
3866                         delete this._containerId;\r
3867                 } catch (e) {\r
3868                         /*eslint-disable */\r
3869                         this._container._leaflet_id = undefined;\r
3870                         /* eslint-enable */\r
3871                         this._containerId = undefined;\r
3872                 }\r
3873 \r
3874                 if (this._locationWatchId !== undefined) {\r
3875                         this.stopLocate();\r
3876                 }\r
3877 \r
3878                 this._stop();\r
3879 \r
3880                 remove(this._mapPane);\r
3881 \r
3882                 if (this._clearControlPos) {\r
3883                         this._clearControlPos();\r
3884                 }\r
3885                 if (this._resizeRequest) {\r
3886                         cancelAnimFrame(this._resizeRequest);\r
3887                         this._resizeRequest = null;\r
3888                 }\r
3889 \r
3890                 this._clearHandlers();\r
3891 \r
3892                 if (this._loaded) {\r
3893                         // @section Map state change events\r
3894                         // @event unload: Event\r
3895                         // Fired when the map is destroyed with [remove](#map-remove) method.\r
3896                         this.fire('unload');\r
3897                 }\r
3898 \r
3899                 var i;\r
3900                 for (i in this._layers) {\r
3901                         this._layers[i].remove();\r
3902                 }\r
3903                 for (i in this._panes) {\r
3904                         remove(this._panes[i]);\r
3905                 }\r
3906 \r
3907                 this._layers = [];\r
3908                 this._panes = [];\r
3909                 delete this._mapPane;\r
3910                 delete this._renderer;\r
3911 \r
3912                 return this;\r
3913         },\r
3914 \r
3915         // @section Other Methods\r
3916         // @method createPane(name: String, container?: HTMLElement): HTMLElement\r
3917         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,\r
3918         // then returns it. The pane is created as a child of `container`, or\r
3919         // as a child of the main map pane if not set.\r
3920         createPane: function (name, container) {\r
3921                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),\r
3922                     pane = create$1('div', className, container || this._mapPane);\r
3923 \r
3924                 if (name) {\r
3925                         this._panes[name] = pane;\r
3926                 }\r
3927                 return pane;\r
3928         },\r
3929 \r
3930         // @section Methods for Getting Map State\r
3931 \r
3932         // @method getCenter(): LatLng\r
3933         // Returns the geographical center of the map view\r
3934         getCenter: function () {\r
3935                 this._checkIfLoaded();\r
3936 \r
3937                 if (this._lastCenter && !this._moved()) {\r
3938                         return this._lastCenter.clone();\r
3939                 }\r
3940                 return this.layerPointToLatLng(this._getCenterLayerPoint());\r
3941         },\r
3942 \r
3943         // @method getZoom(): Number\r
3944         // Returns the current zoom level of the map view\r
3945         getZoom: function () {\r
3946                 return this._zoom;\r
3947         },\r
3948 \r
3949         // @method getBounds(): LatLngBounds\r
3950         // Returns the geographical bounds visible in the current map view\r
3951         getBounds: function () {\r
3952                 var bounds = this.getPixelBounds(),\r
3953                     sw = this.unproject(bounds.getBottomLeft()),\r
3954                     ne = this.unproject(bounds.getTopRight());\r
3955 \r
3956                 return new LatLngBounds(sw, ne);\r
3957         },\r
3958 \r
3959         // @method getMinZoom(): Number\r
3960         // 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.\r
3961         getMinZoom: function () {\r
3962                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;\r
3963         },\r
3964 \r
3965         // @method getMaxZoom(): Number\r
3966         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).\r
3967         getMaxZoom: function () {\r
3968                 return this.options.maxZoom === undefined ?\r
3969                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :\r
3970                         this.options.maxZoom;\r
3971         },\r
3972 \r
3973         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number\r
3974         // Returns the maximum zoom level on which the given bounds fit to the map\r
3975         // view in its entirety. If `inside` (optional) is set to `true`, the method\r
3976         // instead returns the minimum zoom level on which the map view fits into\r
3977         // the given bounds in its entirety.\r
3978         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number\r
3979                 bounds = toLatLngBounds(bounds);\r
3980                 padding = toPoint(padding || [0, 0]);\r
3981 \r
3982                 var zoom = this.getZoom() || 0,\r
3983                     min = this.getMinZoom(),\r
3984                     max = this.getMaxZoom(),\r
3985                     nw = bounds.getNorthWest(),\r
3986                     se = bounds.getSouthEast(),\r
3987                     size = this.getSize().subtract(padding),\r
3988                     boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),\r
3989                     snap = Browser.any3d ? this.options.zoomSnap : 1,\r
3990                     scalex = size.x / boundsSize.x,\r
3991                     scaley = size.y / boundsSize.y,\r
3992                     scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);\r
3993 \r
3994                 zoom = this.getScaleZoom(scale, zoom);\r
3995 \r
3996                 if (snap) {\r
3997                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level\r
3998                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;\r
3999                 }\r
4000 \r
4001                 return Math.max(min, Math.min(max, zoom));\r
4002         },\r
4003 \r
4004         // @method getSize(): Point\r
4005         // Returns the current size of the map container (in pixels).\r
4006         getSize: function () {\r
4007                 if (!this._size || this._sizeChanged) {\r
4008                         this._size = new Point(\r
4009                                 this._container.clientWidth || 0,\r
4010                                 this._container.clientHeight || 0);\r
4011 \r
4012                         this._sizeChanged = false;\r
4013                 }\r
4014                 return this._size.clone();\r
4015         },\r
4016 \r
4017         // @method getPixelBounds(): Bounds\r
4018         // Returns the bounds of the current map view in projected pixel\r
4019         // coordinates (sometimes useful in layer and overlay implementations).\r
4020         getPixelBounds: function (center, zoom) {\r
4021                 var topLeftPoint = this._getTopLeftPoint(center, zoom);\r
4022                 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));\r
4023         },\r
4024 \r
4025         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to\r
4026         // the map pane? "left point of the map layer" can be confusing, specially\r
4027         // since there can be negative offsets.\r
4028         // @method getPixelOrigin(): Point\r
4029         // Returns the projected pixel coordinates of the top left point of\r
4030         // the map layer (useful in custom layer and overlay implementations).\r
4031         getPixelOrigin: function () {\r
4032                 this._checkIfLoaded();\r
4033                 return this._pixelOrigin;\r
4034         },\r
4035 \r
4036         // @method getPixelWorldBounds(zoom?: Number): Bounds\r
4037         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.\r
4038         // If `zoom` is omitted, the map's current zoom level is used.\r
4039         getPixelWorldBounds: function (zoom) {\r
4040                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);\r
4041         },\r
4042 \r
4043         // @section Other Methods\r
4044 \r
4045         // @method getPane(pane: String|HTMLElement): HTMLElement\r
4046         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).\r
4047         getPane: function (pane) {\r
4048                 return typeof pane === 'string' ? this._panes[pane] : pane;\r
4049         },\r
4050 \r
4051         // @method getPanes(): Object\r
4052         // Returns a plain object containing the names of all [panes](#map-pane) as keys and\r
4053         // the panes as values.\r
4054         getPanes: function () {\r
4055                 return this._panes;\r
4056         },\r
4057 \r
4058         // @method getContainer: HTMLElement\r
4059         // Returns the HTML element that contains the map.\r
4060         getContainer: function () {\r
4061                 return this._container;\r
4062         },\r
4063 \r
4064 \r
4065         // @section Conversion Methods\r
4066 \r
4067         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number\r
4068         // Returns the scale factor to be applied to a map transition from zoom level\r
4069         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.\r
4070         getZoomScale: function (toZoom, fromZoom) {\r
4071                 // TODO replace with universal implementation after refactoring projections\r
4072                 var crs = this.options.crs;\r
4073                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;\r
4074                 return crs.scale(toZoom) / crs.scale(fromZoom);\r
4075         },\r
4076 \r
4077         // @method getScaleZoom(scale: Number, fromZoom: Number): Number\r
4078         // Returns the zoom level that the map would end up at, if it is at `fromZoom`\r
4079         // level and everything is scaled by a factor of `scale`. Inverse of\r
4080         // [`getZoomScale`](#map-getZoomScale).\r
4081         getScaleZoom: function (scale, fromZoom) {\r
4082                 var crs = this.options.crs;\r
4083                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;\r
4084                 var zoom = crs.zoom(scale * crs.scale(fromZoom));\r
4085                 return isNaN(zoom) ? Infinity : zoom;\r
4086         },\r
4087 \r
4088         // @method project(latlng: LatLng, zoom: Number): Point\r
4089         // Projects a geographical coordinate `LatLng` according to the projection\r
4090         // of the map's CRS, then scales it according to `zoom` and the CRS's\r
4091         // `Transformation`. The result is pixel coordinate relative to\r
4092         // the CRS origin.\r
4093         project: function (latlng, zoom) {\r
4094                 zoom = zoom === undefined ? this._zoom : zoom;\r
4095                 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);\r
4096         },\r
4097 \r
4098         // @method unproject(point: Point, zoom: Number): LatLng\r
4099         // Inverse of [`project`](#map-project).\r
4100         unproject: function (point, zoom) {\r
4101                 zoom = zoom === undefined ? this._zoom : zoom;\r
4102                 return this.options.crs.pointToLatLng(toPoint(point), zoom);\r
4103         },\r
4104 \r
4105         // @method layerPointToLatLng(point: Point): LatLng\r
4106         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),\r
4107         // returns the corresponding geographical coordinate (for the current zoom level).\r
4108         layerPointToLatLng: function (point) {\r
4109                 var projectedPoint = toPoint(point).add(this.getPixelOrigin());\r
4110                 return this.unproject(projectedPoint);\r
4111         },\r
4112 \r
4113         // @method latLngToLayerPoint(latlng: LatLng): Point\r
4114         // Given a geographical coordinate, returns the corresponding pixel coordinate\r
4115         // relative to the [origin pixel](#map-getpixelorigin).\r
4116         latLngToLayerPoint: function (latlng) {\r
4117                 var projectedPoint = this.project(toLatLng(latlng))._round();\r
4118                 return projectedPoint._subtract(this.getPixelOrigin());\r
4119         },\r
4120 \r
4121         // @method wrapLatLng(latlng: LatLng): LatLng\r
4122         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the\r
4123         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the\r
4124         // CRS's bounds.\r
4125         // By default this means longitude is wrapped around the dateline so its\r
4126         // value is between -180 and +180 degrees.\r
4127         wrapLatLng: function (latlng) {\r
4128                 return this.options.crs.wrapLatLng(toLatLng(latlng));\r
4129         },\r
4130 \r
4131         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds\r
4132         // Returns a `LatLngBounds` with the same size as the given one, ensuring that\r
4133         // its center is within the CRS's bounds.\r
4134         // By default this means the center longitude is wrapped around the dateline so its\r
4135         // value is between -180 and +180 degrees, and the majority of the bounds\r
4136         // overlaps the CRS's bounds.\r
4137         wrapLatLngBounds: function (latlng) {\r
4138                 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));\r
4139         },\r
4140 \r
4141         // @method distance(latlng1: LatLng, latlng2: LatLng): Number\r
4142         // Returns the distance between two geographical coordinates according to\r
4143         // the map's CRS. By default this measures distance in meters.\r
4144         distance: function (latlng1, latlng2) {\r
4145                 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));\r
4146         },\r
4147 \r
4148         // @method containerPointToLayerPoint(point: Point): Point\r
4149         // Given a pixel coordinate relative to the map container, returns the corresponding\r
4150         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).\r
4151         containerPointToLayerPoint: function (point) { // (Point)\r
4152                 return toPoint(point).subtract(this._getMapPanePos());\r
4153         },\r
4154 \r
4155         // @method layerPointToContainerPoint(point: Point): Point\r
4156         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),\r
4157         // returns the corresponding pixel coordinate relative to the map container.\r
4158         layerPointToContainerPoint: function (point) { // (Point)\r
4159                 return toPoint(point).add(this._getMapPanePos());\r
4160         },\r
4161 \r
4162         // @method containerPointToLatLng(point: Point): LatLng\r
4163         // Given a pixel coordinate relative to the map container, returns\r
4164         // the corresponding geographical coordinate (for the current zoom level).\r
4165         containerPointToLatLng: function (point) {\r
4166                 var layerPoint = this.containerPointToLayerPoint(toPoint(point));\r
4167                 return this.layerPointToLatLng(layerPoint);\r
4168         },\r
4169 \r
4170         // @method latLngToContainerPoint(latlng: LatLng): Point\r
4171         // Given a geographical coordinate, returns the corresponding pixel coordinate\r
4172         // relative to the map container.\r
4173         latLngToContainerPoint: function (latlng) {\r
4174                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));\r
4175         },\r
4176 \r
4177         // @method mouseEventToContainerPoint(ev: MouseEvent): Point\r
4178         // Given a MouseEvent object, returns the pixel coordinate relative to the\r
4179         // map container where the event took place.\r
4180         mouseEventToContainerPoint: function (e) {\r
4181                 return getMousePosition(e, this._container);\r
4182         },\r
4183 \r
4184         // @method mouseEventToLayerPoint(ev: MouseEvent): Point\r
4185         // Given a MouseEvent object, returns the pixel coordinate relative to\r
4186         // the [origin pixel](#map-getpixelorigin) where the event took place.\r
4187         mouseEventToLayerPoint: function (e) {\r
4188                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));\r
4189         },\r
4190 \r
4191         // @method mouseEventToLatLng(ev: MouseEvent): LatLng\r
4192         // Given a MouseEvent object, returns geographical coordinate where the\r
4193         // event took place.\r
4194         mouseEventToLatLng: function (e) { // (MouseEvent)\r
4195                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));\r
4196         },\r
4197 \r
4198 \r
4199         // map initialization methods\r
4200 \r
4201         _initContainer: function (id) {\r
4202                 var container = this._container = get(id);\r
4203 \r
4204                 if (!container) {\r
4205                         throw new Error('Map container not found.');\r
4206                 } else if (container._leaflet_id) {\r
4207                         throw new Error('Map container is already initialized.');\r
4208                 }\r
4209 \r
4210                 on(container, 'scroll', this._onScroll, this);\r
4211                 this._containerId = stamp(container);\r
4212         },\r
4213 \r
4214         _initLayout: function () {\r
4215                 var container = this._container;\r
4216 \r
4217                 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;\r
4218 \r
4219                 addClass(container, 'leaflet-container' +\r
4220                         (Browser.touch ? ' leaflet-touch' : '') +\r
4221                         (Browser.retina ? ' leaflet-retina' : '') +\r
4222                         (Browser.ielt9 ? ' leaflet-oldie' : '') +\r
4223                         (Browser.safari ? ' leaflet-safari' : '') +\r
4224                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));\r
4225 \r
4226                 var position = getStyle(container, 'position');\r
4227 \r
4228                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {\r
4229                         container.style.position = 'relative';\r
4230                 }\r
4231 \r
4232                 this._initPanes();\r
4233 \r
4234                 if (this._initControlPos) {\r
4235                         this._initControlPos();\r
4236                 }\r
4237         },\r
4238 \r
4239         _initPanes: function () {\r
4240                 var panes = this._panes = {};\r
4241                 this._paneRenderers = {};\r
4242 \r
4243                 // @section\r
4244                 //\r
4245                 // Panes are DOM elements used to control the ordering of layers on the map. You\r
4246                 // can access panes with [`map.getPane`](#map-getpane) or\r
4247                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the\r
4248                 // [`map.createPane`](#map-createpane) method.\r
4249                 //\r
4250                 // Every map has the following default panes that differ only in zIndex.\r
4251                 //\r
4252                 // @pane mapPane: HTMLElement = 'auto'\r
4253                 // Pane that contains all other map panes\r
4254 \r
4255                 this._mapPane = this.createPane('mapPane', this._container);\r
4256                 setPosition(this._mapPane, new Point(0, 0));\r
4257 \r
4258                 // @pane tilePane: HTMLElement = 200\r
4259                 // Pane for `GridLayer`s and `TileLayer`s\r
4260                 this.createPane('tilePane');\r
4261                 // @pane overlayPane: HTMLElement = 400\r
4262                 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s\r
4263                 this.createPane('overlayPane');\r
4264                 // @pane shadowPane: HTMLElement = 500\r
4265                 // Pane for overlay shadows (e.g. `Marker` shadows)\r
4266                 this.createPane('shadowPane');\r
4267                 // @pane markerPane: HTMLElement = 600\r
4268                 // Pane for `Icon`s of `Marker`s\r
4269                 this.createPane('markerPane');\r
4270                 // @pane tooltipPane: HTMLElement = 650\r
4271                 // Pane for `Tooltip`s.\r
4272                 this.createPane('tooltipPane');\r
4273                 // @pane popupPane: HTMLElement = 700\r
4274                 // Pane for `Popup`s.\r
4275                 this.createPane('popupPane');\r
4276 \r
4277                 if (!this.options.markerZoomAnimation) {\r
4278                         addClass(panes.markerPane, 'leaflet-zoom-hide');\r
4279                         addClass(panes.shadowPane, 'leaflet-zoom-hide');\r
4280                 }\r
4281         },\r
4282 \r
4283 \r
4284         // private methods that modify map state\r
4285 \r
4286         // @section Map state change events\r
4287         _resetView: function (center, zoom, noMoveStart) {\r
4288                 setPosition(this._mapPane, new Point(0, 0));\r
4289 \r
4290                 var loading = !this._loaded;\r
4291                 this._loaded = true;\r
4292                 zoom = this._limitZoom(zoom);\r
4293 \r
4294                 this.fire('viewprereset');\r
4295 \r
4296                 var zoomChanged = this._zoom !== zoom;\r
4297                 this\r
4298                         ._moveStart(zoomChanged, noMoveStart)\r
4299                         ._move(center, zoom)\r
4300                         ._moveEnd(zoomChanged);\r
4301 \r
4302                 // @event viewreset: Event\r
4303                 // Fired when the map needs to redraw its content (this usually happens\r
4304                 // on map zoom or load). Very useful for creating custom overlays.\r
4305                 this.fire('viewreset');\r
4306 \r
4307                 // @event load: Event\r
4308                 // Fired when the map is initialized (when its center and zoom are set\r
4309                 // for the first time).\r
4310                 if (loading) {\r
4311                         this.fire('load');\r
4312                 }\r
4313         },\r
4314 \r
4315         _moveStart: function (zoomChanged, noMoveStart) {\r
4316                 // @event zoomstart: Event\r
4317                 // Fired when the map zoom is about to change (e.g. before zoom animation).\r
4318                 // @event movestart: Event\r
4319                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).\r
4320                 if (zoomChanged) {\r
4321                         this.fire('zoomstart');\r
4322                 }\r
4323                 if (!noMoveStart) {\r
4324                         this.fire('movestart');\r
4325                 }\r
4326                 return this;\r
4327         },\r
4328 \r
4329         _move: function (center, zoom, data, supressEvent) {\r
4330                 if (zoom === undefined) {\r
4331                         zoom = this._zoom;\r
4332                 }\r
4333                 var zoomChanged = this._zoom !== zoom;\r
4334 \r
4335                 this._zoom = zoom;\r
4336                 this._lastCenter = center;\r
4337                 this._pixelOrigin = this._getNewPixelOrigin(center);\r
4338 \r
4339                 if (!supressEvent) {\r
4340                         // @event zoom: Event\r
4341                         // Fired repeatedly during any change in zoom level,\r
4342                         // including zoom and fly animations.\r
4343                         if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530\r
4344                                 this.fire('zoom', data);\r
4345                         }\r
4346 \r
4347                         // @event move: Event\r
4348                         // Fired repeatedly during any movement of the map,\r
4349                         // including pan and fly animations.\r
4350                         this.fire('move', data);\r
4351                 } else if (data && data.pinch) {        // Always fire 'zoom' if pinching because #3530\r
4352                         this.fire('zoom', data);\r
4353                 }\r
4354                 return this;\r
4355         },\r
4356 \r
4357         _moveEnd: function (zoomChanged) {\r
4358                 // @event zoomend: Event\r
4359                 // Fired when the map zoom changed, after any animations.\r
4360                 if (zoomChanged) {\r
4361                         this.fire('zoomend');\r
4362                 }\r
4363 \r
4364                 // @event moveend: Event\r
4365                 // Fired when the center of the map stops changing\r
4366                 // (e.g. user stopped dragging the map or after non-centered zoom).\r
4367                 return this.fire('moveend');\r
4368         },\r
4369 \r
4370         _stop: function () {\r
4371                 cancelAnimFrame(this._flyToFrame);\r
4372                 if (this._panAnim) {\r
4373                         this._panAnim.stop();\r
4374                 }\r
4375                 return this;\r
4376         },\r
4377 \r
4378         _rawPanBy: function (offset) {\r
4379                 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));\r
4380         },\r
4381 \r
4382         _getZoomSpan: function () {\r
4383                 return this.getMaxZoom() - this.getMinZoom();\r
4384         },\r
4385 \r
4386         _panInsideMaxBounds: function () {\r
4387                 if (!this._enforcingBounds) {\r
4388                         this.panInsideBounds(this.options.maxBounds);\r
4389                 }\r
4390         },\r
4391 \r
4392         _checkIfLoaded: function () {\r
4393                 if (!this._loaded) {\r
4394                         throw new Error('Set map center and zoom first.');\r
4395                 }\r
4396         },\r
4397 \r
4398         // DOM event handling\r
4399 \r
4400         // @section Interaction events\r
4401         _initEvents: function (remove) {\r
4402                 this._targets = {};\r
4403                 this._targets[stamp(this._container)] = this;\r
4404 \r
4405                 var onOff = remove ? off : on;\r
4406 \r
4407                 // @event click: MouseEvent\r
4408                 // Fired when the user clicks (or taps) the map.\r
4409                 // @event dblclick: MouseEvent\r
4410                 // Fired when the user double-clicks (or double-taps) the map.\r
4411                 // @event mousedown: MouseEvent\r
4412                 // Fired when the user pushes the mouse button on the map.\r
4413                 // @event mouseup: MouseEvent\r
4414                 // Fired when the user releases the mouse button on the map.\r
4415                 // @event mouseover: MouseEvent\r
4416                 // Fired when the mouse enters the map.\r
4417                 // @event mouseout: MouseEvent\r
4418                 // Fired when the mouse leaves the map.\r
4419                 // @event mousemove: MouseEvent\r
4420                 // Fired while the mouse moves over the map.\r
4421                 // @event contextmenu: MouseEvent\r
4422                 // Fired when the user pushes the right mouse button on the map, prevents\r
4423                 // default browser context menu from showing if there are listeners on\r
4424                 // this event. Also fired on mobile when the user holds a single touch\r
4425                 // for a second (also called long press).\r
4426                 // @event keypress: KeyboardEvent\r
4427                 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.\r
4428                 // @event keydown: KeyboardEvent\r
4429                 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,\r
4430                 // the `keydown` event is fired for keys that produce a character value and for keys\r
4431                 // that do not produce a character value.\r
4432                 // @event keyup: KeyboardEvent\r
4433                 // Fired when the user releases a key from the keyboard while the map is focused.\r
4434                 onOff(this._container, 'click dblclick mousedown mouseup ' +\r
4435                         'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);\r
4436 \r
4437                 if (this.options.trackResize) {\r
4438                         onOff(window, 'resize', this._onResize, this);\r
4439                 }\r
4440 \r
4441                 if (Browser.any3d && this.options.transform3DLimit) {\r
4442                         (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);\r
4443                 }\r
4444         },\r
4445 \r
4446         _onResize: function () {\r
4447                 cancelAnimFrame(this._resizeRequest);\r
4448                 this._resizeRequest = requestAnimFrame(\r
4449                         function () { this.invalidateSize({debounceMoveend: true}); }, this);\r
4450         },\r
4451 \r
4452         _onScroll: function () {\r
4453                 this._container.scrollTop  = 0;\r
4454                 this._container.scrollLeft = 0;\r
4455         },\r
4456 \r
4457         _onMoveEnd: function () {\r
4458                 var pos = this._getMapPanePos();\r
4459                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {\r
4460                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have\r
4461                         // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/\r
4462                         this._resetView(this.getCenter(), this.getZoom());\r
4463                 }\r
4464         },\r
4465 \r
4466         _findEventTargets: function (e, type) {\r
4467                 var targets = [],\r
4468                     target,\r
4469                     isHover = type === 'mouseout' || type === 'mouseover',\r
4470                     src = e.target || e.srcElement,\r
4471                     dragging = false;\r
4472 \r
4473                 while (src) {\r
4474                         target = this._targets[stamp(src)];\r
4475                         if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {\r
4476                                 // Prevent firing click after you just dragged an object.\r
4477                                 dragging = true;\r
4478                                 break;\r
4479                         }\r
4480                         if (target && target.listens(type, true)) {\r
4481                                 if (isHover && !isExternalTarget(src, e)) { break; }\r
4482                                 targets.push(target);\r
4483                                 if (isHover) { break; }\r
4484                         }\r
4485                         if (src === this._container) { break; }\r
4486                         src = src.parentNode;\r
4487                 }\r
4488                 if (!targets.length && !dragging && !isHover && this.listens(type, true)) {\r
4489                         targets = [this];\r
4490                 }\r
4491                 return targets;\r
4492         },\r
4493 \r
4494         _isClickDisabled: function (el) {\r
4495                 while (el && el !== this._container) {\r
4496                         if (el['_leaflet_disable_click']) { return true; }\r
4497                         el = el.parentNode;\r
4498                 }\r
4499         },\r
4500 \r
4501         _handleDOMEvent: function (e) {\r
4502                 var el = (e.target || e.srcElement);\r
4503                 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {\r
4504                         return;\r
4505                 }\r
4506 \r
4507                 var type = e.type;\r
4508 \r
4509                 if (type === 'mousedown') {\r
4510                         // prevents outline when clicking on keyboard-focusable element\r
4511                         preventOutline(el);\r
4512                 }\r
4513 \r
4514                 this._fireDOMEvent(e, type);\r
4515         },\r
4516 \r
4517         _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],\r
4518 \r
4519         _fireDOMEvent: function (e, type, canvasTargets) {\r
4520 \r
4521                 if (e.type === 'click') {\r
4522                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).\r
4523                         // @event preclick: MouseEvent\r
4524                         // Fired before mouse click on the map (sometimes useful when you\r
4525                         // want something to happen on click before any existing click\r
4526                         // handlers start running).\r
4527                         var synth = extend({}, e);\r
4528                         synth.type = 'preclick';\r
4529                         this._fireDOMEvent(synth, synth.type, canvasTargets);\r
4530                 }\r
4531 \r
4532                 // Find the layer the event is propagating from and its parents.\r
4533                 var targets = this._findEventTargets(e, type);\r
4534 \r
4535                 if (canvasTargets) {\r
4536                         var filtered = []; // pick only targets with listeners\r
4537                         for (var i = 0; i < canvasTargets.length; i++) {\r
4538                                 if (canvasTargets[i].listens(type, true)) {\r
4539                                         filtered.push(canvasTargets[i]);\r
4540                                 }\r
4541                         }\r
4542                         targets = filtered.concat(targets);\r
4543                 }\r
4544 \r
4545                 if (!targets.length) { return; }\r
4546 \r
4547                 if (type === 'contextmenu') {\r
4548                         preventDefault(e);\r
4549                 }\r
4550 \r
4551                 var target = targets[0];\r
4552                 var data = {\r
4553                         originalEvent: e\r
4554                 };\r
4555 \r
4556                 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {\r
4557                         var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);\r
4558                         data.containerPoint = isMarker ?\r
4559                                 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);\r
4560                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);\r
4561                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);\r
4562                 }\r
4563 \r
4564                 for (i = 0; i < targets.length; i++) {\r
4565                         targets[i].fire(type, data, true);\r
4566                         if (data.originalEvent._stopped ||\r
4567                                 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }\r
4568                 }\r
4569         },\r
4570 \r
4571         _draggableMoved: function (obj) {\r
4572                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;\r
4573                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());\r
4574         },\r
4575 \r
4576         _clearHandlers: function () {\r
4577                 for (var i = 0, len = this._handlers.length; i < len; i++) {\r
4578                         this._handlers[i].disable();\r
4579                 }\r
4580         },\r
4581 \r
4582         // @section Other Methods\r
4583 \r
4584         // @method whenReady(fn: Function, context?: Object): this\r
4585         // Runs the given function `fn` when the map gets initialized with\r
4586         // a view (center and zoom) and at least one layer, or immediately\r
4587         // if it's already initialized, optionally passing a function context.\r
4588         whenReady: function (callback, context) {\r
4589                 if (this._loaded) {\r
4590                         callback.call(context || this, {target: this});\r
4591                 } else {\r
4592                         this.on('load', callback, context);\r
4593                 }\r
4594                 return this;\r
4595         },\r
4596 \r
4597 \r
4598         // private methods for getting map state\r
4599 \r
4600         _getMapPanePos: function () {\r
4601                 return getPosition(this._mapPane) || new Point(0, 0);\r
4602         },\r
4603 \r
4604         _moved: function () {\r
4605                 var pos = this._getMapPanePos();\r
4606                 return pos && !pos.equals([0, 0]);\r
4607         },\r
4608 \r
4609         _getTopLeftPoint: function (center, zoom) {\r
4610                 var pixelOrigin = center && zoom !== undefined ?\r
4611                         this._getNewPixelOrigin(center, zoom) :\r
4612                         this.getPixelOrigin();\r
4613                 return pixelOrigin.subtract(this._getMapPanePos());\r
4614         },\r
4615 \r
4616         _getNewPixelOrigin: function (center, zoom) {\r
4617                 var viewHalf = this.getSize()._divideBy(2);\r
4618                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();\r
4619         },\r
4620 \r
4621         _latLngToNewLayerPoint: function (latlng, zoom, center) {\r
4622                 var topLeft = this._getNewPixelOrigin(center, zoom);\r
4623                 return this.project(latlng, zoom)._subtract(topLeft);\r
4624         },\r
4625 \r
4626         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {\r
4627                 var topLeft = this._getNewPixelOrigin(center, zoom);\r
4628                 return toBounds([\r
4629                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),\r
4630                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),\r
4631                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),\r
4632                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)\r
4633                 ]);\r
4634         },\r
4635 \r
4636         // layer point of the current center\r
4637         _getCenterLayerPoint: function () {\r
4638                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));\r
4639         },\r
4640 \r
4641         // offset of the specified place to the current center in pixels\r
4642         _getCenterOffset: function (latlng) {\r
4643                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());\r
4644         },\r
4645 \r
4646         // adjust center for view to get inside bounds\r
4647         _limitCenter: function (center, zoom, bounds) {\r
4648 \r
4649                 if (!bounds) { return center; }\r
4650 \r
4651                 var centerPoint = this.project(center, zoom),\r
4652                     viewHalf = this.getSize().divideBy(2),\r
4653                     viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),\r
4654                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);\r
4655 \r
4656                 // If offset is less than a pixel, ignore.\r
4657                 // This prevents unstable projections from getting into\r
4658                 // an infinite loop of tiny offsets.\r
4659                 if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {\r
4660                         return center;\r
4661                 }\r
4662 \r
4663                 return this.unproject(centerPoint.add(offset), zoom);\r
4664         },\r
4665 \r
4666         // adjust offset for view to get inside bounds\r
4667         _limitOffset: function (offset, bounds) {\r
4668                 if (!bounds) { return offset; }\r
4669 \r
4670                 var viewBounds = this.getPixelBounds(),\r
4671                     newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));\r
4672 \r
4673                 return offset.add(this._getBoundsOffset(newBounds, bounds));\r
4674         },\r
4675 \r
4676         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom\r
4677         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {\r
4678                 var projectedMaxBounds = toBounds(\r
4679                         this.project(maxBounds.getNorthEast(), zoom),\r
4680                         this.project(maxBounds.getSouthWest(), zoom)\r
4681                     ),\r
4682                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),\r
4683                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),\r
4684 \r
4685                     dx = this._rebound(minOffset.x, -maxOffset.x),\r
4686                     dy = this._rebound(minOffset.y, -maxOffset.y);\r
4687 \r
4688                 return new Point(dx, dy);\r
4689         },\r
4690 \r
4691         _rebound: function (left, right) {\r
4692                 return left + right > 0 ?\r
4693                         Math.round(left - right) / 2 :\r
4694                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));\r
4695         },\r
4696 \r
4697         _limitZoom: function (zoom) {\r
4698                 var min = this.getMinZoom(),\r
4699                     max = this.getMaxZoom(),\r
4700                     snap = Browser.any3d ? this.options.zoomSnap : 1;\r
4701                 if (snap) {\r
4702                         zoom = Math.round(zoom / snap) * snap;\r
4703                 }\r
4704                 return Math.max(min, Math.min(max, zoom));\r
4705         },\r
4706 \r
4707         _onPanTransitionStep: function () {\r
4708                 this.fire('move');\r
4709         },\r
4710 \r
4711         _onPanTransitionEnd: function () {\r
4712                 removeClass(this._mapPane, 'leaflet-pan-anim');\r
4713                 this.fire('moveend');\r
4714         },\r
4715 \r
4716         _tryAnimatedPan: function (center, options) {\r
4717                 // difference between the new and current centers in pixels\r
4718                 var offset = this._getCenterOffset(center)._trunc();\r
4719 \r
4720                 // don't animate too far unless animate: true specified in options\r
4721                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }\r
4722 \r
4723                 this.panBy(offset, options);\r
4724 \r
4725                 return true;\r
4726         },\r
4727 \r
4728         _createAnimProxy: function () {\r
4729 \r
4730                 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');\r
4731                 this._panes.mapPane.appendChild(proxy);\r
4732 \r
4733                 this.on('zoomanim', function (e) {\r
4734                         var prop = TRANSFORM,\r
4735                             transform = this._proxy.style[prop];\r
4736 \r
4737                         setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));\r
4738 \r
4739                         // workaround for case when transform is the same and so transitionend event is not fired\r
4740                         if (transform === this._proxy.style[prop] && this._animatingZoom) {\r
4741                                 this._onZoomTransitionEnd();\r
4742                         }\r
4743                 }, this);\r
4744 \r
4745                 this.on('load moveend', this._animMoveEnd, this);\r
4746 \r
4747                 this._on('unload', this._destroyAnimProxy, this);\r
4748         },\r
4749 \r
4750         _destroyAnimProxy: function () {\r
4751                 remove(this._proxy);\r
4752                 this.off('load moveend', this._animMoveEnd, this);\r
4753                 delete this._proxy;\r
4754         },\r
4755 \r
4756         _animMoveEnd: function () {\r
4757                 var c = this.getCenter(),\r
4758                     z = this.getZoom();\r
4759                 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));\r
4760         },\r
4761 \r
4762         _catchTransitionEnd: function (e) {\r
4763                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {\r
4764                         this._onZoomTransitionEnd();\r
4765                 }\r
4766         },\r
4767 \r
4768         _nothingToAnimate: function () {\r
4769                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;\r
4770         },\r
4771 \r
4772         _tryAnimatedZoom: function (center, zoom, options) {\r
4773 \r
4774                 if (this._animatingZoom) { return true; }\r
4775 \r
4776                 options = options || {};\r
4777 \r
4778                 // don't animate if disabled, not supported or zoom difference is too large\r
4779                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||\r
4780                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }\r
4781 \r
4782                 // offset is the pixel coords of the zoom origin relative to the current center\r
4783                 var scale = this.getZoomScale(zoom),\r
4784                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);\r
4785 \r
4786                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced\r
4787                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }\r
4788 \r
4789                 requestAnimFrame(function () {\r
4790                         this\r
4791                             ._moveStart(true, options.noMoveStart || false)\r
4792                             ._animateZoom(center, zoom, true);\r
4793                 }, this);\r
4794 \r
4795                 return true;\r
4796         },\r
4797 \r
4798         _animateZoom: function (center, zoom, startAnim, noUpdate) {\r
4799                 if (!this._mapPane) { return; }\r
4800 \r
4801                 if (startAnim) {\r
4802                         this._animatingZoom = true;\r
4803 \r
4804                         // remember what center/zoom to set after animation\r
4805                         this._animateToCenter = center;\r
4806                         this._animateToZoom = zoom;\r
4807 \r
4808                         addClass(this._mapPane, 'leaflet-zoom-anim');\r
4809                 }\r
4810 \r
4811                 // @section Other Events\r
4812                 // @event zoomanim: ZoomAnimEvent\r
4813                 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.\r
4814                 this.fire('zoomanim', {\r
4815                         center: center,\r
4816                         zoom: zoom,\r
4817                         noUpdate: noUpdate\r
4818                 });\r
4819 \r
4820                 if (!this._tempFireZoomEvent) {\r
4821                         this._tempFireZoomEvent = this._zoom !== this._animateToZoom;\r
4822                 }\r
4823 \r
4824                 this._move(this._animateToCenter, this._animateToZoom, undefined, true);\r
4825 \r
4826                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693\r
4827                 setTimeout(bind(this._onZoomTransitionEnd, this), 250);\r
4828         },\r
4829 \r
4830         _onZoomTransitionEnd: function () {\r
4831                 if (!this._animatingZoom) { return; }\r
4832 \r
4833                 if (this._mapPane) {\r
4834                         removeClass(this._mapPane, 'leaflet-zoom-anim');\r
4835                 }\r
4836 \r
4837                 this._animatingZoom = false;\r
4838 \r
4839                 this._move(this._animateToCenter, this._animateToZoom, undefined, true);\r
4840 \r
4841                 if (this._tempFireZoomEvent) {\r
4842                         this.fire('zoom');\r
4843                 }\r
4844                 delete this._tempFireZoomEvent;\r
4845 \r
4846                 this.fire('move');\r
4847 \r
4848                 this._moveEnd(true);\r
4849         }\r
4850   });\r
4851 \r
4852   // @section\r
4853 \r
4854   // @factory L.map(id: String, options?: Map options)\r
4855   // Instantiates a map object given the DOM ID of a `<div>` element\r
4856   // and optionally an object literal with `Map options`.\r
4857   //\r
4858   // @alternative\r
4859   // @factory L.map(el: HTMLElement, options?: Map options)\r
4860   // Instantiates a map object given an instance of a `<div>` HTML element\r
4861   // and optionally an object literal with `Map options`.\r
4862   function createMap(id, options) {\r
4863         return new Map(id, options);\r
4864   }
4865
4866   /*\r
4867    * @class Control\r
4868    * @aka L.Control\r
4869    * @inherits Class\r
4870    *\r
4871    * L.Control is a base class for implementing map controls. Handles positioning.\r
4872    * All other controls extend from this class.\r
4873    */\r
4874 \r
4875   var Control = Class.extend({\r
4876         // @section\r
4877         // @aka Control Options\r
4878         options: {\r
4879                 // @option position: String = 'topright'\r
4880                 // The position of the control (one of the map corners). Possible values are `'topleft'`,\r
4881                 // `'topright'`, `'bottomleft'` or `'bottomright'`\r
4882                 position: 'topright'\r
4883         },\r
4884 \r
4885         initialize: function (options) {\r
4886                 setOptions(this, options);\r
4887         },\r
4888 \r
4889         /* @section\r
4890          * Classes extending L.Control will inherit the following methods:\r
4891          *\r
4892          * @method getPosition: string\r
4893          * Returns the position of the control.\r
4894          */\r
4895         getPosition: function () {\r
4896                 return this.options.position;\r
4897         },\r
4898 \r
4899         // @method setPosition(position: string): this\r
4900         // Sets the position of the control.\r
4901         setPosition: function (position) {\r
4902                 var map = this._map;\r
4903 \r
4904                 if (map) {\r
4905                         map.removeControl(this);\r
4906                 }\r
4907 \r
4908                 this.options.position = position;\r
4909 \r
4910                 if (map) {\r
4911                         map.addControl(this);\r
4912                 }\r
4913 \r
4914                 return this;\r
4915         },\r
4916 \r
4917         // @method getContainer: HTMLElement\r
4918         // Returns the HTMLElement that contains the control.\r
4919         getContainer: function () {\r
4920                 return this._container;\r
4921         },\r
4922 \r
4923         // @method addTo(map: Map): this\r
4924         // Adds the control to the given map.\r
4925         addTo: function (map) {\r
4926                 this.remove();\r
4927                 this._map = map;\r
4928 \r
4929                 var container = this._container = this.onAdd(map),\r
4930                     pos = this.getPosition(),\r
4931                     corner = map._controlCorners[pos];\r
4932 \r
4933                 addClass(container, 'leaflet-control');\r
4934 \r
4935                 if (pos.indexOf('bottom') !== -1) {\r
4936                         corner.insertBefore(container, corner.firstChild);\r
4937                 } else {\r
4938                         corner.appendChild(container);\r
4939                 }\r
4940 \r
4941                 this._map.on('unload', this.remove, this);\r
4942 \r
4943                 return this;\r
4944         },\r
4945 \r
4946         // @method remove: this\r
4947         // Removes the control from the map it is currently active on.\r
4948         remove: function () {\r
4949                 if (!this._map) {\r
4950                         return this;\r
4951                 }\r
4952 \r
4953                 remove(this._container);\r
4954 \r
4955                 if (this.onRemove) {\r
4956                         this.onRemove(this._map);\r
4957                 }\r
4958 \r
4959                 this._map.off('unload', this.remove, this);\r
4960                 this._map = null;\r
4961 \r
4962                 return this;\r
4963         },\r
4964 \r
4965         _refocusOnMap: function (e) {\r
4966                 // if map exists and event is not a keyboard event\r
4967                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {\r
4968                         this._map.getContainer().focus();\r
4969                 }\r
4970         }\r
4971   });\r
4972 \r
4973   var control = function (options) {\r
4974         return new Control(options);\r
4975   };\r
4976 \r
4977   /* @section Extension methods\r
4978    * @uninheritable\r
4979    *\r
4980    * Every control should extend from `L.Control` and (re-)implement the following methods.\r
4981    *\r
4982    * @method onAdd(map: Map): HTMLElement\r
4983    * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).\r
4984    *\r
4985    * @method onRemove(map: Map)\r
4986    * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).\r
4987    */\r
4988 \r
4989   /* @namespace Map\r
4990    * @section Methods for Layers and Controls\r
4991    */\r
4992   Map.include({\r
4993         // @method addControl(control: Control): this\r
4994         // Adds the given control to the map\r
4995         addControl: function (control) {\r
4996                 control.addTo(this);\r
4997                 return this;\r
4998         },\r
4999 \r
5000         // @method removeControl(control: Control): this\r
5001         // Removes the given control from the map\r
5002         removeControl: function (control) {\r
5003                 control.remove();\r
5004                 return this;\r
5005         },\r
5006 \r
5007         _initControlPos: function () {\r
5008                 var corners = this._controlCorners = {},\r
5009                     l = 'leaflet-',\r
5010                     container = this._controlContainer =\r
5011                             create$1('div', l + 'control-container', this._container);\r
5012 \r
5013                 function createCorner(vSide, hSide) {\r
5014                         var className = l + vSide + ' ' + l + hSide;\r
5015 \r
5016                         corners[vSide + hSide] = create$1('div', className, container);\r
5017                 }\r
5018 \r
5019                 createCorner('top', 'left');\r
5020                 createCorner('top', 'right');\r
5021                 createCorner('bottom', 'left');\r
5022                 createCorner('bottom', 'right');\r
5023         },\r
5024 \r
5025         _clearControlPos: function () {\r
5026                 for (var i in this._controlCorners) {\r
5027                         remove(this._controlCorners[i]);\r
5028                 }\r
5029                 remove(this._controlContainer);\r
5030                 delete this._controlCorners;\r
5031                 delete this._controlContainer;\r
5032         }\r
5033   });
5034
5035   /*\r
5036    * @class Control.Layers\r
5037    * @aka L.Control.Layers\r
5038    * @inherits Control\r
5039    *\r
5040    * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`.\r
5041    *\r
5042    * @example\r
5043    *\r
5044    * ```js\r
5045    * var baseLayers = {\r
5046    *    "Mapbox": mapbox,\r
5047    *    "OpenStreetMap": osm\r
5048    * };\r
5049    *\r
5050    * var overlays = {\r
5051    *    "Marker": marker,\r
5052    *    "Roads": roadsLayer\r
5053    * };\r
5054    *\r
5055    * L.control.layers(baseLayers, overlays).addTo(map);\r
5056    * ```\r
5057    *\r
5058    * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:\r
5059    *\r
5060    * ```js\r
5061    * {\r
5062    *     "<someName1>": layer1,\r
5063    *     "<someName2>": layer2\r
5064    * }\r
5065    * ```\r
5066    *\r
5067    * The layer names can contain HTML, which allows you to add additional styling to the items:\r
5068    *\r
5069    * ```js\r
5070    * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}\r
5071    * ```\r
5072    */\r
5073 \r
5074   var Layers = Control.extend({\r
5075         // @section\r
5076         // @aka Control.Layers options\r
5077         options: {\r
5078                 // @option collapsed: Boolean = true\r
5079                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.\r
5080                 collapsed: true,\r
5081                 position: 'topright',\r
5082 \r
5083                 // @option autoZIndex: Boolean = true\r
5084                 // 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.\r
5085                 autoZIndex: true,\r
5086 \r
5087                 // @option hideSingleBase: Boolean = false\r
5088                 // If `true`, the base layers in the control will be hidden when there is only one.\r
5089                 hideSingleBase: false,\r
5090 \r
5091                 // @option sortLayers: Boolean = false\r
5092                 // Whether to sort the layers. When `false`, layers will keep the order\r
5093                 // in which they were added to the control.\r
5094                 sortLayers: false,\r
5095 \r
5096                 // @option sortFunction: Function = *\r
5097                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)\r
5098                 // that will be used for sorting the layers, when `sortLayers` is `true`.\r
5099                 // The function receives both the `L.Layer` instances and their names, as in\r
5100                 // `sortFunction(layerA, layerB, nameA, nameB)`.\r
5101                 // By default, it sorts layers alphabetically by their name.\r
5102                 sortFunction: function (layerA, layerB, nameA, nameB) {\r
5103                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);\r
5104                 }\r
5105         },\r
5106 \r
5107         initialize: function (baseLayers, overlays, options) {\r
5108                 setOptions(this, options);\r
5109 \r
5110                 this._layerControlInputs = [];\r
5111                 this._layers = [];\r
5112                 this._lastZIndex = 0;\r
5113                 this._handlingClick = false;\r
5114                 this._preventClick = false;\r
5115 \r
5116                 for (var i in baseLayers) {\r
5117                         this._addLayer(baseLayers[i], i);\r
5118                 }\r
5119 \r
5120                 for (i in overlays) {\r
5121                         this._addLayer(overlays[i], i, true);\r
5122                 }\r
5123         },\r
5124 \r
5125         onAdd: function (map) {\r
5126                 this._initLayout();\r
5127                 this._update();\r
5128 \r
5129                 this._map = map;\r
5130                 map.on('zoomend', this._checkDisabledLayers, this);\r
5131 \r
5132                 for (var i = 0; i < this._layers.length; i++) {\r
5133                         this._layers[i].layer.on('add remove', this._onLayerChange, this);\r
5134                 }\r
5135 \r
5136                 return this._container;\r
5137         },\r
5138 \r
5139         addTo: function (map) {\r
5140                 Control.prototype.addTo.call(this, map);\r
5141                 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.\r
5142                 return this._expandIfNotCollapsed();\r
5143         },\r
5144 \r
5145         onRemove: function () {\r
5146                 this._map.off('zoomend', this._checkDisabledLayers, this);\r
5147 \r
5148                 for (var i = 0; i < this._layers.length; i++) {\r
5149                         this._layers[i].layer.off('add remove', this._onLayerChange, this);\r
5150                 }\r
5151         },\r
5152 \r
5153         // @method addBaseLayer(layer: Layer, name: String): this\r
5154         // Adds a base layer (radio button entry) with the given name to the control.\r
5155         addBaseLayer: function (layer, name) {\r
5156                 this._addLayer(layer, name);\r
5157                 return (this._map) ? this._update() : this;\r
5158         },\r
5159 \r
5160         // @method addOverlay(layer: Layer, name: String): this\r
5161         // Adds an overlay (checkbox entry) with the given name to the control.\r
5162         addOverlay: function (layer, name) {\r
5163                 this._addLayer(layer, name, true);\r
5164                 return (this._map) ? this._update() : this;\r
5165         },\r
5166 \r
5167         // @method removeLayer(layer: Layer): this\r
5168         // Remove the given layer from the control.\r
5169         removeLayer: function (layer) {\r
5170                 layer.off('add remove', this._onLayerChange, this);\r
5171 \r
5172                 var obj = this._getLayer(stamp(layer));\r
5173                 if (obj) {\r
5174                         this._layers.splice(this._layers.indexOf(obj), 1);\r
5175                 }\r
5176                 return (this._map) ? this._update() : this;\r
5177         },\r
5178 \r
5179         // @method expand(): this\r
5180         // Expand the control container if collapsed.\r
5181         expand: function () {\r
5182                 addClass(this._container, 'leaflet-control-layers-expanded');\r
5183                 this._section.style.height = null;\r
5184                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);\r
5185                 if (acceptableHeight < this._section.clientHeight) {\r
5186                         addClass(this._section, 'leaflet-control-layers-scrollbar');\r
5187                         this._section.style.height = acceptableHeight + 'px';\r
5188                 } else {\r
5189                         removeClass(this._section, 'leaflet-control-layers-scrollbar');\r
5190                 }\r
5191                 this._checkDisabledLayers();\r
5192                 return this;\r
5193         },\r
5194 \r
5195         // @method collapse(): this\r
5196         // Collapse the control container if expanded.\r
5197         collapse: function () {\r
5198                 removeClass(this._container, 'leaflet-control-layers-expanded');\r
5199                 return this;\r
5200         },\r
5201 \r
5202         _initLayout: function () {\r
5203                 var className = 'leaflet-control-layers',\r
5204                     container = this._container = create$1('div', className),\r
5205                     collapsed = this.options.collapsed;\r
5206 \r
5207                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released\r
5208                 container.setAttribute('aria-haspopup', true);\r
5209 \r
5210                 disableClickPropagation(container);\r
5211                 disableScrollPropagation(container);\r
5212 \r
5213                 var section = this._section = create$1('section', className + '-list');\r
5214 \r
5215                 if (collapsed) {\r
5216                         this._map.on('click', this.collapse, this);\r
5217 \r
5218                         on(container, {\r
5219                                 mouseenter: this._expandSafely,\r
5220                                 mouseleave: this.collapse\r
5221                         }, this);\r
5222                 }\r
5223 \r
5224                 var link = this._layersLink = create$1('a', className + '-toggle', container);\r
5225                 link.href = '#';\r
5226                 link.title = 'Layers';\r
5227                 link.setAttribute('role', 'button');\r
5228 \r
5229                 on(link, {\r
5230                         keydown: function (e) {\r
5231                                 if (e.keyCode === 13) {\r
5232                                         this._expandSafely();\r
5233                                 }\r
5234                         },\r
5235                         // Certain screen readers intercept the key event and instead send a click event\r
5236                         click: function (e) {\r
5237                                 preventDefault(e);\r
5238                                 this._expandSafely();\r
5239                         }\r
5240                 }, this);\r
5241 \r
5242                 if (!collapsed) {\r
5243                         this.expand();\r
5244                 }\r
5245 \r
5246                 this._baseLayersList = create$1('div', className + '-base', section);\r
5247                 this._separator = create$1('div', className + '-separator', section);\r
5248                 this._overlaysList = create$1('div', className + '-overlays', section);\r
5249 \r
5250                 container.appendChild(section);\r
5251         },\r
5252 \r
5253         _getLayer: function (id) {\r
5254                 for (var i = 0; i < this._layers.length; i++) {\r
5255 \r
5256                         if (this._layers[i] && stamp(this._layers[i].layer) === id) {\r
5257                                 return this._layers[i];\r
5258                         }\r
5259                 }\r
5260         },\r
5261 \r
5262         _addLayer: function (layer, name, overlay) {\r
5263                 if (this._map) {\r
5264                         layer.on('add remove', this._onLayerChange, this);\r
5265                 }\r
5266 \r
5267                 this._layers.push({\r
5268                         layer: layer,\r
5269                         name: name,\r
5270                         overlay: overlay\r
5271                 });\r
5272 \r
5273                 if (this.options.sortLayers) {\r
5274                         this._layers.sort(bind(function (a, b) {\r
5275                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);\r
5276                         }, this));\r
5277                 }\r
5278 \r
5279                 if (this.options.autoZIndex && layer.setZIndex) {\r
5280                         this._lastZIndex++;\r
5281                         layer.setZIndex(this._lastZIndex);\r
5282                 }\r
5283 \r
5284                 this._expandIfNotCollapsed();\r
5285         },\r
5286 \r
5287         _update: function () {\r
5288                 if (!this._container) { return this; }\r
5289 \r
5290                 empty(this._baseLayersList);\r
5291                 empty(this._overlaysList);\r
5292 \r
5293                 this._layerControlInputs = [];\r
5294                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;\r
5295 \r
5296                 for (i = 0; i < this._layers.length; i++) {\r
5297                         obj = this._layers[i];\r
5298                         this._addItem(obj);\r
5299                         overlaysPresent = overlaysPresent || obj.overlay;\r
5300                         baseLayersPresent = baseLayersPresent || !obj.overlay;\r
5301                         baseLayersCount += !obj.overlay ? 1 : 0;\r
5302                 }\r
5303 \r
5304                 // Hide base layers section if there's only one layer.\r
5305                 if (this.options.hideSingleBase) {\r
5306                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;\r
5307                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';\r
5308                 }\r
5309 \r
5310                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';\r
5311 \r
5312                 return this;\r
5313         },\r
5314 \r
5315         _onLayerChange: function (e) {\r
5316                 if (!this._handlingClick) {\r
5317                         this._update();\r
5318                 }\r
5319 \r
5320                 var obj = this._getLayer(stamp(e.target));\r
5321 \r
5322                 // @namespace Map\r
5323                 // @section Layer events\r
5324                 // @event baselayerchange: LayersControlEvent\r
5325                 // Fired when the base layer is changed through the [layers control](#control-layers).\r
5326                 // @event overlayadd: LayersControlEvent\r
5327                 // Fired when an overlay is selected through the [layers control](#control-layers).\r
5328                 // @event overlayremove: LayersControlEvent\r
5329                 // Fired when an overlay is deselected through the [layers control](#control-layers).\r
5330                 // @namespace Control.Layers\r
5331                 var type = obj.overlay ?\r
5332                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :\r
5333                         (e.type === 'add' ? 'baselayerchange' : null);\r
5334 \r
5335                 if (type) {\r
5336                         this._map.fire(type, obj);\r
5337                 }\r
5338         },\r
5339 \r
5340         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)\r
5341         _createRadioElement: function (name, checked) {\r
5342 \r
5343                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +\r
5344                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';\r
5345 \r
5346                 var radioFragment = document.createElement('div');\r
5347                 radioFragment.innerHTML = radioHtml;\r
5348 \r
5349                 return radioFragment.firstChild;\r
5350         },\r
5351 \r
5352         _addItem: function (obj) {\r
5353                 var label = document.createElement('label'),\r
5354                     checked = this._map.hasLayer(obj.layer),\r
5355                     input;\r
5356 \r
5357                 if (obj.overlay) {\r
5358                         input = document.createElement('input');\r
5359                         input.type = 'checkbox';\r
5360                         input.className = 'leaflet-control-layers-selector';\r
5361                         input.defaultChecked = checked;\r
5362                 } else {\r
5363                         input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);\r
5364                 }\r
5365 \r
5366                 this._layerControlInputs.push(input);\r
5367                 input.layerId = stamp(obj.layer);\r
5368 \r
5369                 on(input, 'click', this._onInputClick, this);\r
5370 \r
5371                 var name = document.createElement('span');\r
5372                 name.innerHTML = ' ' + obj.name;\r
5373 \r
5374                 // Helps from preventing layer control flicker when checkboxes are disabled\r
5375                 // https://github.com/Leaflet/Leaflet/issues/2771\r
5376                 var holder = document.createElement('span');\r
5377 \r
5378                 label.appendChild(holder);\r
5379                 holder.appendChild(input);\r
5380                 holder.appendChild(name);\r
5381 \r
5382                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;\r
5383                 container.appendChild(label);\r
5384 \r
5385                 this._checkDisabledLayers();\r
5386                 return label;\r
5387         },\r
5388 \r
5389         _onInputClick: function () {\r
5390                 // expanding the control on mobile with a click can cause adding a layer - we don't want this\r
5391                 if (this._preventClick) {\r
5392                         return;\r
5393                 }\r
5394 \r
5395                 var inputs = this._layerControlInputs,\r
5396                     input, layer;\r
5397                 var addedLayers = [],\r
5398                     removedLayers = [];\r
5399 \r
5400                 this._handlingClick = true;\r
5401 \r
5402                 for (var i = inputs.length - 1; i >= 0; i--) {\r
5403                         input = inputs[i];\r
5404                         layer = this._getLayer(input.layerId).layer;\r
5405 \r
5406                         if (input.checked) {\r
5407                                 addedLayers.push(layer);\r
5408                         } else if (!input.checked) {\r
5409                                 removedLayers.push(layer);\r
5410                         }\r
5411                 }\r
5412 \r
5413                 // Bugfix issue 2318: Should remove all old layers before readding new ones\r
5414                 for (i = 0; i < removedLayers.length; i++) {\r
5415                         if (this._map.hasLayer(removedLayers[i])) {\r
5416                                 this._map.removeLayer(removedLayers[i]);\r
5417                         }\r
5418                 }\r
5419                 for (i = 0; i < addedLayers.length; i++) {\r
5420                         if (!this._map.hasLayer(addedLayers[i])) {\r
5421                                 this._map.addLayer(addedLayers[i]);\r
5422                         }\r
5423                 }\r
5424 \r
5425                 this._handlingClick = false;\r
5426 \r
5427                 this._refocusOnMap();\r
5428         },\r
5429 \r
5430         _checkDisabledLayers: function () {\r
5431                 var inputs = this._layerControlInputs,\r
5432                     input,\r
5433                     layer,\r
5434                     zoom = this._map.getZoom();\r
5435 \r
5436                 for (var i = inputs.length - 1; i >= 0; i--) {\r
5437                         input = inputs[i];\r
5438                         layer = this._getLayer(input.layerId).layer;\r
5439                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||\r
5440                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);\r
5441 \r
5442                 }\r
5443         },\r
5444 \r
5445         _expandIfNotCollapsed: function () {\r
5446                 if (this._map && !this.options.collapsed) {\r
5447                         this.expand();\r
5448                 }\r
5449                 return this;\r
5450         },\r
5451 \r
5452         _expandSafely: function () {\r
5453                 var section = this._section;\r
5454                 this._preventClick = true;\r
5455                 on(section, 'click', preventDefault);\r
5456                 this.expand();\r
5457                 var that = this;\r
5458                 setTimeout(function () {\r
5459                         off(section, 'click', preventDefault);\r
5460                         that._preventClick = false;\r
5461                 });\r
5462         }\r
5463 \r
5464   });\r
5465 \r
5466 \r
5467   // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)\r
5468   // Creates a layers 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.\r
5469   var layers = function (baseLayers, overlays, options) {\r
5470         return new Layers(baseLayers, overlays, options);\r
5471   };
5472
5473   /*\r
5474    * @class Control.Zoom\r
5475    * @aka L.Control.Zoom\r
5476    * @inherits Control\r
5477    *\r
5478    * 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`.\r
5479    */\r
5480 \r
5481   var Zoom = Control.extend({\r
5482         // @section\r
5483         // @aka Control.Zoom options\r
5484         options: {\r
5485                 position: 'topleft',\r
5486 \r
5487                 // @option zoomInText: String = '<span aria-hidden="true">+</span>'\r
5488                 // The text set on the 'zoom in' button.\r
5489                 zoomInText: '<span aria-hidden="true">+</span>',\r
5490 \r
5491                 // @option zoomInTitle: String = 'Zoom in'\r
5492                 // The title set on the 'zoom in' button.\r
5493                 zoomInTitle: 'Zoom in',\r
5494 \r
5495                 // @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'\r
5496                 // The text set on the 'zoom out' button.\r
5497                 zoomOutText: '<span aria-hidden="true">&#x2212;</span>',\r
5498 \r
5499                 // @option zoomOutTitle: String = 'Zoom out'\r
5500                 // The title set on the 'zoom out' button.\r
5501                 zoomOutTitle: 'Zoom out'\r
5502         },\r
5503 \r
5504         onAdd: function (map) {\r
5505                 var zoomName = 'leaflet-control-zoom',\r
5506                     container = create$1('div', zoomName + ' leaflet-bar'),\r
5507                     options = this.options;\r
5508 \r
5509                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,\r
5510                         zoomName + '-in',  container, this._zoomIn);\r
5511                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,\r
5512                         zoomName + '-out', container, this._zoomOut);\r
5513 \r
5514                 this._updateDisabled();\r
5515                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);\r
5516 \r
5517                 return container;\r
5518         },\r
5519 \r
5520         onRemove: function (map) {\r
5521                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);\r
5522         },\r
5523 \r
5524         disable: function () {\r
5525                 this._disabled = true;\r
5526                 this._updateDisabled();\r
5527                 return this;\r
5528         },\r
5529 \r
5530         enable: function () {\r
5531                 this._disabled = false;\r
5532                 this._updateDisabled();\r
5533                 return this;\r
5534         },\r
5535 \r
5536         _zoomIn: function (e) {\r
5537                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {\r
5538                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));\r
5539                 }\r
5540         },\r
5541 \r
5542         _zoomOut: function (e) {\r
5543                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {\r
5544                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));\r
5545                 }\r
5546         },\r
5547 \r
5548         _createButton: function (html, title, className, container, fn) {\r
5549                 var link = create$1('a', className, container);\r
5550                 link.innerHTML = html;\r
5551                 link.href = '#';\r
5552                 link.title = title;\r
5553 \r
5554                 /*\r
5555                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"\r
5556                  */\r
5557                 link.setAttribute('role', 'button');\r
5558                 link.setAttribute('aria-label', title);\r
5559 \r
5560                 disableClickPropagation(link);\r
5561                 on(link, 'click', stop);\r
5562                 on(link, 'click', fn, this);\r
5563                 on(link, 'click', this._refocusOnMap, this);\r
5564 \r
5565                 return link;\r
5566         },\r
5567 \r
5568         _updateDisabled: function () {\r
5569                 var map = this._map,\r
5570                     className = 'leaflet-disabled';\r
5571 \r
5572                 removeClass(this._zoomInButton, className);\r
5573                 removeClass(this._zoomOutButton, className);\r
5574                 this._zoomInButton.setAttribute('aria-disabled', 'false');\r
5575                 this._zoomOutButton.setAttribute('aria-disabled', 'false');\r
5576 \r
5577                 if (this._disabled || map._zoom === map.getMinZoom()) {\r
5578                         addClass(this._zoomOutButton, className);\r
5579                         this._zoomOutButton.setAttribute('aria-disabled', 'true');\r
5580                 }\r
5581                 if (this._disabled || map._zoom === map.getMaxZoom()) {\r
5582                         addClass(this._zoomInButton, className);\r
5583                         this._zoomInButton.setAttribute('aria-disabled', 'true');\r
5584                 }\r
5585         }\r
5586   });\r
5587 \r
5588   // @namespace Map\r
5589   // @section Control options\r
5590   // @option zoomControl: Boolean = true\r
5591   // Whether a [zoom control](#control-zoom) is added to the map by default.\r
5592   Map.mergeOptions({\r
5593         zoomControl: true\r
5594   });\r
5595 \r
5596   Map.addInitHook(function () {\r
5597         if (this.options.zoomControl) {\r
5598                 // @section Controls\r
5599                 // @property zoomControl: Control.Zoom\r
5600                 // The default zoom control (only available if the\r
5601                 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).\r
5602                 this.zoomControl = new Zoom();\r
5603                 this.addControl(this.zoomControl);\r
5604         }\r
5605   });\r
5606 \r
5607   // @namespace Control.Zoom\r
5608   // @factory L.control.zoom(options: Control.Zoom options)\r
5609   // Creates a zoom control\r
5610   var zoom = function (options) {\r
5611         return new Zoom(options);\r
5612   };
5613
5614   /*
5615    * @class Control.Scale
5616    * @aka L.Control.Scale
5617    * @inherits Control
5618    *
5619    * 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`.
5620    *
5621    * @example
5622    *
5623    * ```js
5624    * L.control.scale().addTo(map);
5625    * ```
5626    */
5627
5628   var Scale = Control.extend({
5629         // @section
5630         // @aka Control.Scale options
5631         options: {
5632                 position: 'bottomleft',
5633
5634                 // @option maxWidth: Number = 100
5635                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5636                 maxWidth: 100,
5637
5638                 // @option metric: Boolean = True
5639                 // Whether to show the metric scale line (m/km).
5640                 metric: true,
5641
5642                 // @option imperial: Boolean = True
5643                 // Whether to show the imperial scale line (mi/ft).
5644                 imperial: true
5645
5646                 // @option updateWhenIdle: Boolean = false
5647                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5648         },
5649
5650         onAdd: function (map) {
5651                 var className = 'leaflet-control-scale',
5652                     container = create$1('div', className),
5653                     options = this.options;
5654
5655                 this._addScales(options, className + '-line', container);
5656
5657                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5658                 map.whenReady(this._update, this);
5659
5660                 return container;
5661         },
5662
5663         onRemove: function (map) {
5664                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5665         },
5666
5667         _addScales: function (options, className, container) {
5668                 if (options.metric) {
5669                         this._mScale = create$1('div', className, container);
5670                 }
5671                 if (options.imperial) {
5672                         this._iScale = create$1('div', className, container);
5673                 }
5674         },
5675
5676         _update: function () {
5677                 var map = this._map,
5678                     y = map.getSize().y / 2;
5679
5680                 var maxMeters = map.distance(
5681                         map.containerPointToLatLng([0, y]),
5682                         map.containerPointToLatLng([this.options.maxWidth, y]));
5683
5684                 this._updateScales(maxMeters);
5685         },
5686
5687         _updateScales: function (maxMeters) {
5688                 if (this.options.metric && maxMeters) {
5689                         this._updateMetric(maxMeters);
5690                 }
5691                 if (this.options.imperial && maxMeters) {
5692                         this._updateImperial(maxMeters);
5693                 }
5694         },
5695
5696         _updateMetric: function (maxMeters) {
5697                 var meters = this._getRoundNum(maxMeters),
5698                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5699
5700                 this._updateScale(this._mScale, label, meters / maxMeters);
5701         },
5702
5703         _updateImperial: function (maxMeters) {
5704                 var maxFeet = maxMeters * 3.2808399,
5705                     maxMiles, miles, feet;
5706
5707                 if (maxFeet > 5280) {
5708                         maxMiles = maxFeet / 5280;
5709                         miles = this._getRoundNum(maxMiles);
5710                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5711
5712                 } else {
5713                         feet = this._getRoundNum(maxFeet);
5714                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5715                 }
5716         },
5717
5718         _updateScale: function (scale, text, ratio) {
5719                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5720                 scale.innerHTML = text;
5721         },
5722
5723         _getRoundNum: function (num) {
5724                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5725                     d = num / pow10;
5726
5727                 d = d >= 10 ? 10 :
5728                     d >= 5 ? 5 :
5729                     d >= 3 ? 3 :
5730                     d >= 2 ? 2 : 1;
5731
5732                 return pow10 * d;
5733         }
5734   });
5735
5736
5737   // @factory L.control.scale(options?: Control.Scale options)
5738   // Creates an scale control with the given options.
5739   var scale = function (options) {
5740         return new Scale(options);
5741   };
5742
5743   var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>';\r
5744 \r
5745 \r
5746   /*\r
5747    * @class Control.Attribution\r
5748    * @aka L.Control.Attribution\r
5749    * @inherits Control\r
5750    *\r
5751    * 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.\r
5752    */\r
5753 \r
5754   var Attribution = Control.extend({\r
5755         // @section\r
5756         // @aka Control.Attribution options\r
5757         options: {\r
5758                 position: 'bottomright',\r
5759 \r
5760                 // @option prefix: String|false = 'Leaflet'\r
5761                 // The HTML text shown before the attributions. Pass `false` to disable.\r
5762                 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'\r
5763         },\r
5764 \r
5765         initialize: function (options) {\r
5766                 setOptions(this, options);\r
5767 \r
5768                 this._attributions = {};\r
5769         },\r
5770 \r
5771         onAdd: function (map) {\r
5772                 map.attributionControl = this;\r
5773                 this._container = create$1('div', 'leaflet-control-attribution');\r
5774                 disableClickPropagation(this._container);\r
5775 \r
5776                 // TODO ugly, refactor\r
5777                 for (var i in map._layers) {\r
5778                         if (map._layers[i].getAttribution) {\r
5779                                 this.addAttribution(map._layers[i].getAttribution());\r
5780                         }\r
5781                 }\r
5782 \r
5783                 this._update();\r
5784 \r
5785                 map.on('layeradd', this._addAttribution, this);\r
5786 \r
5787                 return this._container;\r
5788         },\r
5789 \r
5790         onRemove: function (map) {\r
5791                 map.off('layeradd', this._addAttribution, this);\r
5792         },\r
5793 \r
5794         _addAttribution: function (ev) {\r
5795                 if (ev.layer.getAttribution) {\r
5796                         this.addAttribution(ev.layer.getAttribution());\r
5797                         ev.layer.once('remove', function () {\r
5798                                 this.removeAttribution(ev.layer.getAttribution());\r
5799                         }, this);\r
5800                 }\r
5801         },\r
5802 \r
5803         // @method setPrefix(prefix: String|false): this\r
5804         // The HTML text shown before the attributions. Pass `false` to disable.\r
5805         setPrefix: function (prefix) {\r
5806                 this.options.prefix = prefix;\r
5807                 this._update();\r
5808                 return this;\r
5809         },\r
5810 \r
5811         // @method addAttribution(text: String): this\r
5812         // Adds an attribution text (e.g. `'&copy; OpenStreetMap contributors'`).\r
5813         addAttribution: function (text) {\r
5814                 if (!text) { return this; }\r
5815 \r
5816                 if (!this._attributions[text]) {\r
5817                         this._attributions[text] = 0;\r
5818                 }\r
5819                 this._attributions[text]++;\r
5820 \r
5821                 this._update();\r
5822 \r
5823                 return this;\r
5824         },\r
5825 \r
5826         // @method removeAttribution(text: String): this\r
5827         // Removes an attribution text.\r
5828         removeAttribution: function (text) {\r
5829                 if (!text) { return this; }\r
5830 \r
5831                 if (this._attributions[text]) {\r
5832                         this._attributions[text]--;\r
5833                         this._update();\r
5834                 }\r
5835 \r
5836                 return this;\r
5837         },\r
5838 \r
5839         _update: function () {\r
5840                 if (!this._map) { return; }\r
5841 \r
5842                 var attribs = [];\r
5843 \r
5844                 for (var i in this._attributions) {\r
5845                         if (this._attributions[i]) {\r
5846                                 attribs.push(i);\r
5847                         }\r
5848                 }\r
5849 \r
5850                 var prefixAndAttribs = [];\r
5851 \r
5852                 if (this.options.prefix) {\r
5853                         prefixAndAttribs.push(this.options.prefix);\r
5854                 }\r
5855                 if (attribs.length) {\r
5856                         prefixAndAttribs.push(attribs.join(', '));\r
5857                 }\r
5858 \r
5859                 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');\r
5860         }\r
5861   });\r
5862 \r
5863   // @namespace Map\r
5864   // @section Control options\r
5865   // @option attributionControl: Boolean = true\r
5866   // Whether a [attribution control](#control-attribution) is added to the map by default.\r
5867   Map.mergeOptions({\r
5868         attributionControl: true\r
5869   });\r
5870 \r
5871   Map.addInitHook(function () {\r
5872         if (this.options.attributionControl) {\r
5873                 new Attribution().addTo(this);\r
5874         }\r
5875   });\r
5876 \r
5877   // @namespace Control.Attribution\r
5878   // @factory L.control.attribution(options: Control.Attribution options)\r
5879   // Creates an attribution control.\r
5880   var attribution = function (options) {\r
5881         return new Attribution(options);\r
5882   };
5883
5884   Control.Layers = Layers;
5885   Control.Zoom = Zoom;
5886   Control.Scale = Scale;
5887   Control.Attribution = Attribution;
5888
5889   control.layers = layers;
5890   control.zoom = zoom;
5891   control.scale = scale;
5892   control.attribution = attribution;
5893
5894   /*
5895         L.Handler is a base class for handler classes that are used internally to inject
5896         interaction features like dragging to classes like Map and Marker.
5897   */
5898
5899   // @class Handler
5900   // @aka L.Handler
5901   // Abstract class for map interaction handlers
5902
5903   var Handler = Class.extend({
5904         initialize: function (map) {
5905                 this._map = map;
5906         },
5907
5908         // @method enable(): this
5909         // Enables the handler
5910         enable: function () {
5911                 if (this._enabled) { return this; }
5912
5913                 this._enabled = true;
5914                 this.addHooks();
5915                 return this;
5916         },
5917
5918         // @method disable(): this
5919         // Disables the handler
5920         disable: function () {
5921                 if (!this._enabled) { return this; }
5922
5923                 this._enabled = false;
5924                 this.removeHooks();
5925                 return this;
5926         },
5927
5928         // @method enabled(): Boolean
5929         // Returns `true` if the handler is enabled
5930         enabled: function () {
5931                 return !!this._enabled;
5932         }
5933
5934         // @section Extension methods
5935         // Classes inheriting from `Handler` must implement the two following methods:
5936         // @method addHooks()
5937         // Called when the handler is enabled, should add event hooks.
5938         // @method removeHooks()
5939         // Called when the handler is disabled, should remove the event hooks added previously.
5940   });
5941
5942   // @section There is static function which can be called without instantiating L.Handler:
5943   // @function addTo(map: Map, name: String): this
5944   // Adds a new Handler to the given map with the given name.
5945   Handler.addTo = function (map, name) {
5946         map.addHandler(name, this);
5947         return this;
5948   };
5949
5950   var Mixin = {Events: Events};
5951
5952   /*\r
5953    * @class Draggable\r
5954    * @aka L.Draggable\r
5955    * @inherits Evented\r
5956    *\r
5957    * A class for making DOM elements draggable (including touch support).\r
5958    * Used internally for map and marker dragging. Only works for elements\r
5959    * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).\r
5960    *\r
5961    * @example\r
5962    * ```js\r
5963    * var draggable = new L.Draggable(elementToDrag);\r
5964    * draggable.enable();\r
5965    * ```\r
5966    */\r
5967 \r
5968   var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';\r
5969 \r
5970   var Draggable = Evented.extend({\r
5971 \r
5972         options: {\r
5973                 // @section\r
5974                 // @aka Draggable options\r
5975                 // @option clickTolerance: Number = 3\r
5976                 // The max number of pixels a user can shift the mouse pointer during a click\r
5977                 // for it to be considered a valid click (as opposed to a mouse drag).\r
5978                 clickTolerance: 3\r
5979         },\r
5980 \r
5981         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)\r
5982         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).\r
5983         initialize: function (element, dragStartTarget, preventOutline, options) {\r
5984                 setOptions(this, options);\r
5985 \r
5986                 this._element = element;\r
5987                 this._dragStartTarget = dragStartTarget || element;\r
5988                 this._preventOutline = preventOutline;\r
5989         },\r
5990 \r
5991         // @method enable()\r
5992         // Enables the dragging ability\r
5993         enable: function () {\r
5994                 if (this._enabled) { return; }\r
5995 \r
5996                 on(this._dragStartTarget, START, this._onDown, this);\r
5997 \r
5998                 this._enabled = true;\r
5999         },\r
6000 \r
6001         // @method disable()\r
6002         // Disables the dragging ability\r
6003         disable: function () {\r
6004                 if (!this._enabled) { return; }\r
6005 \r
6006                 // If we're currently dragging this draggable,\r
6007                 // disabling it counts as first ending the drag.\r
6008                 if (Draggable._dragging === this) {\r
6009                         this.finishDrag(true);\r
6010                 }\r
6011 \r
6012                 off(this._dragStartTarget, START, this._onDown, this);\r
6013 \r
6014                 this._enabled = false;\r
6015                 this._moved = false;\r
6016         },\r
6017 \r
6018         _onDown: function (e) {\r
6019                 // Ignore the event if disabled; this happens in IE11\r
6020                 // under some circumstances, see #3666.\r
6021                 if (!this._enabled) { return; }\r
6022 \r
6023                 this._moved = false;\r
6024 \r
6025                 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }\r
6026 \r
6027                 if (e.touches && e.touches.length !== 1) {\r
6028                         // Finish dragging to avoid conflict with touchZoom\r
6029                         if (Draggable._dragging === this) {\r
6030                                 this.finishDrag();\r
6031                         }\r
6032                         return;\r
6033                 }\r
6034 \r
6035                 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }\r
6036                 Draggable._dragging = this;  // Prevent dragging multiple objects at once.\r
6037 \r
6038                 if (this._preventOutline) {\r
6039                         preventOutline(this._element);\r
6040                 }\r
6041 \r
6042                 disableImageDrag();\r
6043                 disableTextSelection();\r
6044 \r
6045                 if (this._moving) { return; }\r
6046 \r
6047                 // @event down: Event\r
6048                 // Fired when a drag is about to start.\r
6049                 this.fire('down');\r
6050 \r
6051                 var first = e.touches ? e.touches[0] : e,\r
6052                     sizedParent = getSizedParentNode(this._element);\r
6053 \r
6054                 this._startPoint = new Point(first.clientX, first.clientY);\r
6055                 this._startPos = getPosition(this._element);\r
6056 \r
6057                 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).\r
6058                 this._parentScale = getScale(sizedParent);\r
6059 \r
6060                 var mouseevent = e.type === 'mousedown';\r
6061                 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);\r
6062                 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);\r
6063         },\r
6064 \r
6065         _onMove: function (e) {\r
6066                 // Ignore the event if disabled; this happens in IE11\r
6067                 // under some circumstances, see #3666.\r
6068                 if (!this._enabled) { return; }\r
6069 \r
6070                 if (e.touches && e.touches.length > 1) {\r
6071                         this._moved = true;\r
6072                         return;\r
6073                 }\r
6074 \r
6075                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),\r
6076                     offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);\r
6077 \r
6078                 if (!offset.x && !offset.y) { return; }\r
6079                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }\r
6080 \r
6081                 // We assume that the parent container's position, border and scale do not change for the duration of the drag.\r
6082                 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)\r
6083                 // and we can use the cached value for the scale.\r
6084                 offset.x /= this._parentScale.x;\r
6085                 offset.y /= this._parentScale.y;\r
6086 \r
6087                 preventDefault(e);\r
6088 \r
6089                 if (!this._moved) {\r
6090                         // @event dragstart: Event\r
6091                         // Fired when a drag starts\r
6092                         this.fire('dragstart');\r
6093 \r
6094                         this._moved = true;\r
6095 \r
6096                         addClass(document.body, 'leaflet-dragging');\r
6097 \r
6098                         this._lastTarget = e.target || e.srcElement;\r
6099                         // IE and Edge do not give the <use> element, so fetch it\r
6100                         // if necessary\r
6101                         if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {\r
6102                                 this._lastTarget = this._lastTarget.correspondingUseElement;\r
6103                         }\r
6104                         addClass(this._lastTarget, 'leaflet-drag-target');\r
6105                 }\r
6106 \r
6107                 this._newPos = this._startPos.add(offset);\r
6108                 this._moving = true;\r
6109 \r
6110                 this._lastEvent = e;\r
6111                 this._updatePosition();\r
6112         },\r
6113 \r
6114         _updatePosition: function () {\r
6115                 var e = {originalEvent: this._lastEvent};\r
6116 \r
6117                 // @event predrag: Event\r
6118                 // Fired continuously during dragging *before* each corresponding\r
6119                 // update of the element's position.\r
6120                 this.fire('predrag', e);\r
6121                 setPosition(this._element, this._newPos);\r
6122 \r
6123                 // @event drag: Event\r
6124                 // Fired continuously during dragging.\r
6125                 this.fire('drag', e);\r
6126         },\r
6127 \r
6128         _onUp: function () {\r
6129                 // Ignore the event if disabled; this happens in IE11\r
6130                 // under some circumstances, see #3666.\r
6131                 if (!this._enabled) { return; }\r
6132                 this.finishDrag();\r
6133         },\r
6134 \r
6135         finishDrag: function (noInertia) {\r
6136                 removeClass(document.body, 'leaflet-dragging');\r
6137 \r
6138                 if (this._lastTarget) {\r
6139                         removeClass(this._lastTarget, 'leaflet-drag-target');\r
6140                         this._lastTarget = null;\r
6141                 }\r
6142 \r
6143                 off(document, 'mousemove touchmove', this._onMove, this);\r
6144                 off(document, 'mouseup touchend touchcancel', this._onUp, this);\r
6145 \r
6146                 enableImageDrag();\r
6147                 enableTextSelection();\r
6148 \r
6149                 var fireDragend = this._moved && this._moving;\r
6150 \r
6151                 this._moving = false;\r
6152                 Draggable._dragging = false;\r
6153 \r
6154                 if (fireDragend) {\r
6155                         // @event dragend: DragEndEvent\r
6156                         // Fired when the drag ends.\r
6157                         this.fire('dragend', {\r
6158                                 noInertia: noInertia,\r
6159                                 distance: this._newPos.distanceTo(this._startPos)\r
6160                         });\r
6161                 }\r
6162         }\r
6163 \r
6164   });
6165
6166   /*\r
6167    * @namespace PolyUtil\r
6168    * Various utility functions for polygon geometries.\r
6169    */\r
6170 \r
6171   /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]\r
6172    * 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)).\r
6173    * Used by Leaflet to only show polygon points that are on the screen or near, increasing\r
6174    * performance. Note that polygon points needs different algorithm for clipping\r
6175    * than polyline, so there's a separate method for it.\r
6176    */\r
6177   function clipPolygon(points, bounds, round) {\r
6178         var clippedPoints,\r
6179             edges = [1, 4, 2, 8],\r
6180             i, j, k,\r
6181             a, b,\r
6182             len, edge, p;\r
6183 \r
6184         for (i = 0, len = points.length; i < len; i++) {\r
6185                 points[i]._code = _getBitCode(points[i], bounds);\r
6186         }\r
6187 \r
6188         // for each edge (left, bottom, right, top)\r
6189         for (k = 0; k < 4; k++) {\r
6190                 edge = edges[k];\r
6191                 clippedPoints = [];\r
6192 \r
6193                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {\r
6194                         a = points[i];\r
6195                         b = points[j];\r
6196 \r
6197                         // if a is inside the clip window\r
6198                         if (!(a._code & edge)) {\r
6199                                 // if b is outside the clip window (a->b goes out of screen)\r
6200                                 if (b._code & edge) {\r
6201                                         p = _getEdgeIntersection(b, a, edge, bounds, round);\r
6202                                         p._code = _getBitCode(p, bounds);\r
6203                                         clippedPoints.push(p);\r
6204                                 }\r
6205                                 clippedPoints.push(a);\r
6206 \r
6207                         // else if b is inside the clip window (a->b enters the screen)\r
6208                         } else if (!(b._code & edge)) {\r
6209                                 p = _getEdgeIntersection(b, a, edge, bounds, round);\r
6210                                 p._code = _getBitCode(p, bounds);\r
6211                                 clippedPoints.push(p);\r
6212                         }\r
6213                 }\r
6214                 points = clippedPoints;\r
6215         }\r
6216 \r
6217         return points;\r
6218   }\r
6219 \r
6220   /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng\r
6221    * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.\r
6222    */\r
6223   function polygonCenter(latlngs, crs) {\r
6224         var i, j, p1, p2, f, area, x, y, center;\r
6225 \r
6226         if (!latlngs || latlngs.length === 0) {\r
6227                 throw new Error('latlngs not passed');\r
6228         }\r
6229 \r
6230         if (!isFlat(latlngs)) {\r
6231                 console.warn('latlngs are not flat! Only the first ring will be used');\r
6232                 latlngs = latlngs[0];\r
6233         }\r
6234 \r
6235         var centroidLatLng = toLatLng([0, 0]);\r
6236 \r
6237         var bounds = toLatLngBounds(latlngs);\r
6238         var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());\r
6239         // tests showed that below 1700 rounding errors are happening\r
6240         if (areaBounds < 1700) {\r
6241                 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors\r
6242                 centroidLatLng = centroid(latlngs);\r
6243         }\r
6244 \r
6245         var len = latlngs.length;\r
6246         var points = [];\r
6247         for (i = 0; i < len; i++) {\r
6248                 var latlng = toLatLng(latlngs[i]);\r
6249                 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));\r
6250         }\r
6251 \r
6252         area = x = y = 0;\r
6253 \r
6254         // polygon centroid algorithm;\r
6255         for (i = 0, j = len - 1; i < len; j = i++) {\r
6256                 p1 = points[i];\r
6257                 p2 = points[j];\r
6258 \r
6259                 f = p1.y * p2.x - p2.y * p1.x;\r
6260                 x += (p1.x + p2.x) * f;\r
6261                 y += (p1.y + p2.y) * f;\r
6262                 area += f * 3;\r
6263         }\r
6264 \r
6265         if (area === 0) {\r
6266                 // Polygon is so small that all points are on same pixel.\r
6267                 center = points[0];\r
6268         } else {\r
6269                 center = [x / area, y / area];\r
6270         }\r
6271 \r
6272         var latlngCenter = crs.unproject(toPoint(center));\r
6273         return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);\r
6274   }\r
6275 \r
6276   /* @function centroid(latlngs: LatLng[]): LatLng\r
6277    * Returns the 'center of mass' of the passed LatLngs.\r
6278    */\r
6279   function centroid(coords) {\r
6280         var latSum = 0;\r
6281         var lngSum = 0;\r
6282         var len = 0;\r
6283         for (var i = 0; i < coords.length; i++) {\r
6284                 var latlng = toLatLng(coords[i]);\r
6285                 latSum += latlng.lat;\r
6286                 lngSum += latlng.lng;\r
6287                 len++;\r
6288         }\r
6289         return toLatLng([latSum / len, lngSum / len]);\r
6290   }
6291
6292   var PolyUtil = {
6293     __proto__: null,
6294     clipPolygon: clipPolygon,
6295     polygonCenter: polygonCenter,
6296     centroid: centroid
6297   };
6298
6299   /*\r
6300    * @namespace LineUtil\r
6301    *\r
6302    * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.\r
6303    */\r
6304 \r
6305   // Simplify polyline with vertex reduction and Douglas-Peucker simplification.\r
6306   // Improves rendering performance dramatically by lessening the number of points to draw.\r
6307 \r
6308   // @function simplify(points: Point[], tolerance: Number): Point[]\r
6309   // Dramatically reduces the number of points in a polyline while retaining\r
6310   // its shape and returns a new array of simplified points, using the\r
6311   // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).\r
6312   // Used for a huge performance boost when processing/displaying Leaflet polylines for\r
6313   // each zoom level and also reducing visual noise. tolerance affects the amount of\r
6314   // simplification (lesser value means higher quality but slower and with more points).\r
6315   // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).\r
6316   function simplify(points, tolerance) {\r
6317         if (!tolerance || !points.length) {\r
6318                 return points.slice();\r
6319         }\r
6320 \r
6321         var sqTolerance = tolerance * tolerance;\r
6322 \r
6323             // stage 1: vertex reduction\r
6324             points = _reducePoints(points, sqTolerance);\r
6325 \r
6326             // stage 2: Douglas-Peucker simplification\r
6327             points = _simplifyDP(points, sqTolerance);\r
6328 \r
6329         return points;\r
6330   }\r
6331 \r
6332   // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number\r
6333   // Returns the distance between point `p` and segment `p1` to `p2`.\r
6334   function pointToSegmentDistance(p, p1, p2) {\r
6335         return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));\r
6336   }\r
6337 \r
6338   // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number\r
6339   // Returns the closest point from a point `p` on a segment `p1` to `p2`.\r
6340   function closestPointOnSegment(p, p1, p2) {\r
6341         return _sqClosestPointOnSegment(p, p1, p2);\r
6342   }\r
6343 \r
6344   // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm\r
6345   function _simplifyDP(points, sqTolerance) {\r
6346 \r
6347         var len = points.length,\r
6348             ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,\r
6349             markers = new ArrayConstructor(len);\r
6350 \r
6351             markers[0] = markers[len - 1] = 1;\r
6352 \r
6353         _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);\r
6354 \r
6355         var i,\r
6356             newPoints = [];\r
6357 \r
6358         for (i = 0; i < len; i++) {\r
6359                 if (markers[i]) {\r
6360                         newPoints.push(points[i]);\r
6361                 }\r
6362         }\r
6363 \r
6364         return newPoints;\r
6365   }\r
6366 \r
6367   function _simplifyDPStep(points, markers, sqTolerance, first, last) {\r
6368 \r
6369         var maxSqDist = 0,\r
6370         index, i, sqDist;\r
6371 \r
6372         for (i = first + 1; i <= last - 1; i++) {\r
6373                 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);\r
6374 \r
6375                 if (sqDist > maxSqDist) {\r
6376                         index = i;\r
6377                         maxSqDist = sqDist;\r
6378                 }\r
6379         }\r
6380 \r
6381         if (maxSqDist > sqTolerance) {\r
6382                 markers[index] = 1;\r
6383 \r
6384                 _simplifyDPStep(points, markers, sqTolerance, first, index);\r
6385                 _simplifyDPStep(points, markers, sqTolerance, index, last);\r
6386         }\r
6387   }\r
6388 \r
6389   // reduce points that are too close to each other to a single point\r
6390   function _reducePoints(points, sqTolerance) {\r
6391         var reducedPoints = [points[0]];\r
6392 \r
6393         for (var i = 1, prev = 0, len = points.length; i < len; i++) {\r
6394                 if (_sqDist(points[i], points[prev]) > sqTolerance) {\r
6395                         reducedPoints.push(points[i]);\r
6396                         prev = i;\r
6397                 }\r
6398         }\r
6399         if (prev < len - 1) {\r
6400                 reducedPoints.push(points[len - 1]);\r
6401         }\r
6402         return reducedPoints;\r
6403   }\r
6404 \r
6405   var _lastCode;\r
6406 \r
6407   // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean\r
6408   // Clips the segment a to b by rectangular bounds with the\r
6409   // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)\r
6410   // (modifying the segment points directly!). Used by Leaflet to only show polyline\r
6411   // points that are on the screen or near, increasing performance.\r
6412   function clipSegment(a, b, bounds, useLastCode, round) {\r
6413         var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),\r
6414             codeB = _getBitCode(b, bounds),\r
6415 \r
6416             codeOut, p, newCode;\r
6417 \r
6418             // save 2nd code to avoid calculating it on the next segment\r
6419             _lastCode = codeB;\r
6420 \r
6421         while (true) {\r
6422                 // if a,b is inside the clip window (trivial accept)\r
6423                 if (!(codeA | codeB)) {\r
6424                         return [a, b];\r
6425                 }\r
6426 \r
6427                 // if a,b is outside the clip window (trivial reject)\r
6428                 if (codeA & codeB) {\r
6429                         return false;\r
6430                 }\r
6431 \r
6432                 // other cases\r
6433                 codeOut = codeA || codeB;\r
6434                 p = _getEdgeIntersection(a, b, codeOut, bounds, round);\r
6435                 newCode = _getBitCode(p, bounds);\r
6436 \r
6437                 if (codeOut === codeA) {\r
6438                         a = p;\r
6439                         codeA = newCode;\r
6440                 } else {\r
6441                         b = p;\r
6442                         codeB = newCode;\r
6443                 }\r
6444         }\r
6445   }\r
6446 \r
6447   function _getEdgeIntersection(a, b, code, bounds, round) {\r
6448         var dx = b.x - a.x,\r
6449             dy = b.y - a.y,\r
6450             min = bounds.min,\r
6451             max = bounds.max,\r
6452             x, y;\r
6453 \r
6454         if (code & 8) { // top\r
6455                 x = a.x + dx * (max.y - a.y) / dy;\r
6456                 y = max.y;\r
6457 \r
6458         } else if (code & 4) { // bottom\r
6459                 x = a.x + dx * (min.y - a.y) / dy;\r
6460                 y = min.y;\r
6461 \r
6462         } else if (code & 2) { // right\r
6463                 x = max.x;\r
6464                 y = a.y + dy * (max.x - a.x) / dx;\r
6465 \r
6466         } else if (code & 1) { // left\r
6467                 x = min.x;\r
6468                 y = a.y + dy * (min.x - a.x) / dx;\r
6469         }\r
6470 \r
6471         return new Point(x, y, round);\r
6472   }\r
6473 \r
6474   function _getBitCode(p, bounds) {\r
6475         var code = 0;\r
6476 \r
6477         if (p.x < bounds.min.x) { // left\r
6478                 code |= 1;\r
6479         } else if (p.x > bounds.max.x) { // right\r
6480                 code |= 2;\r
6481         }\r
6482 \r
6483         if (p.y < bounds.min.y) { // bottom\r
6484                 code |= 4;\r
6485         } else if (p.y > bounds.max.y) { // top\r
6486                 code |= 8;\r
6487         }\r
6488 \r
6489         return code;\r
6490   }\r
6491 \r
6492   // square distance (to avoid unnecessary Math.sqrt calls)\r
6493   function _sqDist(p1, p2) {\r
6494         var dx = p2.x - p1.x,\r
6495             dy = p2.y - p1.y;\r
6496         return dx * dx + dy * dy;\r
6497   }\r
6498 \r
6499   // return closest point on segment or distance to that point\r
6500   function _sqClosestPointOnSegment(p, p1, p2, sqDist) {\r
6501         var x = p1.x,\r
6502             y = p1.y,\r
6503             dx = p2.x - x,\r
6504             dy = p2.y - y,\r
6505             dot = dx * dx + dy * dy,\r
6506             t;\r
6507 \r
6508         if (dot > 0) {\r
6509                 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;\r
6510 \r
6511                 if (t > 1) {\r
6512                         x = p2.x;\r
6513                         y = p2.y;\r
6514                 } else if (t > 0) {\r
6515                         x += dx * t;\r
6516                         y += dy * t;\r
6517                 }\r
6518         }\r
6519 \r
6520         dx = p.x - x;\r
6521         dy = p.y - y;\r
6522 \r
6523         return sqDist ? dx * dx + dy * dy : new Point(x, y);\r
6524   }\r
6525 \r
6526 \r
6527   // @function isFlat(latlngs: LatLng[]): Boolean\r
6528   // Returns true if `latlngs` is a flat array, false is nested.\r
6529   function isFlat(latlngs) {\r
6530         return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');\r
6531   }\r
6532 \r
6533   function _flat(latlngs) {\r
6534         console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');\r
6535         return isFlat(latlngs);\r
6536   }\r
6537 \r
6538   /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng\r
6539    * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.\r
6540    */\r
6541   function polylineCenter(latlngs, crs) {\r
6542         var i, halfDist, segDist, dist, p1, p2, ratio, center;\r
6543 \r
6544         if (!latlngs || latlngs.length === 0) {\r
6545                 throw new Error('latlngs not passed');\r
6546         }\r
6547 \r
6548         if (!isFlat(latlngs)) {\r
6549                 console.warn('latlngs are not flat! Only the first ring will be used');\r
6550                 latlngs = latlngs[0];\r
6551         }\r
6552 \r
6553         var centroidLatLng = toLatLng([0, 0]);\r
6554 \r
6555         var bounds = toLatLngBounds(latlngs);\r
6556         var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());\r
6557         // tests showed that below 1700 rounding errors are happening\r
6558         if (areaBounds < 1700) {\r
6559                 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors\r
6560                 centroidLatLng = centroid(latlngs);\r
6561         }\r
6562 \r
6563         var len = latlngs.length;\r
6564         var points = [];\r
6565         for (i = 0; i < len; i++) {\r
6566                 var latlng = toLatLng(latlngs[i]);\r
6567                 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));\r
6568         }\r
6569 \r
6570         for (i = 0, halfDist = 0; i < len - 1; i++) {\r
6571                 halfDist += points[i].distanceTo(points[i + 1]) / 2;\r
6572         }\r
6573 \r
6574         // The line is so small in the current view that all points are on the same pixel.\r
6575         if (halfDist === 0) {\r
6576                 center = points[0];\r
6577         } else {\r
6578                 for (i = 0, dist = 0; i < len - 1; i++) {\r
6579                         p1 = points[i];\r
6580                         p2 = points[i + 1];\r
6581                         segDist = p1.distanceTo(p2);\r
6582                         dist += segDist;\r
6583 \r
6584                         if (dist > halfDist) {\r
6585                                 ratio = (dist - halfDist) / segDist;\r
6586                                 center = [\r
6587                                         p2.x - ratio * (p2.x - p1.x),\r
6588                                         p2.y - ratio * (p2.y - p1.y)\r
6589                                 ];\r
6590                                 break;\r
6591                         }\r
6592                 }\r
6593         }\r
6594 \r
6595         var latlngCenter = crs.unproject(toPoint(center));\r
6596         return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);\r
6597   }
6598
6599   var LineUtil = {
6600     __proto__: null,
6601     simplify: simplify,
6602     pointToSegmentDistance: pointToSegmentDistance,
6603     closestPointOnSegment: closestPointOnSegment,
6604     clipSegment: clipSegment,
6605     _getEdgeIntersection: _getEdgeIntersection,
6606     _getBitCode: _getBitCode,
6607     _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6608     isFlat: isFlat,
6609     _flat: _flat,
6610     polylineCenter: polylineCenter
6611   };
6612
6613   /*\r
6614    * @namespace Projection\r
6615    * @section\r
6616    * Leaflet comes with a set of already defined Projections out of the box:\r
6617    *\r
6618    * @projection L.Projection.LonLat\r
6619    *\r
6620    * Equirectangular, or Plate Carree projection — the most simple projection,\r
6621    * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as\r
6622    * latitude. Also suitable for flat worlds, e.g. game maps. Used by the\r
6623    * `EPSG:4326` and `Simple` CRS.\r
6624    */\r
6625 \r
6626   var LonLat = {\r
6627         project: function (latlng) {\r
6628                 return new Point(latlng.lng, latlng.lat);\r
6629         },\r
6630 \r
6631         unproject: function (point) {\r
6632                 return new LatLng(point.y, point.x);\r
6633         },\r
6634 \r
6635         bounds: new Bounds([-180, -90], [180, 90])\r
6636   };
6637
6638   /*\r
6639    * @namespace Projection\r
6640    * @projection L.Projection.Mercator\r
6641    *\r
6642    * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.\r
6643    */\r
6644 \r
6645   var Mercator = {\r
6646         R: 6378137,\r
6647         R_MINOR: 6356752.314245179,\r
6648 \r
6649         bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),\r
6650 \r
6651         project: function (latlng) {\r
6652                 var d = Math.PI / 180,\r
6653                     r = this.R,\r
6654                     y = latlng.lat * d,\r
6655                     tmp = this.R_MINOR / r,\r
6656                     e = Math.sqrt(1 - tmp * tmp),\r
6657                     con = e * Math.sin(y);\r
6658 \r
6659                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);\r
6660                 y = -r * Math.log(Math.max(ts, 1E-10));\r
6661 \r
6662                 return new Point(latlng.lng * d * r, y);\r
6663         },\r
6664 \r
6665         unproject: function (point) {\r
6666                 var d = 180 / Math.PI,\r
6667                     r = this.R,\r
6668                     tmp = this.R_MINOR / r,\r
6669                     e = Math.sqrt(1 - tmp * tmp),\r
6670                     ts = Math.exp(-point.y / r),\r
6671                     phi = Math.PI / 2 - 2 * Math.atan(ts);\r
6672 \r
6673                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {\r
6674                         con = e * Math.sin(phi);\r
6675                         con = Math.pow((1 - con) / (1 + con), e / 2);\r
6676                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;\r
6677                         phi += dphi;\r
6678                 }\r
6679 \r
6680                 return new LatLng(phi * d, point.x * d / r);\r
6681         }\r
6682   };
6683
6684   /*
6685    * @class Projection
6686
6687    * An object with methods for projecting geographical coordinates of the world onto
6688    * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
6689
6690    * @property bounds: Bounds
6691    * The bounds (specified in CRS units) where the projection is valid
6692
6693    * @method project(latlng: LatLng): Point
6694    * Projects geographical coordinates into a 2D point.
6695    * Only accepts actual `L.LatLng` instances, not arrays.
6696
6697    * @method unproject(point: Point): LatLng
6698    * The inverse of `project`. Projects a 2D point into a geographical location.
6699    * Only accepts actual `L.Point` instances, not arrays.
6700
6701    * Note that the projection instances do not inherit from Leaflet's `Class` object,
6702    * and can't be instantiated. Also, new classes can't inherit from them,
6703    * and methods can't be added to them with the `include` function.
6704
6705    */
6706
6707   var index = {
6708     __proto__: null,
6709     LonLat: LonLat,
6710     Mercator: Mercator,
6711     SphericalMercator: SphericalMercator
6712   };
6713
6714   /*\r
6715    * @namespace CRS\r
6716    * @crs L.CRS.EPSG3395\r
6717    *\r
6718    * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.\r
6719    */\r
6720   var EPSG3395 = extend({}, Earth, {\r
6721         code: 'EPSG:3395',\r
6722         projection: Mercator,\r
6723 \r
6724         transformation: (function () {\r
6725                 var scale = 0.5 / (Math.PI * Mercator.R);\r
6726                 return toTransformation(scale, 0.5, -scale, 0.5);\r
6727         }())\r
6728   });
6729
6730   /*\r
6731    * @namespace CRS\r
6732    * @crs L.CRS.EPSG4326\r
6733    *\r
6734    * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.\r
6735    *\r
6736    * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),\r
6737    * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`\r
6738    * with this CRS, ensure that there are two 256x256 pixel tiles covering the\r
6739    * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),\r
6740    * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.\r
6741    */\r
6742 \r
6743   var EPSG4326 = extend({}, Earth, {\r
6744         code: 'EPSG:4326',\r
6745         projection: LonLat,\r
6746         transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)\r
6747   });
6748
6749   /*
6750    * @namespace CRS
6751    * @crs L.CRS.Simple
6752    *
6753    * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6754    * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6755    * axis should still be inverted (going from bottom to top). `distance()` returns
6756    * simple euclidean distance.
6757    */
6758
6759   var Simple = extend({}, CRS, {
6760         projection: LonLat,
6761         transformation: toTransformation(1, 0, -1, 0),
6762
6763         scale: function (zoom) {
6764                 return Math.pow(2, zoom);
6765         },
6766
6767         zoom: function (scale) {
6768                 return Math.log(scale) / Math.LN2;
6769         },
6770
6771         distance: function (latlng1, latlng2) {
6772                 var dx = latlng2.lng - latlng1.lng,
6773                     dy = latlng2.lat - latlng1.lat;
6774
6775                 return Math.sqrt(dx * dx + dy * dy);
6776         },
6777
6778         infinite: true
6779   });
6780
6781   CRS.Earth = Earth;
6782   CRS.EPSG3395 = EPSG3395;
6783   CRS.EPSG3857 = EPSG3857;
6784   CRS.EPSG900913 = EPSG900913;
6785   CRS.EPSG4326 = EPSG4326;
6786   CRS.Simple = Simple;
6787
6788   /*
6789    * @class Layer
6790    * @inherits Evented
6791    * @aka L.Layer
6792    * @aka ILayer
6793    *
6794    * A set of methods from the Layer base class that all Leaflet layers use.
6795    * Inherits all methods, options and events from `L.Evented`.
6796    *
6797    * @example
6798    *
6799    * ```js
6800    * var layer = L.marker(latlng).addTo(map);
6801    * layer.addTo(map);
6802    * layer.remove();
6803    * ```
6804    *
6805    * @event add: Event
6806    * Fired after the layer is added to a map
6807    *
6808    * @event remove: Event
6809    * Fired after the layer is removed from a map
6810    */
6811
6812
6813   var Layer = Evented.extend({
6814
6815         // Classes extending `L.Layer` will inherit the following options:
6816         options: {
6817                 // @option pane: String = 'overlayPane'
6818                 // 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.
6819                 pane: 'overlayPane',
6820
6821                 // @option attribution: String = null
6822                 // 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.
6823                 attribution: null,
6824
6825                 bubblingMouseEvents: true
6826         },
6827
6828         /* @section
6829          * Classes extending `L.Layer` will inherit the following methods:
6830          *
6831          * @method addTo(map: Map|LayerGroup): this
6832          * Adds the layer to the given map or layer group.
6833          */
6834         addTo: function (map) {
6835                 map.addLayer(this);
6836                 return this;
6837         },
6838
6839         // @method remove: this
6840         // Removes the layer from the map it is currently active on.
6841         remove: function () {
6842                 return this.removeFrom(this._map || this._mapToAdd);
6843         },
6844
6845         // @method removeFrom(map: Map): this
6846         // Removes the layer from the given map
6847         //
6848         // @alternative
6849         // @method removeFrom(group: LayerGroup): this
6850         // Removes the layer from the given `LayerGroup`
6851         removeFrom: function (obj) {
6852                 if (obj) {
6853                         obj.removeLayer(this);
6854                 }
6855                 return this;
6856         },
6857
6858         // @method getPane(name? : String): HTMLElement
6859         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6860         getPane: function (name) {
6861                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6862         },
6863
6864         addInteractiveTarget: function (targetEl) {
6865                 this._map._targets[stamp(targetEl)] = this;
6866                 return this;
6867         },
6868
6869         removeInteractiveTarget: function (targetEl) {
6870                 delete this._map._targets[stamp(targetEl)];
6871                 return this;
6872         },
6873
6874         // @method getAttribution: String
6875         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6876         getAttribution: function () {
6877                 return this.options.attribution;
6878         },
6879
6880         _layerAdd: function (e) {
6881                 var map = e.target;
6882
6883                 // check in case layer gets added and then removed before the map is ready
6884                 if (!map.hasLayer(this)) { return; }
6885
6886                 this._map = map;
6887                 this._zoomAnimated = map._zoomAnimated;
6888
6889                 if (this.getEvents) {
6890                         var events = this.getEvents();
6891                         map.on(events, this);
6892                         this.once('remove', function () {
6893                                 map.off(events, this);
6894                         }, this);
6895                 }
6896
6897                 this.onAdd(map);
6898
6899                 this.fire('add');
6900                 map.fire('layeradd', {layer: this});
6901         }
6902   });
6903
6904   /* @section Extension methods
6905    * @uninheritable
6906    *
6907    * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6908    *
6909    * @method onAdd(map: Map): this
6910    * 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).
6911    *
6912    * @method onRemove(map: Map): this
6913    * 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).
6914    *
6915    * @method getEvents(): Object
6916    * 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.
6917    *
6918    * @method getAttribution(): String
6919    * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6920    *
6921    * @method beforeAdd(map: Map): this
6922    * 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.
6923    */
6924
6925
6926   /* @namespace Map
6927    * @section Layer events
6928    *
6929    * @event layeradd: LayerEvent
6930    * Fired when a new layer is added to the map.
6931    *
6932    * @event layerremove: LayerEvent
6933    * Fired when some layer is removed from the map
6934    *
6935    * @section Methods for Layers and Controls
6936    */
6937   Map.include({
6938         // @method addLayer(layer: Layer): this
6939         // Adds the given layer to the map
6940         addLayer: function (layer) {
6941                 if (!layer._layerAdd) {
6942                         throw new Error('The provided object is not a Layer.');
6943                 }
6944
6945                 var id = stamp(layer);
6946                 if (this._layers[id]) { return this; }
6947                 this._layers[id] = layer;
6948
6949                 layer._mapToAdd = this;
6950
6951                 if (layer.beforeAdd) {
6952                         layer.beforeAdd(this);
6953                 }
6954
6955                 this.whenReady(layer._layerAdd, layer);
6956
6957                 return this;
6958         },
6959
6960         // @method removeLayer(layer: Layer): this
6961         // Removes the given layer from the map.
6962         removeLayer: function (layer) {
6963                 var id = stamp(layer);
6964
6965                 if (!this._layers[id]) { return this; }
6966
6967                 if (this._loaded) {
6968                         layer.onRemove(this);
6969                 }
6970
6971                 delete this._layers[id];
6972
6973                 if (this._loaded) {
6974                         this.fire('layerremove', {layer: layer});
6975                         layer.fire('remove');
6976                 }
6977
6978                 layer._map = layer._mapToAdd = null;
6979
6980                 return this;
6981         },
6982
6983         // @method hasLayer(layer: Layer): Boolean
6984         // Returns `true` if the given layer is currently added to the map
6985         hasLayer: function (layer) {
6986                 return stamp(layer) in this._layers;
6987         },
6988
6989         /* @method eachLayer(fn: Function, context?: Object): this
6990          * Iterates over the layers of the map, optionally specifying context of the iterator function.
6991          * ```
6992          * map.eachLayer(function(layer){
6993          *     layer.bindPopup('Hello');
6994          * });
6995          * ```
6996          */
6997         eachLayer: function (method, context) {
6998                 for (var i in this._layers) {
6999                         method.call(context, this._layers[i]);
7000                 }
7001                 return this;
7002         },
7003
7004         _addLayers: function (layers) {
7005                 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
7006
7007                 for (var i = 0, len = layers.length; i < len; i++) {
7008                         this.addLayer(layers[i]);
7009                 }
7010         },
7011
7012         _addZoomLimit: function (layer) {
7013                 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
7014                         this._zoomBoundLayers[stamp(layer)] = layer;
7015                         this._updateZoomLevels();
7016                 }
7017         },
7018
7019         _removeZoomLimit: function (layer) {
7020                 var id = stamp(layer);
7021
7022                 if (this._zoomBoundLayers[id]) {
7023                         delete this._zoomBoundLayers[id];
7024                         this._updateZoomLevels();
7025                 }
7026         },
7027
7028         _updateZoomLevels: function () {
7029                 var minZoom = Infinity,
7030                     maxZoom = -Infinity,
7031                     oldZoomSpan = this._getZoomSpan();
7032
7033                 for (var i in this._zoomBoundLayers) {
7034                         var options = this._zoomBoundLayers[i].options;
7035
7036                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
7037                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
7038                 }
7039
7040                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
7041                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
7042
7043                 // @section Map state change events
7044                 // @event zoomlevelschange: Event
7045                 // Fired when the number of zoomlevels on the map is changed due
7046                 // to adding or removing a layer.
7047                 if (oldZoomSpan !== this._getZoomSpan()) {
7048                         this.fire('zoomlevelschange');
7049                 }
7050
7051                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
7052                         this.setZoom(this._layersMaxZoom);
7053                 }
7054                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
7055                         this.setZoom(this._layersMinZoom);
7056                 }
7057         }
7058   });
7059
7060   /*\r
7061    * @class LayerGroup\r
7062    * @aka L.LayerGroup\r
7063    * @inherits Interactive layer\r
7064    *\r
7065    * Used to group several layers and handle them as one. If you add it to the map,\r
7066    * any layers added or removed from the group will be added/removed on the map as\r
7067    * well. Extends `Layer`.\r
7068    *\r
7069    * @example\r
7070    *\r
7071    * ```js\r
7072    * L.layerGroup([marker1, marker2])\r
7073    *    .addLayer(polyline)\r
7074    *    .addTo(map);\r
7075    * ```\r
7076    */\r
7077 \r
7078   var LayerGroup = Layer.extend({\r
7079 \r
7080         initialize: function (layers, options) {\r
7081                 setOptions(this, options);\r
7082 \r
7083                 this._layers = {};\r
7084 \r
7085                 var i, len;\r
7086 \r
7087                 if (layers) {\r
7088                         for (i = 0, len = layers.length; i < len; i++) {\r
7089                                 this.addLayer(layers[i]);\r
7090                         }\r
7091                 }\r
7092         },\r
7093 \r
7094         // @method addLayer(layer: Layer): this\r
7095         // Adds the given layer to the group.\r
7096         addLayer: function (layer) {\r
7097                 var id = this.getLayerId(layer);\r
7098 \r
7099                 this._layers[id] = layer;\r
7100 \r
7101                 if (this._map) {\r
7102                         this._map.addLayer(layer);\r
7103                 }\r
7104 \r
7105                 return this;\r
7106         },\r
7107 \r
7108         // @method removeLayer(layer: Layer): this\r
7109         // Removes the given layer from the group.\r
7110         // @alternative\r
7111         // @method removeLayer(id: Number): this\r
7112         // Removes the layer with the given internal ID from the group.\r
7113         removeLayer: function (layer) {\r
7114                 var id = layer in this._layers ? layer : this.getLayerId(layer);\r
7115 \r
7116                 if (this._map && this._layers[id]) {\r
7117                         this._map.removeLayer(this._layers[id]);\r
7118                 }\r
7119 \r
7120                 delete this._layers[id];\r
7121 \r
7122                 return this;\r
7123         },\r
7124 \r
7125         // @method hasLayer(layer: Layer): Boolean\r
7126         // Returns `true` if the given layer is currently added to the group.\r
7127         // @alternative\r
7128         // @method hasLayer(id: Number): Boolean\r
7129         // Returns `true` if the given internal ID is currently added to the group.\r
7130         hasLayer: function (layer) {\r
7131                 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);\r
7132                 return layerId in this._layers;\r
7133         },\r
7134 \r
7135         // @method clearLayers(): this\r
7136         // Removes all the layers from the group.\r
7137         clearLayers: function () {\r
7138                 return this.eachLayer(this.removeLayer, this);\r
7139         },\r
7140 \r
7141         // @method invoke(methodName: String, …): this\r
7142         // Calls `methodName` on every layer contained in this group, passing any\r
7143         // additional parameters. Has no effect if the layers contained do not\r
7144         // implement `methodName`.\r
7145         invoke: function (methodName) {\r
7146                 var args = Array.prototype.slice.call(arguments, 1),\r
7147                     i, layer;\r
7148 \r
7149                 for (i in this._layers) {\r
7150                         layer = this._layers[i];\r
7151 \r
7152                         if (layer[methodName]) {\r
7153                                 layer[methodName].apply(layer, args);\r
7154                         }\r
7155                 }\r
7156 \r
7157                 return this;\r
7158         },\r
7159 \r
7160         onAdd: function (map) {\r
7161                 this.eachLayer(map.addLayer, map);\r
7162         },\r
7163 \r
7164         onRemove: function (map) {\r
7165                 this.eachLayer(map.removeLayer, map);\r
7166         },\r
7167 \r
7168         // @method eachLayer(fn: Function, context?: Object): this\r
7169         // Iterates over the layers of the group, optionally specifying context of the iterator function.\r
7170         // ```js\r
7171         // group.eachLayer(function (layer) {\r
7172         //      layer.bindPopup('Hello');\r
7173         // });\r
7174         // ```\r
7175         eachLayer: function (method, context) {\r
7176                 for (var i in this._layers) {\r
7177                         method.call(context, this._layers[i]);\r
7178                 }\r
7179                 return this;\r
7180         },\r
7181 \r
7182         // @method getLayer(id: Number): Layer\r
7183         // Returns the layer with the given internal ID.\r
7184         getLayer: function (id) {\r
7185                 return this._layers[id];\r
7186         },\r
7187 \r
7188         // @method getLayers(): Layer[]\r
7189         // Returns an array of all the layers added to the group.\r
7190         getLayers: function () {\r
7191                 var layers = [];\r
7192                 this.eachLayer(layers.push, layers);\r
7193                 return layers;\r
7194         },\r
7195 \r
7196         // @method setZIndex(zIndex: Number): this\r
7197         // Calls `setZIndex` on every layer contained in this group, passing the z-index.\r
7198         setZIndex: function (zIndex) {\r
7199                 return this.invoke('setZIndex', zIndex);\r
7200         },\r
7201 \r
7202         // @method getLayerId(layer: Layer): Number\r
7203         // Returns the internal ID for a layer\r
7204         getLayerId: function (layer) {\r
7205                 return stamp(layer);\r
7206         }\r
7207   });\r
7208 \r
7209 \r
7210   // @factory L.layerGroup(layers?: Layer[], options?: Object)\r
7211   // Create a layer group, optionally given an initial set of layers and an `options` object.\r
7212   var layerGroup = function (layers, options) {\r
7213         return new LayerGroup(layers, options);\r
7214   };
7215
7216   /*\r
7217    * @class FeatureGroup\r
7218    * @aka L.FeatureGroup\r
7219    * @inherits LayerGroup\r
7220    *\r
7221    * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:\r
7222    *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))\r
7223    *  * Events are propagated to the `FeatureGroup`, so if the group has an event\r
7224    * handler, it will handle events from any of the layers. This includes mouse events\r
7225    * and custom events.\r
7226    *  * Has `layeradd` and `layerremove` events\r
7227    *\r
7228    * @example\r
7229    *\r
7230    * ```js\r
7231    * L.featureGroup([marker1, marker2, polyline])\r
7232    *    .bindPopup('Hello world!')\r
7233    *    .on('click', function() { alert('Clicked on a member of the group!'); })\r
7234    *    .addTo(map);\r
7235    * ```\r
7236    */\r
7237 \r
7238   var FeatureGroup = LayerGroup.extend({\r
7239 \r
7240         addLayer: function (layer) {\r
7241                 if (this.hasLayer(layer)) {\r
7242                         return this;\r
7243                 }\r
7244 \r
7245                 layer.addEventParent(this);\r
7246 \r
7247                 LayerGroup.prototype.addLayer.call(this, layer);\r
7248 \r
7249                 // @event layeradd: LayerEvent\r
7250                 // Fired when a layer is added to this `FeatureGroup`\r
7251                 return this.fire('layeradd', {layer: layer});\r
7252         },\r
7253 \r
7254         removeLayer: function (layer) {\r
7255                 if (!this.hasLayer(layer)) {\r
7256                         return this;\r
7257                 }\r
7258                 if (layer in this._layers) {\r
7259                         layer = this._layers[layer];\r
7260                 }\r
7261 \r
7262                 layer.removeEventParent(this);\r
7263 \r
7264                 LayerGroup.prototype.removeLayer.call(this, layer);\r
7265 \r
7266                 // @event layerremove: LayerEvent\r
7267                 // Fired when a layer is removed from this `FeatureGroup`\r
7268                 return this.fire('layerremove', {layer: layer});\r
7269         },\r
7270 \r
7271         // @method setStyle(style: Path options): this\r
7272         // Sets the given path options to each layer of the group that has a `setStyle` method.\r
7273         setStyle: function (style) {\r
7274                 return this.invoke('setStyle', style);\r
7275         },\r
7276 \r
7277         // @method bringToFront(): this\r
7278         // Brings the layer group to the top of all other layers\r
7279         bringToFront: function () {\r
7280                 return this.invoke('bringToFront');\r
7281         },\r
7282 \r
7283         // @method bringToBack(): this\r
7284         // Brings the layer group to the back of all other layers\r
7285         bringToBack: function () {\r
7286                 return this.invoke('bringToBack');\r
7287         },\r
7288 \r
7289         // @method getBounds(): LatLngBounds\r
7290         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).\r
7291         getBounds: function () {\r
7292                 var bounds = new LatLngBounds();\r
7293 \r
7294                 for (var id in this._layers) {\r
7295                         var layer = this._layers[id];\r
7296                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());\r
7297                 }\r
7298                 return bounds;\r
7299         }\r
7300   });\r
7301 \r
7302   // @factory L.featureGroup(layers?: Layer[], options?: Object)\r
7303   // Create a feature group, optionally given an initial set of layers and an `options` object.\r
7304   var featureGroup = function (layers, options) {\r
7305         return new FeatureGroup(layers, options);\r
7306   };
7307
7308   /*\r
7309    * @class Icon\r
7310    * @aka L.Icon\r
7311    *\r
7312    * Represents an icon to provide when creating a marker.\r
7313    *\r
7314    * @example\r
7315    *\r
7316    * ```js\r
7317    * var myIcon = L.icon({\r
7318    *     iconUrl: 'my-icon.png',\r
7319    *     iconRetinaUrl: 'my-icon@2x.png',\r
7320    *     iconSize: [38, 95],\r
7321    *     iconAnchor: [22, 94],\r
7322    *     popupAnchor: [-3, -76],\r
7323    *     shadowUrl: 'my-icon-shadow.png',\r
7324    *     shadowRetinaUrl: 'my-icon-shadow@2x.png',\r
7325    *     shadowSize: [68, 95],\r
7326    *     shadowAnchor: [22, 94]\r
7327    * });\r
7328    *\r
7329    * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);\r
7330    * ```\r
7331    *\r
7332    * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.\r
7333    *\r
7334    */\r
7335 \r
7336   var Icon = Class.extend({\r
7337 \r
7338         /* @section\r
7339          * @aka Icon options\r
7340          *\r
7341          * @option iconUrl: String = null\r
7342          * **(required)** The URL to the icon image (absolute or relative to your script path).\r
7343          *\r
7344          * @option iconRetinaUrl: String = null\r
7345          * The URL to a retina sized version of the icon image (absolute or relative to your\r
7346          * script path). Used for Retina screen devices.\r
7347          *\r
7348          * @option iconSize: Point = null\r
7349          * Size of the icon image in pixels.\r
7350          *\r
7351          * @option iconAnchor: Point = null\r
7352          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon\r
7353          * will be aligned so that this point is at the marker's geographical location. Centered\r
7354          * by default if size is specified, also can be set in CSS with negative margins.\r
7355          *\r
7356          * @option popupAnchor: Point = [0, 0]\r
7357          * The coordinates of the point from which popups will "open", relative to the icon anchor.\r
7358          *\r
7359          * @option tooltipAnchor: Point = [0, 0]\r
7360          * The coordinates of the point from which tooltips will "open", relative to the icon anchor.\r
7361          *\r
7362          * @option shadowUrl: String = null\r
7363          * The URL to the icon shadow image. If not specified, no shadow image will be created.\r
7364          *\r
7365          * @option shadowRetinaUrl: String = null\r
7366          *\r
7367          * @option shadowSize: Point = null\r
7368          * Size of the shadow image in pixels.\r
7369          *\r
7370          * @option shadowAnchor: Point = null\r
7371          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same\r
7372          * as iconAnchor if not specified).\r
7373          *\r
7374          * @option className: String = ''\r
7375          * A custom class name to assign to both icon and shadow images. Empty by default.\r
7376          */\r
7377 \r
7378         options: {\r
7379                 popupAnchor: [0, 0],\r
7380                 tooltipAnchor: [0, 0],\r
7381 \r
7382                 // @option crossOrigin: Boolean|String = false\r
7383                 // Whether the crossOrigin attribute will be added to the tiles.\r
7384                 // 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.\r
7385                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.\r
7386                 crossOrigin: false\r
7387         },\r
7388 \r
7389         initialize: function (options) {\r
7390                 setOptions(this, options);\r
7391         },\r
7392 \r
7393         // @method createIcon(oldIcon?: HTMLElement): HTMLElement\r
7394         // Called internally when the icon has to be shown, returns a `<img>` HTML element\r
7395         // styled according to the options.\r
7396         createIcon: function (oldIcon) {\r
7397                 return this._createIcon('icon', oldIcon);\r
7398         },\r
7399 \r
7400         // @method createShadow(oldIcon?: HTMLElement): HTMLElement\r
7401         // As `createIcon`, but for the shadow beneath it.\r
7402         createShadow: function (oldIcon) {\r
7403                 return this._createIcon('shadow', oldIcon);\r
7404         },\r
7405 \r
7406         _createIcon: function (name, oldIcon) {\r
7407                 var src = this._getIconUrl(name);\r
7408 \r
7409                 if (!src) {\r
7410                         if (name === 'icon') {\r
7411                                 throw new Error('iconUrl not set in Icon options (see the docs).');\r
7412                         }\r
7413                         return null;\r
7414                 }\r
7415 \r
7416                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);\r
7417                 this._setIconStyles(img, name);\r
7418 \r
7419                 if (this.options.crossOrigin || this.options.crossOrigin === '') {\r
7420                         img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;\r
7421                 }\r
7422 \r
7423                 return img;\r
7424         },\r
7425 \r
7426         _setIconStyles: function (img, name) {\r
7427                 var options = this.options;\r
7428                 var sizeOption = options[name + 'Size'];\r
7429 \r
7430                 if (typeof sizeOption === 'number') {\r
7431                         sizeOption = [sizeOption, sizeOption];\r
7432                 }\r
7433 \r
7434                 var size = toPoint(sizeOption),\r
7435                     anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||\r
7436                             size && size.divideBy(2, true));\r
7437 \r
7438                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');\r
7439 \r
7440                 if (anchor) {\r
7441                         img.style.marginLeft = (-anchor.x) + 'px';\r
7442                         img.style.marginTop  = (-anchor.y) + 'px';\r
7443                 }\r
7444 \r
7445                 if (size) {\r
7446                         img.style.width  = size.x + 'px';\r
7447                         img.style.height = size.y + 'px';\r
7448                 }\r
7449         },\r
7450 \r
7451         _createImg: function (src, el) {\r
7452                 el = el || document.createElement('img');\r
7453                 el.src = src;\r
7454                 return el;\r
7455         },\r
7456 \r
7457         _getIconUrl: function (name) {\r
7458                 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];\r
7459         }\r
7460   });\r
7461 \r
7462 \r
7463   // @factory L.icon(options: Icon options)\r
7464   // Creates an icon instance with the given options.\r
7465   function icon(options) {\r
7466         return new Icon(options);\r
7467   }
7468
7469   /*
7470    * @miniclass Icon.Default (Icon)
7471    * @aka L.Icon.Default
7472    * @section
7473    *
7474    * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7475    * no icon is specified. Points to the blue marker image distributed with Leaflet
7476    * releases.
7477    *
7478    * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7479    * (which is a set of `Icon options`).
7480    *
7481    * If you want to _completely_ replace the default icon, override the
7482    * `L.Marker.prototype.options.icon` with your own icon instead.
7483    */
7484
7485   var IconDefault = Icon.extend({
7486
7487         options: {
7488                 iconUrl:       'marker-icon.png',
7489                 iconRetinaUrl: 'marker-icon-2x.png',
7490                 shadowUrl:     'marker-shadow.png',
7491                 iconSize:    [25, 41],
7492                 iconAnchor:  [12, 41],
7493                 popupAnchor: [1, -34],
7494                 tooltipAnchor: [16, -28],
7495                 shadowSize:  [41, 41]
7496         },
7497
7498         _getIconUrl: function (name) {
7499                 if (typeof IconDefault.imagePath !== 'string') {        // Deprecated, backwards-compatibility only
7500                         IconDefault.imagePath = this._detectIconPath();
7501                 }
7502
7503                 // @option imagePath: String
7504                 // `Icon.Default` will try to auto-detect the location of the
7505                 // blue icon images. If you are placing these images in a non-standard
7506                 // way, set this option to point to the right path.
7507                 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7508         },
7509
7510         _stripUrl: function (path) {    // separate function to use in tests
7511                 var strip = function (str, re, idx) {
7512                         var match = re.exec(str);
7513                         return match && match[idx];
7514                 };
7515                 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
7516                 return path && strip(path, /^(.*)marker-icon\.png$/, 1);
7517         },
7518
7519         _detectIconPath: function () {
7520                 var el = create$1('div',  'leaflet-default-icon-path', document.body);
7521                 var path = getStyle(el, 'background-image') ||
7522                            getStyle(el, 'backgroundImage');     // IE8
7523
7524                 document.body.removeChild(el);
7525                 path = this._stripUrl(path);
7526                 if (path) { return path; }
7527                 var link = document.querySelector('link[href$="leaflet.css"]');
7528                 if (!link) { return ''; }
7529                 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
7530         }
7531   });
7532
7533   /*
7534    * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7535    */
7536
7537
7538   /* @namespace Marker
7539    * @section Interaction handlers
7540    *
7541    * 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:
7542    *
7543    * ```js
7544    * marker.dragging.disable();
7545    * ```
7546    *
7547    * @property dragging: Handler
7548    * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7549    */
7550
7551   var MarkerDrag = Handler.extend({
7552         initialize: function (marker) {
7553                 this._marker = marker;
7554         },
7555
7556         addHooks: function () {
7557                 var icon = this._marker._icon;
7558
7559                 if (!this._draggable) {
7560                         this._draggable = new Draggable(icon, icon, true);
7561                 }
7562
7563                 this._draggable.on({
7564                         dragstart: this._onDragStart,
7565                         predrag: this._onPreDrag,
7566                         drag: this._onDrag,
7567                         dragend: this._onDragEnd
7568                 }, this).enable();
7569
7570                 addClass(icon, 'leaflet-marker-draggable');
7571         },
7572
7573         removeHooks: function () {
7574                 this._draggable.off({
7575                         dragstart: this._onDragStart,
7576                         predrag: this._onPreDrag,
7577                         drag: this._onDrag,
7578                         dragend: this._onDragEnd
7579                 }, this).disable();
7580
7581                 if (this._marker._icon) {
7582                         removeClass(this._marker._icon, 'leaflet-marker-draggable');
7583                 }
7584         },
7585
7586         moved: function () {
7587                 return this._draggable && this._draggable._moved;
7588         },
7589
7590         _adjustPan: function (e) {
7591                 var marker = this._marker,
7592                     map = marker._map,
7593                     speed = this._marker.options.autoPanSpeed,
7594                     padding = this._marker.options.autoPanPadding,
7595                     iconPos = getPosition(marker._icon),
7596                     bounds = map.getPixelBounds(),
7597                     origin = map.getPixelOrigin();
7598
7599                 var panBounds = toBounds(
7600                         bounds.min._subtract(origin).add(padding),
7601                         bounds.max._subtract(origin).subtract(padding)
7602                 );
7603
7604                 if (!panBounds.contains(iconPos)) {
7605                         // Compute incremental movement
7606                         var movement = toPoint(
7607                                 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7608                                 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7609
7610                                 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7611                                 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7612                         ).multiplyBy(speed);
7613
7614                         map.panBy(movement, {animate: false});
7615
7616                         this._draggable._newPos._add(movement);
7617                         this._draggable._startPos._add(movement);
7618
7619                         setPosition(marker._icon, this._draggable._newPos);
7620                         this._onDrag(e);
7621
7622                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7623                 }
7624         },
7625
7626         _onDragStart: function () {
7627                 // @section Dragging events
7628                 // @event dragstart: Event
7629                 // Fired when the user starts dragging the marker.
7630
7631                 // @event movestart: Event
7632                 // Fired when the marker starts moving (because of dragging).
7633
7634                 this._oldLatLng = this._marker.getLatLng();
7635
7636                 // When using ES6 imports it could not be set when `Popup` was not imported as well
7637                 this._marker.closePopup && this._marker.closePopup();
7638
7639                 this._marker
7640                         .fire('movestart')
7641                         .fire('dragstart');
7642         },
7643
7644         _onPreDrag: function (e) {
7645                 if (this._marker.options.autoPan) {
7646                         cancelAnimFrame(this._panRequest);
7647                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7648                 }
7649         },
7650
7651         _onDrag: function (e) {
7652                 var marker = this._marker,
7653                     shadow = marker._shadow,
7654                     iconPos = getPosition(marker._icon),
7655                     latlng = marker._map.layerPointToLatLng(iconPos);
7656
7657                 // update shadow position
7658                 if (shadow) {
7659                         setPosition(shadow, iconPos);
7660                 }
7661
7662                 marker._latlng = latlng;
7663                 e.latlng = latlng;
7664                 e.oldLatLng = this._oldLatLng;
7665
7666                 // @event drag: Event
7667                 // Fired repeatedly while the user drags the marker.
7668                 marker
7669                     .fire('move', e)
7670                     .fire('drag', e);
7671         },
7672
7673         _onDragEnd: function (e) {
7674                 // @event dragend: DragEndEvent
7675                 // Fired when the user stops dragging the marker.
7676
7677                  cancelAnimFrame(this._panRequest);
7678
7679                 // @event moveend: Event
7680                 // Fired when the marker stops moving (because of dragging).
7681                 delete this._oldLatLng;
7682                 this._marker
7683                     .fire('moveend')
7684                     .fire('dragend', e);
7685         }
7686   });
7687
7688   /*\r
7689    * @class Marker\r
7690    * @inherits Interactive layer\r
7691    * @aka L.Marker\r
7692    * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.\r
7693    *\r
7694    * @example\r
7695    *\r
7696    * ```js\r
7697    * L.marker([50.5, 30.5]).addTo(map);\r
7698    * ```\r
7699    */\r
7700 \r
7701   var Marker = Layer.extend({\r
7702 \r
7703         // @section\r
7704         // @aka Marker options\r
7705         options: {\r
7706                 // @option icon: Icon = *\r
7707                 // Icon instance to use for rendering the marker.\r
7708                 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.\r
7709                 // If not specified, a common instance of `L.Icon.Default` is used.\r
7710                 icon: new IconDefault(),\r
7711 \r
7712                 // Option inherited from "Interactive layer" abstract class\r
7713                 interactive: true,\r
7714 \r
7715                 // @option keyboard: Boolean = true\r
7716                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.\r
7717                 keyboard: true,\r
7718 \r
7719                 // @option title: String = ''\r
7720                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).\r
7721                 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).\r
7722                 title: '',\r
7723 \r
7724                 // @option alt: String = 'Marker'\r
7725                 // Text for the `alt` attribute of the icon image.\r
7726                 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).\r
7727                 alt: 'Marker',\r
7728 \r
7729                 // @option zIndexOffset: Number = 0\r
7730                 // 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).\r
7731                 zIndexOffset: 0,\r
7732 \r
7733                 // @option opacity: Number = 1.0\r
7734                 // The opacity of the marker.\r
7735                 opacity: 1,\r
7736 \r
7737                 // @option riseOnHover: Boolean = false\r
7738                 // If `true`, the marker will get on top of others when you hover the mouse over it.\r
7739                 riseOnHover: false,\r
7740 \r
7741                 // @option riseOffset: Number = 250\r
7742                 // The z-index offset used for the `riseOnHover` feature.\r
7743                 riseOffset: 250,\r
7744 \r
7745                 // @option pane: String = 'markerPane'\r
7746                 // `Map pane` where the markers icon will be added.\r
7747                 pane: 'markerPane',\r
7748 \r
7749                 // @option shadowPane: String = 'shadowPane'\r
7750                 // `Map pane` where the markers shadow will be added.\r
7751                 shadowPane: 'shadowPane',\r
7752 \r
7753                 // @option bubblingMouseEvents: Boolean = false\r
7754                 // When `true`, a mouse event on this marker will trigger the same event on the map\r
7755                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).\r
7756                 bubblingMouseEvents: false,\r
7757 \r
7758                 // @option autoPanOnFocus: Boolean = true\r
7759                 // When `true`, the map will pan whenever the marker is focused (via\r
7760                 // e.g. pressing `tab` on the keyboard) to ensure the marker is\r
7761                 // visible within the map's bounds\r
7762                 autoPanOnFocus: true,\r
7763 \r
7764                 // @section Draggable marker options\r
7765                 // @option draggable: Boolean = false\r
7766                 // Whether the marker is draggable with mouse/touch or not.\r
7767                 draggable: false,\r
7768 \r
7769                 // @option autoPan: Boolean = false\r
7770                 // Whether to pan the map when dragging this marker near its edge or not.\r
7771                 autoPan: false,\r
7772 \r
7773                 // @option autoPanPadding: Point = Point(50, 50)\r
7774                 // Distance (in pixels to the left/right and to the top/bottom) of the\r
7775                 // map edge to start panning the map.\r
7776                 autoPanPadding: [50, 50],\r
7777 \r
7778                 // @option autoPanSpeed: Number = 10\r
7779                 // Number of pixels the map should pan by.\r
7780                 autoPanSpeed: 10\r
7781         },\r
7782 \r
7783         /* @section\r
7784          *\r
7785          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:\r
7786          */\r
7787 \r
7788         initialize: function (latlng, options) {\r
7789                 setOptions(this, options);\r
7790                 this._latlng = toLatLng(latlng);\r
7791         },\r
7792 \r
7793         onAdd: function (map) {\r
7794                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;\r
7795 \r
7796                 if (this._zoomAnimated) {\r
7797                         map.on('zoomanim', this._animateZoom, this);\r
7798                 }\r
7799 \r
7800                 this._initIcon();\r
7801                 this.update();\r
7802         },\r
7803 \r
7804         onRemove: function (map) {\r
7805                 if (this.dragging && this.dragging.enabled()) {\r
7806                         this.options.draggable = true;\r
7807                         this.dragging.removeHooks();\r
7808                 }\r
7809                 delete this.dragging;\r
7810 \r
7811                 if (this._zoomAnimated) {\r
7812                         map.off('zoomanim', this._animateZoom, this);\r
7813                 }\r
7814 \r
7815                 this._removeIcon();\r
7816                 this._removeShadow();\r
7817         },\r
7818 \r
7819         getEvents: function () {\r
7820                 return {\r
7821                         zoom: this.update,\r
7822                         viewreset: this.update\r
7823                 };\r
7824         },\r
7825 \r
7826         // @method getLatLng: LatLng\r
7827         // Returns the current geographical position of the marker.\r
7828         getLatLng: function () {\r
7829                 return this._latlng;\r
7830         },\r
7831 \r
7832         // @method setLatLng(latlng: LatLng): this\r
7833         // Changes the marker position to the given point.\r
7834         setLatLng: function (latlng) {\r
7835                 var oldLatLng = this._latlng;\r
7836                 this._latlng = toLatLng(latlng);\r
7837                 this.update();\r
7838 \r
7839                 // @event move: Event\r
7840                 // 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`.\r
7841                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});\r
7842         },\r
7843 \r
7844         // @method setZIndexOffset(offset: Number): this\r
7845         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.\r
7846         setZIndexOffset: function (offset) {\r
7847                 this.options.zIndexOffset = offset;\r
7848                 return this.update();\r
7849         },\r
7850 \r
7851         // @method getIcon: Icon\r
7852         // Returns the current icon used by the marker\r
7853         getIcon: function () {\r
7854                 return this.options.icon;\r
7855         },\r
7856 \r
7857         // @method setIcon(icon: Icon): this\r
7858         // Changes the marker icon.\r
7859         setIcon: function (icon) {\r
7860 \r
7861                 this.options.icon = icon;\r
7862 \r
7863                 if (this._map) {\r
7864                         this._initIcon();\r
7865                         this.update();\r
7866                 }\r
7867 \r
7868                 if (this._popup) {\r
7869                         this.bindPopup(this._popup, this._popup.options);\r
7870                 }\r
7871 \r
7872                 return this;\r
7873         },\r
7874 \r
7875         getElement: function () {\r
7876                 return this._icon;\r
7877         },\r
7878 \r
7879         update: function () {\r
7880 \r
7881                 if (this._icon && this._map) {\r
7882                         var pos = this._map.latLngToLayerPoint(this._latlng).round();\r
7883                         this._setPos(pos);\r
7884                 }\r
7885 \r
7886                 return this;\r
7887         },\r
7888 \r
7889         _initIcon: function () {\r
7890                 var options = this.options,\r
7891                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');\r
7892 \r
7893                 var icon = options.icon.createIcon(this._icon),\r
7894                     addIcon = false;\r
7895 \r
7896                 // if we're not reusing the icon, remove the old one and init new one\r
7897                 if (icon !== this._icon) {\r
7898                         if (this._icon) {\r
7899                                 this._removeIcon();\r
7900                         }\r
7901                         addIcon = true;\r
7902 \r
7903                         if (options.title) {\r
7904                                 icon.title = options.title;\r
7905                         }\r
7906 \r
7907                         if (icon.tagName === 'IMG') {\r
7908                                 icon.alt = options.alt || '';\r
7909                         }\r
7910                 }\r
7911 \r
7912                 addClass(icon, classToAdd);\r
7913 \r
7914                 if (options.keyboard) {\r
7915                         icon.tabIndex = '0';\r
7916                         icon.setAttribute('role', 'button');\r
7917                 }\r
7918 \r
7919                 this._icon = icon;\r
7920 \r
7921                 if (options.riseOnHover) {\r
7922                         this.on({\r
7923                                 mouseover: this._bringToFront,\r
7924                                 mouseout: this._resetZIndex\r
7925                         });\r
7926                 }\r
7927 \r
7928                 if (this.options.autoPanOnFocus) {\r
7929                         on(icon, 'focus', this._panOnFocus, this);\r
7930                 }\r
7931 \r
7932                 var newShadow = options.icon.createShadow(this._shadow),\r
7933                     addShadow = false;\r
7934 \r
7935                 if (newShadow !== this._shadow) {\r
7936                         this._removeShadow();\r
7937                         addShadow = true;\r
7938                 }\r
7939 \r
7940                 if (newShadow) {\r
7941                         addClass(newShadow, classToAdd);\r
7942                         newShadow.alt = '';\r
7943                 }\r
7944                 this._shadow = newShadow;\r
7945 \r
7946 \r
7947                 if (options.opacity < 1) {\r
7948                         this._updateOpacity();\r
7949                 }\r
7950 \r
7951 \r
7952                 if (addIcon) {\r
7953                         this.getPane().appendChild(this._icon);\r
7954                 }\r
7955                 this._initInteraction();\r
7956                 if (newShadow && addShadow) {\r
7957                         this.getPane(options.shadowPane).appendChild(this._shadow);\r
7958                 }\r
7959         },\r
7960 \r
7961         _removeIcon: function () {\r
7962                 if (this.options.riseOnHover) {\r
7963                         this.off({\r
7964                                 mouseover: this._bringToFront,\r
7965                                 mouseout: this._resetZIndex\r
7966                         });\r
7967                 }\r
7968 \r
7969                 if (this.options.autoPanOnFocus) {\r
7970                         off(this._icon, 'focus', this._panOnFocus, this);\r
7971                 }\r
7972 \r
7973                 remove(this._icon);\r
7974                 this.removeInteractiveTarget(this._icon);\r
7975 \r
7976                 this._icon = null;\r
7977         },\r
7978 \r
7979         _removeShadow: function () {\r
7980                 if (this._shadow) {\r
7981                         remove(this._shadow);\r
7982                 }\r
7983                 this._shadow = null;\r
7984         },\r
7985 \r
7986         _setPos: function (pos) {\r
7987 \r
7988                 if (this._icon) {\r
7989                         setPosition(this._icon, pos);\r
7990                 }\r
7991 \r
7992                 if (this._shadow) {\r
7993                         setPosition(this._shadow, pos);\r
7994                 }\r
7995 \r
7996                 this._zIndex = pos.y + this.options.zIndexOffset;\r
7997 \r
7998                 this._resetZIndex();\r
7999         },\r
8000 \r
8001         _updateZIndex: function (offset) {\r
8002                 if (this._icon) {\r
8003                         this._icon.style.zIndex = this._zIndex + offset;\r
8004                 }\r
8005         },\r
8006 \r
8007         _animateZoom: function (opt) {\r
8008                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();\r
8009 \r
8010                 this._setPos(pos);\r
8011         },\r
8012 \r
8013         _initInteraction: function () {\r
8014 \r
8015                 if (!this.options.interactive) { return; }\r
8016 \r
8017                 addClass(this._icon, 'leaflet-interactive');\r
8018 \r
8019                 this.addInteractiveTarget(this._icon);\r
8020 \r
8021                 if (MarkerDrag) {\r
8022                         var draggable = this.options.draggable;\r
8023                         if (this.dragging) {\r
8024                                 draggable = this.dragging.enabled();\r
8025                                 this.dragging.disable();\r
8026                         }\r
8027 \r
8028                         this.dragging = new MarkerDrag(this);\r
8029 \r
8030                         if (draggable) {\r
8031                                 this.dragging.enable();\r
8032                         }\r
8033                 }\r
8034         },\r
8035 \r
8036         // @method setOpacity(opacity: Number): this\r
8037         // Changes the opacity of the marker.\r
8038         setOpacity: function (opacity) {\r
8039                 this.options.opacity = opacity;\r
8040                 if (this._map) {\r
8041                         this._updateOpacity();\r
8042                 }\r
8043 \r
8044                 return this;\r
8045         },\r
8046 \r
8047         _updateOpacity: function () {\r
8048                 var opacity = this.options.opacity;\r
8049 \r
8050                 if (this._icon) {\r
8051                         setOpacity(this._icon, opacity);\r
8052                 }\r
8053 \r
8054                 if (this._shadow) {\r
8055                         setOpacity(this._shadow, opacity);\r
8056                 }\r
8057         },\r
8058 \r
8059         _bringToFront: function () {\r
8060                 this._updateZIndex(this.options.riseOffset);\r
8061         },\r
8062 \r
8063         _resetZIndex: function () {\r
8064                 this._updateZIndex(0);\r
8065         },\r
8066 \r
8067         _panOnFocus: function () {\r
8068                 var map = this._map;\r
8069                 if (!map) { return; }\r
8070 \r
8071                 var iconOpts = this.options.icon.options;\r
8072                 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);\r
8073                 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);\r
8074 \r
8075                 map.panInside(this._latlng, {\r
8076                         paddingTopLeft: anchor,\r
8077                         paddingBottomRight: size.subtract(anchor)\r
8078                 });\r
8079         },\r
8080 \r
8081         _getPopupAnchor: function () {\r
8082                 return this.options.icon.options.popupAnchor;\r
8083         },\r
8084 \r
8085         _getTooltipAnchor: function () {\r
8086                 return this.options.icon.options.tooltipAnchor;\r
8087         }\r
8088   });\r
8089 \r
8090 \r
8091   // factory L.marker(latlng: LatLng, options? : Marker options)\r
8092 \r
8093   // @factory L.marker(latlng: LatLng, options? : Marker options)\r
8094   // Instantiates a Marker object given a geographical point and optionally an options object.\r
8095   function marker(latlng, options) {\r
8096         return new Marker(latlng, options);\r
8097   }
8098
8099   /*
8100    * @class Path
8101    * @aka L.Path
8102    * @inherits Interactive layer
8103    *
8104    * An abstract class that contains options and constants shared between vector
8105    * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8106    */
8107
8108   var Path = Layer.extend({
8109
8110         // @section
8111         // @aka Path options
8112         options: {
8113                 // @option stroke: Boolean = true
8114                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8115                 stroke: true,
8116
8117                 // @option color: String = '#3388ff'
8118                 // Stroke color
8119                 color: '#3388ff',
8120
8121                 // @option weight: Number = 3
8122                 // Stroke width in pixels
8123                 weight: 3,
8124
8125                 // @option opacity: Number = 1.0
8126                 // Stroke opacity
8127                 opacity: 1,
8128
8129                 // @option lineCap: String= 'round'
8130                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8131                 lineCap: 'round',
8132
8133                 // @option lineJoin: String = 'round'
8134                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8135                 lineJoin: 'round',
8136
8137                 // @option dashArray: String = null
8138                 // 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).
8139                 dashArray: null,
8140
8141                 // @option dashOffset: String = null
8142                 // 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).
8143                 dashOffset: null,
8144
8145                 // @option fill: Boolean = depends
8146                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8147                 fill: false,
8148
8149                 // @option fillColor: String = *
8150                 // Fill color. Defaults to the value of the [`color`](#path-color) option
8151                 fillColor: null,
8152
8153                 // @option fillOpacity: Number = 0.2
8154                 // Fill opacity.
8155                 fillOpacity: 0.2,
8156
8157                 // @option fillRule: String = 'evenodd'
8158                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8159                 fillRule: 'evenodd',
8160
8161                 // className: '',
8162
8163                 // Option inherited from "Interactive layer" abstract class
8164                 interactive: true,
8165
8166                 // @option bubblingMouseEvents: Boolean = true
8167                 // When `true`, a mouse event on this path will trigger the same event on the map
8168                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
8169                 bubblingMouseEvents: true
8170         },
8171
8172         beforeAdd: function (map) {
8173                 // Renderer is set here because we need to call renderer.getEvents
8174                 // before this.getEvents.
8175                 this._renderer = map.getRenderer(this);
8176         },
8177
8178         onAdd: function () {
8179                 this._renderer._initPath(this);
8180                 this._reset();
8181                 this._renderer._addPath(this);
8182         },
8183
8184         onRemove: function () {
8185                 this._renderer._removePath(this);
8186         },
8187
8188         // @method redraw(): this
8189         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8190         redraw: function () {
8191                 if (this._map) {
8192                         this._renderer._updatePath(this);
8193                 }
8194                 return this;
8195         },
8196
8197         // @method setStyle(style: Path options): this
8198         // Changes the appearance of a Path based on the options in the `Path options` object.
8199         setStyle: function (style) {
8200                 setOptions(this, style);
8201                 if (this._renderer) {
8202                         this._renderer._updateStyle(this);
8203                         if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
8204                                 this._updateBounds();
8205                         }
8206                 }
8207                 return this;
8208         },
8209
8210         // @method bringToFront(): this
8211         // Brings the layer to the top of all path layers.
8212         bringToFront: function () {
8213                 if (this._renderer) {
8214                         this._renderer._bringToFront(this);
8215                 }
8216                 return this;
8217         },
8218
8219         // @method bringToBack(): this
8220         // Brings the layer to the bottom of all path layers.
8221         bringToBack: function () {
8222                 if (this._renderer) {
8223                         this._renderer._bringToBack(this);
8224                 }
8225                 return this;
8226         },
8227
8228         getElement: function () {
8229                 return this._path;
8230         },
8231
8232         _reset: function () {
8233                 // defined in child classes
8234                 this._project();
8235                 this._update();
8236         },
8237
8238         _clickTolerance: function () {
8239                 // used when doing hit detection for Canvas layers
8240                 return (this.options.stroke ? this.options.weight / 2 : 0) +
8241                   (this._renderer.options.tolerance || 0);
8242         }
8243   });
8244
8245   /*
8246    * @class CircleMarker
8247    * @aka L.CircleMarker
8248    * @inherits Path
8249    *
8250    * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8251    */
8252
8253   var CircleMarker = Path.extend({
8254
8255         // @section
8256         // @aka CircleMarker options
8257         options: {
8258                 fill: true,
8259
8260                 // @option radius: Number = 10
8261                 // Radius of the circle marker, in pixels
8262                 radius: 10
8263         },
8264
8265         initialize: function (latlng, options) {
8266                 setOptions(this, options);
8267                 this._latlng = toLatLng(latlng);
8268                 this._radius = this.options.radius;
8269         },
8270
8271         // @method setLatLng(latLng: LatLng): this
8272         // Sets the position of a circle marker to a new location.
8273         setLatLng: function (latlng) {
8274                 var oldLatLng = this._latlng;
8275                 this._latlng = toLatLng(latlng);
8276                 this.redraw();
8277
8278                 // @event move: Event
8279                 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
8280                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
8281         },
8282
8283         // @method getLatLng(): LatLng
8284         // Returns the current geographical position of the circle marker
8285         getLatLng: function () {
8286                 return this._latlng;
8287         },
8288
8289         // @method setRadius(radius: Number): this
8290         // Sets the radius of a circle marker. Units are in pixels.
8291         setRadius: function (radius) {
8292                 this.options.radius = this._radius = radius;
8293                 return this.redraw();
8294         },
8295
8296         // @method getRadius(): Number
8297         // Returns the current radius of the circle
8298         getRadius: function () {
8299                 return this._radius;
8300         },
8301
8302         setStyle : function (options) {
8303                 var radius = options && options.radius || this._radius;
8304                 Path.prototype.setStyle.call(this, options);
8305                 this.setRadius(radius);
8306                 return this;
8307         },
8308
8309         _project: function () {
8310                 this._point = this._map.latLngToLayerPoint(this._latlng);
8311                 this._updateBounds();
8312         },
8313
8314         _updateBounds: function () {
8315                 var r = this._radius,
8316                     r2 = this._radiusY || r,
8317                     w = this._clickTolerance(),
8318                     p = [r + w, r2 + w];
8319                 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
8320         },
8321
8322         _update: function () {
8323                 if (this._map) {
8324                         this._updatePath();
8325                 }
8326         },
8327
8328         _updatePath: function () {
8329                 this._renderer._updateCircle(this);
8330         },
8331
8332         _empty: function () {
8333                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8334         },
8335
8336         // Needed by the `Canvas` renderer for interactivity
8337         _containsPoint: function (p) {
8338                 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8339         }
8340   });
8341
8342
8343   // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8344   // Instantiates a circle marker object given a geographical point, and an optional options object.
8345   function circleMarker(latlng, options) {
8346         return new CircleMarker(latlng, options);
8347   }
8348
8349   /*
8350    * @class Circle
8351    * @aka L.Circle
8352    * @inherits CircleMarker
8353    *
8354    * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8355    *
8356    * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8357    *
8358    * @example
8359    *
8360    * ```js
8361    * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8362    * ```
8363    */
8364
8365   var Circle = CircleMarker.extend({
8366
8367         initialize: function (latlng, options, legacyOptions) {
8368                 if (typeof options === 'number') {
8369                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8370                         options = extend({}, legacyOptions, {radius: options});
8371                 }
8372                 setOptions(this, options);
8373                 this._latlng = toLatLng(latlng);
8374
8375                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8376
8377                 // @section
8378                 // @aka Circle options
8379                 // @option radius: Number; Radius of the circle, in meters.
8380                 this._mRadius = this.options.radius;
8381         },
8382
8383         // @method setRadius(radius: Number): this
8384         // Sets the radius of a circle. Units are in meters.
8385         setRadius: function (radius) {
8386                 this._mRadius = radius;
8387                 return this.redraw();
8388         },
8389
8390         // @method getRadius(): Number
8391         // Returns the current radius of a circle. Units are in meters.
8392         getRadius: function () {
8393                 return this._mRadius;
8394         },
8395
8396         // @method getBounds(): LatLngBounds
8397         // Returns the `LatLngBounds` of the path.
8398         getBounds: function () {
8399                 var half = [this._radius, this._radiusY || this._radius];
8400
8401                 return new LatLngBounds(
8402                         this._map.layerPointToLatLng(this._point.subtract(half)),
8403                         this._map.layerPointToLatLng(this._point.add(half)));
8404         },
8405
8406         setStyle: Path.prototype.setStyle,
8407
8408         _project: function () {
8409
8410                 var lng = this._latlng.lng,
8411                     lat = this._latlng.lat,
8412                     map = this._map,
8413                     crs = map.options.crs;
8414
8415                 if (crs.distance === Earth.distance) {
8416                         var d = Math.PI / 180,
8417                             latR = (this._mRadius / Earth.R) / d,
8418                             top = map.project([lat + latR, lng]),
8419                             bottom = map.project([lat - latR, lng]),
8420                             p = top.add(bottom).divideBy(2),
8421                             lat2 = map.unproject(p).lat,
8422                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8423                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8424
8425                         if (isNaN(lngR) || lngR === 0) {
8426                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8427                         }
8428
8429                         this._point = p.subtract(map.getPixelOrigin());
8430                         this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8431                         this._radiusY = p.y - top.y;
8432
8433                 } else {
8434                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8435
8436                         this._point = map.latLngToLayerPoint(this._latlng);
8437                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8438                 }
8439
8440                 this._updateBounds();
8441         }
8442   });
8443
8444   // @factory L.circle(latlng: LatLng, options?: Circle options)
8445   // Instantiates a circle object given a geographical point, and an options object
8446   // which contains the circle radius.
8447   // @alternative
8448   // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8449   // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8450   // Do not use in new applications or plugins.
8451   function circle(latlng, options, legacyOptions) {
8452         return new Circle(latlng, options, legacyOptions);
8453   }
8454
8455   /*
8456    * @class Polyline
8457    * @aka L.Polyline
8458    * @inherits Path
8459    *
8460    * A class for drawing polyline overlays on a map. Extends `Path`.
8461    *
8462    * @example
8463    *
8464    * ```js
8465    * // create a red polyline from an array of LatLng points
8466    * var latlngs = [
8467    *    [45.51, -122.68],
8468    *    [37.77, -122.43],
8469    *    [34.04, -118.2]
8470    * ];
8471    *
8472    * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8473    *
8474    * // zoom the map to the polyline
8475    * map.fitBounds(polyline.getBounds());
8476    * ```
8477    *
8478    * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8479    *
8480    * ```js
8481    * // create a red polyline from an array of arrays of LatLng points
8482    * var latlngs = [
8483    *    [[45.51, -122.68],
8484    *     [37.77, -122.43],
8485    *     [34.04, -118.2]],
8486    *    [[40.78, -73.91],
8487    *     [41.83, -87.62],
8488    *     [32.76, -96.72]]
8489    * ];
8490    * ```
8491    */
8492
8493
8494   var Polyline = Path.extend({
8495
8496         // @section
8497         // @aka Polyline options
8498         options: {
8499                 // @option smoothFactor: Number = 1.0
8500                 // How much to simplify the polyline on each zoom level. More means
8501                 // better performance and smoother look, and less means more accurate representation.
8502                 smoothFactor: 1.0,
8503
8504                 // @option noClip: Boolean = false
8505                 // Disable polyline clipping.
8506                 noClip: false
8507         },
8508
8509         initialize: function (latlngs, options) {
8510                 setOptions(this, options);
8511                 this._setLatLngs(latlngs);
8512         },
8513
8514         // @method getLatLngs(): LatLng[]
8515         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8516         getLatLngs: function () {
8517                 return this._latlngs;
8518         },
8519
8520         // @method setLatLngs(latlngs: LatLng[]): this
8521         // Replaces all the points in the polyline with the given array of geographical points.
8522         setLatLngs: function (latlngs) {
8523                 this._setLatLngs(latlngs);
8524                 return this.redraw();
8525         },
8526
8527         // @method isEmpty(): Boolean
8528         // Returns `true` if the Polyline has no LatLngs.
8529         isEmpty: function () {
8530                 return !this._latlngs.length;
8531         },
8532
8533         // @method closestLayerPoint(p: Point): Point
8534         // Returns the point closest to `p` on the Polyline.
8535         closestLayerPoint: function (p) {
8536                 var minDistance = Infinity,
8537                     minPoint = null,
8538                     closest = _sqClosestPointOnSegment,
8539                     p1, p2;
8540
8541                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8542                         var points = this._parts[j];
8543
8544                         for (var i = 1, len = points.length; i < len; i++) {
8545                                 p1 = points[i - 1];
8546                                 p2 = points[i];
8547
8548                                 var sqDist = closest(p, p1, p2, true);
8549
8550                                 if (sqDist < minDistance) {
8551                                         minDistance = sqDist;
8552                                         minPoint = closest(p, p1, p2);
8553                                 }
8554                         }
8555                 }
8556                 if (minPoint) {
8557                         minPoint.distance = Math.sqrt(minDistance);
8558                 }
8559                 return minPoint;
8560         },
8561
8562         // @method getCenter(): LatLng
8563         // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
8564         getCenter: function () {
8565                 // throws error when not yet added to map as this center calculation requires projected coordinates
8566                 if (!this._map) {
8567                         throw new Error('Must add layer to map before using getCenter()');
8568                 }
8569                 return polylineCenter(this._defaultShape(), this._map.options.crs);
8570         },
8571
8572         // @method getBounds(): LatLngBounds
8573         // Returns the `LatLngBounds` of the path.
8574         getBounds: function () {
8575                 return this._bounds;
8576         },
8577
8578         // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8579         // Adds a given point to the polyline. By default, adds to the first ring of
8580         // the polyline in case of a multi-polyline, but can be overridden by passing
8581         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8582         addLatLng: function (latlng, latlngs) {
8583                 latlngs = latlngs || this._defaultShape();
8584                 latlng = toLatLng(latlng);
8585                 latlngs.push(latlng);
8586                 this._bounds.extend(latlng);
8587                 return this.redraw();
8588         },
8589
8590         _setLatLngs: function (latlngs) {
8591                 this._bounds = new LatLngBounds();
8592                 this._latlngs = this._convertLatLngs(latlngs);
8593         },
8594
8595         _defaultShape: function () {
8596                 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8597         },
8598
8599         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8600         _convertLatLngs: function (latlngs) {
8601                 var result = [],
8602                     flat = isFlat(latlngs);
8603
8604                 for (var i = 0, len = latlngs.length; i < len; i++) {
8605                         if (flat) {
8606                                 result[i] = toLatLng(latlngs[i]);
8607                                 this._bounds.extend(result[i]);
8608                         } else {
8609                                 result[i] = this._convertLatLngs(latlngs[i]);
8610                         }
8611                 }
8612
8613                 return result;
8614         },
8615
8616         _project: function () {
8617                 var pxBounds = new Bounds();
8618                 this._rings = [];
8619                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8620
8621                 if (this._bounds.isValid() && pxBounds.isValid()) {
8622                         this._rawPxBounds = pxBounds;
8623                         this._updateBounds();
8624                 }
8625         },
8626
8627         _updateBounds: function () {
8628                 var w = this._clickTolerance(),
8629                     p = new Point(w, w);
8630
8631                 if (!this._rawPxBounds) {
8632                         return;
8633                 }
8634
8635                 this._pxBounds = new Bounds([
8636                         this._rawPxBounds.min.subtract(p),
8637                         this._rawPxBounds.max.add(p)
8638                 ]);
8639         },
8640
8641         // recursively turns latlngs into a set of rings with projected coordinates
8642         _projectLatlngs: function (latlngs, result, projectedBounds) {
8643                 var flat = latlngs[0] instanceof LatLng,
8644                     len = latlngs.length,
8645                     i, ring;
8646
8647                 if (flat) {
8648                         ring = [];
8649                         for (i = 0; i < len; i++) {
8650                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8651                                 projectedBounds.extend(ring[i]);
8652                         }
8653                         result.push(ring);
8654                 } else {
8655                         for (i = 0; i < len; i++) {
8656                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8657                         }
8658                 }
8659         },
8660
8661         // clip polyline by renderer bounds so that we have less to render for performance
8662         _clipPoints: function () {
8663                 var bounds = this._renderer._bounds;
8664
8665                 this._parts = [];
8666                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8667                         return;
8668                 }
8669
8670                 if (this.options.noClip) {
8671                         this._parts = this._rings;
8672                         return;
8673                 }
8674
8675                 var parts = this._parts,
8676                     i, j, k, len, len2, segment, points;
8677
8678                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8679                         points = this._rings[i];
8680
8681                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8682                                 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8683
8684                                 if (!segment) { continue; }
8685
8686                                 parts[k] = parts[k] || [];
8687                                 parts[k].push(segment[0]);
8688
8689                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8690                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8691                                         parts[k].push(segment[1]);
8692                                         k++;
8693                                 }
8694                         }
8695                 }
8696         },
8697
8698         // simplify each clipped part of the polyline for performance
8699         _simplifyPoints: function () {
8700                 var parts = this._parts,
8701                     tolerance = this.options.smoothFactor;
8702
8703                 for (var i = 0, len = parts.length; i < len; i++) {
8704                         parts[i] = simplify(parts[i], tolerance);
8705                 }
8706         },
8707
8708         _update: function () {
8709                 if (!this._map) { return; }
8710
8711                 this._clipPoints();
8712                 this._simplifyPoints();
8713                 this._updatePath();
8714         },
8715
8716         _updatePath: function () {
8717                 this._renderer._updatePoly(this);
8718         },
8719
8720         // Needed by the `Canvas` renderer for interactivity
8721         _containsPoint: function (p, closed) {
8722                 var i, j, k, len, len2, part,
8723                     w = this._clickTolerance();
8724
8725                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8726
8727                 // hit detection for polylines
8728                 for (i = 0, len = this._parts.length; i < len; i++) {
8729                         part = this._parts[i];
8730
8731                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8732                                 if (!closed && (j === 0)) { continue; }
8733
8734                                 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8735                                         return true;
8736                                 }
8737                         }
8738                 }
8739                 return false;
8740         }
8741   });
8742
8743   // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8744   // Instantiates a polyline object given an array of geographical points and
8745   // optionally an options object. You can create a `Polyline` object with
8746   // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8747   // of geographic points.
8748   function polyline(latlngs, options) {
8749         return new Polyline(latlngs, options);
8750   }
8751
8752   // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8753   Polyline._flat = _flat;
8754
8755   /*
8756    * @class Polygon
8757    * @aka L.Polygon
8758    * @inherits Polyline
8759    *
8760    * A class for drawing polygon overlays on a map. Extends `Polyline`.
8761    *
8762    * 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.
8763    *
8764    *
8765    * @example
8766    *
8767    * ```js
8768    * // create a red polygon from an array of LatLng points
8769    * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8770    *
8771    * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8772    *
8773    * // zoom the map to the polygon
8774    * map.fitBounds(polygon.getBounds());
8775    * ```
8776    *
8777    * 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:
8778    *
8779    * ```js
8780    * var latlngs = [
8781    *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8782    *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8783    * ];
8784    * ```
8785    *
8786    * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8787    *
8788    * ```js
8789    * var latlngs = [
8790    *   [ // first polygon
8791    *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8792    *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8793    *   ],
8794    *   [ // second polygon
8795    *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8796    *   ]
8797    * ];
8798    * ```
8799    */
8800
8801   var Polygon = Polyline.extend({
8802
8803         options: {
8804                 fill: true
8805         },
8806
8807         isEmpty: function () {
8808                 return !this._latlngs.length || !this._latlngs[0].length;
8809         },
8810
8811         // @method getCenter(): LatLng
8812         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
8813         getCenter: function () {
8814                 // throws error when not yet added to map as this center calculation requires projected coordinates
8815                 if (!this._map) {
8816                         throw new Error('Must add layer to map before using getCenter()');
8817                 }
8818                 return polygonCenter(this._defaultShape(), this._map.options.crs);
8819         },
8820
8821         _convertLatLngs: function (latlngs) {
8822                 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8823                     len = result.length;
8824
8825                 // remove last point if it equals first one
8826                 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8827                         result.pop();
8828                 }
8829                 return result;
8830         },
8831
8832         _setLatLngs: function (latlngs) {
8833                 Polyline.prototype._setLatLngs.call(this, latlngs);
8834                 if (isFlat(this._latlngs)) {
8835                         this._latlngs = [this._latlngs];
8836                 }
8837         },
8838
8839         _defaultShape: function () {
8840                 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8841         },
8842
8843         _clipPoints: function () {
8844                 // polygons need a different clipping algorithm so we redefine that
8845
8846                 var bounds = this._renderer._bounds,
8847                     w = this.options.weight,
8848                     p = new Point(w, w);
8849
8850                 // increase clip padding by stroke width to avoid stroke on clip edges
8851                 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8852
8853                 this._parts = [];
8854                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8855                         return;
8856                 }
8857
8858                 if (this.options.noClip) {
8859                         this._parts = this._rings;
8860                         return;
8861                 }
8862
8863                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8864                         clipped = clipPolygon(this._rings[i], bounds, true);
8865                         if (clipped.length) {
8866                                 this._parts.push(clipped);
8867                         }
8868                 }
8869         },
8870
8871         _updatePath: function () {
8872                 this._renderer._updatePoly(this, true);
8873         },
8874
8875         // Needed by the `Canvas` renderer for interactivity
8876         _containsPoint: function (p) {
8877                 var inside = false,
8878                     part, p1, p2, i, j, k, len, len2;
8879
8880                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8881
8882                 // ray casting algorithm for detecting if point is in polygon
8883                 for (i = 0, len = this._parts.length; i < len; i++) {
8884                         part = this._parts[i];
8885
8886                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8887                                 p1 = part[j];
8888                                 p2 = part[k];
8889
8890                                 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)) {
8891                                         inside = !inside;
8892                                 }
8893                         }
8894                 }
8895
8896                 // also check if it's on polygon stroke
8897                 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8898         }
8899
8900   });
8901
8902
8903   // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8904   function polygon(latlngs, options) {
8905         return new Polygon(latlngs, options);
8906   }
8907
8908   /*\r
8909    * @class GeoJSON\r
8910    * @aka L.GeoJSON\r
8911    * @inherits FeatureGroup\r
8912    *\r
8913    * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse\r
8914    * GeoJSON data and display it on the map. Extends `FeatureGroup`.\r
8915    *\r
8916    * @example\r
8917    *\r
8918    * ```js\r
8919    * L.geoJSON(data, {\r
8920    *    style: function (feature) {\r
8921    *            return {color: feature.properties.color};\r
8922    *    }\r
8923    * }).bindPopup(function (layer) {\r
8924    *    return layer.feature.properties.description;\r
8925    * }).addTo(map);\r
8926    * ```\r
8927    */\r
8928 \r
8929   var GeoJSON = FeatureGroup.extend({\r
8930 \r
8931         /* @section\r
8932          * @aka GeoJSON options\r
8933          *\r
8934          * @option pointToLayer: Function = *\r
8935          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally\r
8936          * called when data is added, passing the GeoJSON point feature and its `LatLng`.\r
8937          * The default is to spawn a default `Marker`:\r
8938          * ```js\r
8939          * function(geoJsonPoint, latlng) {\r
8940          *      return L.marker(latlng);\r
8941          * }\r
8942          * ```\r
8943          *\r
8944          * @option style: Function = *\r
8945          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,\r
8946          * called internally when data is added.\r
8947          * The default value is to not override any defaults:\r
8948          * ```js\r
8949          * function (geoJsonFeature) {\r
8950          *      return {}\r
8951          * }\r
8952          * ```\r
8953          *\r
8954          * @option onEachFeature: Function = *\r
8955          * A `Function` that will be called once for each created `Feature`, after it has\r
8956          * been created and styled. Useful for attaching events and popups to features.\r
8957          * The default is to do nothing with the newly created layers:\r
8958          * ```js\r
8959          * function (feature, layer) {}\r
8960          * ```\r
8961          *\r
8962          * @option filter: Function = *\r
8963          * A `Function` that will be used to decide whether to include a feature or not.\r
8964          * The default is to include all features:\r
8965          * ```js\r
8966          * function (geoJsonFeature) {\r
8967          *      return true;\r
8968          * }\r
8969          * ```\r
8970          * Note: dynamically changing the `filter` option will have effect only on newly\r
8971          * added data. It will _not_ re-evaluate already included features.\r
8972          *\r
8973          * @option coordsToLatLng: Function = *\r
8974          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.\r
8975          * The default is the `coordsToLatLng` static method.\r
8976          *\r
8977          * @option markersInheritOptions: Boolean = false\r
8978          * Whether default Markers for "Point" type Features inherit from group options.\r
8979          */\r
8980 \r
8981         initialize: function (geojson, options) {\r
8982                 setOptions(this, options);\r
8983 \r
8984                 this._layers = {};\r
8985 \r
8986                 if (geojson) {\r
8987                         this.addData(geojson);\r
8988                 }\r
8989         },\r
8990 \r
8991         // @method addData( <GeoJSON> data ): this\r
8992         // Adds a GeoJSON object to the layer.\r
8993         addData: function (geojson) {\r
8994                 var features = isArray(geojson) ? geojson : geojson.features,\r
8995                     i, len, feature;\r
8996 \r
8997                 if (features) {\r
8998                         for (i = 0, len = features.length; i < len; i++) {\r
8999                                 // only add this if geometry or geometries are set and not null\r
9000                                 feature = features[i];\r
9001                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {\r
9002                                         this.addData(feature);\r
9003                                 }\r
9004                         }\r
9005                         return this;\r
9006                 }\r
9007 \r
9008                 var options = this.options;\r
9009 \r
9010                 if (options.filter && !options.filter(geojson)) { return this; }\r
9011 \r
9012                 var layer = geometryToLayer(geojson, options);\r
9013                 if (!layer) {\r
9014                         return this;\r
9015                 }\r
9016                 layer.feature = asFeature(geojson);\r
9017 \r
9018                 layer.defaultOptions = layer.options;\r
9019                 this.resetStyle(layer);\r
9020 \r
9021                 if (options.onEachFeature) {\r
9022                         options.onEachFeature(geojson, layer);\r
9023                 }\r
9024 \r
9025                 return this.addLayer(layer);\r
9026         },\r
9027 \r
9028         // @method resetStyle( <Path> layer? ): this\r
9029         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.\r
9030         // If `layer` is omitted, the style of all features in the current layer is reset.\r
9031         resetStyle: function (layer) {\r
9032                 if (layer === undefined) {\r
9033                         return this.eachLayer(this.resetStyle, this);\r
9034                 }\r
9035                 // reset any custom styles\r
9036                 layer.options = extend({}, layer.defaultOptions);\r
9037                 this._setLayerStyle(layer, this.options.style);\r
9038                 return this;\r
9039         },\r
9040 \r
9041         // @method setStyle( <Function> style ): this\r
9042         // Changes styles of GeoJSON vector layers with the given style function.\r
9043         setStyle: function (style) {\r
9044                 return this.eachLayer(function (layer) {\r
9045                         this._setLayerStyle(layer, style);\r
9046                 }, this);\r
9047         },\r
9048 \r
9049         _setLayerStyle: function (layer, style) {\r
9050                 if (layer.setStyle) {\r
9051                         if (typeof style === 'function') {\r
9052                                 style = style(layer.feature);\r
9053                         }\r
9054                         layer.setStyle(style);\r
9055                 }\r
9056         }\r
9057   });\r
9058 \r
9059   // @section\r
9060   // There are several static functions which can be called without instantiating L.GeoJSON:\r
9061 \r
9062   // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer\r
9063   // Creates a `Layer` from a given GeoJSON feature. Can use a custom\r
9064   // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)\r
9065   // functions if provided as options.\r
9066   function geometryToLayer(geojson, options) {\r
9067 \r
9068         var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,\r
9069             coords = geometry ? geometry.coordinates : null,\r
9070             layers = [],\r
9071             pointToLayer = options && options.pointToLayer,\r
9072             _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,\r
9073             latlng, latlngs, i, len;\r
9074 \r
9075         if (!coords && !geometry) {\r
9076                 return null;\r
9077         }\r
9078 \r
9079         switch (geometry.type) {\r
9080         case 'Point':\r
9081                 latlng = _coordsToLatLng(coords);\r
9082                 return _pointToLayer(pointToLayer, geojson, latlng, options);\r
9083 \r
9084         case 'MultiPoint':\r
9085                 for (i = 0, len = coords.length; i < len; i++) {\r
9086                         latlng = _coordsToLatLng(coords[i]);\r
9087                         layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));\r
9088                 }\r
9089                 return new FeatureGroup(layers);\r
9090 \r
9091         case 'LineString':\r
9092         case 'MultiLineString':\r
9093                 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);\r
9094                 return new Polyline(latlngs, options);\r
9095 \r
9096         case 'Polygon':\r
9097         case 'MultiPolygon':\r
9098                 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);\r
9099                 return new Polygon(latlngs, options);\r
9100 \r
9101         case 'GeometryCollection':\r
9102                 for (i = 0, len = geometry.geometries.length; i < len; i++) {\r
9103                         var geoLayer = geometryToLayer({\r
9104                                 geometry: geometry.geometries[i],\r
9105                                 type: 'Feature',\r
9106                                 properties: geojson.properties\r
9107                         }, options);\r
9108 \r
9109                         if (geoLayer) {\r
9110                                 layers.push(geoLayer);\r
9111                         }\r
9112                 }\r
9113                 return new FeatureGroup(layers);\r
9114 \r
9115         case 'FeatureCollection':\r
9116                 for (i = 0, len = geometry.features.length; i < len; i++) {\r
9117                         var featureLayer = geometryToLayer(geometry.features[i], options);\r
9118 \r
9119                         if (featureLayer) {\r
9120                                 layers.push(featureLayer);\r
9121                         }\r
9122                 }\r
9123                 return new FeatureGroup(layers);\r
9124 \r
9125         default:\r
9126                 throw new Error('Invalid GeoJSON object.');\r
9127         }\r
9128   }\r
9129 \r
9130   function _pointToLayer(pointToLayerFn, geojson, latlng, options) {\r
9131         return pointToLayerFn ?\r
9132                 pointToLayerFn(geojson, latlng) :\r
9133                 new Marker(latlng, options && options.markersInheritOptions && options);\r
9134   }\r
9135 \r
9136   // @function coordsToLatLng(coords: Array): LatLng\r
9137   // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)\r
9138   // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.\r
9139   function coordsToLatLng(coords) {\r
9140         return new LatLng(coords[1], coords[0], coords[2]);\r
9141   }\r
9142 \r
9143   // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array\r
9144   // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.\r
9145   // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).\r
9146   // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.\r
9147   function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {\r
9148         var latlngs = [];\r
9149 \r
9150         for (var i = 0, len = coords.length, latlng; i < len; i++) {\r
9151                 latlng = levelsDeep ?\r
9152                         coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :\r
9153                         (_coordsToLatLng || coordsToLatLng)(coords[i]);\r
9154 \r
9155                 latlngs.push(latlng);\r
9156         }\r
9157 \r
9158         return latlngs;\r
9159   }\r
9160 \r
9161   // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array\r
9162   // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)\r
9163   // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.\r
9164   function latLngToCoords(latlng, precision) {\r
9165         latlng = toLatLng(latlng);\r
9166         return latlng.alt !== undefined ?\r
9167                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :\r
9168                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];\r
9169   }\r
9170 \r
9171   // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array\r
9172   // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)\r
9173   // `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.\r
9174   // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.\r
9175   function latLngsToCoords(latlngs, levelsDeep, closed, precision) {\r
9176         var coords = [];\r
9177 \r
9178         for (var i = 0, len = latlngs.length; i < len; i++) {\r
9179                 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion\r
9180                 coords.push(levelsDeep ?\r
9181                         latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :\r
9182                         latLngToCoords(latlngs[i], precision));\r
9183         }\r
9184 \r
9185         if (!levelsDeep && closed && coords.length > 0) {\r
9186                 coords.push(coords[0].slice());\r
9187         }\r
9188 \r
9189         return coords;\r
9190   }\r
9191 \r
9192   function getFeature(layer, newGeometry) {\r
9193         return layer.feature ?\r
9194                 extend({}, layer.feature, {geometry: newGeometry}) :\r
9195                 asFeature(newGeometry);\r
9196   }\r
9197 \r
9198   // @function asFeature(geojson: Object): Object\r
9199   // Normalize GeoJSON geometries/features into GeoJSON features.\r
9200   function asFeature(geojson) {\r
9201         if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {\r
9202                 return geojson;\r
9203         }\r
9204 \r
9205         return {\r
9206                 type: 'Feature',\r
9207                 properties: {},\r
9208                 geometry: geojson\r
9209         };\r
9210   }\r
9211 \r
9212   var PointToGeoJSON = {\r
9213         toGeoJSON: function (precision) {\r
9214                 return getFeature(this, {\r
9215                         type: 'Point',\r
9216                         coordinates: latLngToCoords(this.getLatLng(), precision)\r
9217                 });\r
9218         }\r
9219   };\r
9220 \r
9221   // @namespace Marker\r
9222   // @section Other methods\r
9223   // @method toGeoJSON(precision?: Number|false): Object\r
9224   // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.\r
9225   // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).\r
9226   Marker.include(PointToGeoJSON);\r
9227 \r
9228   // @namespace CircleMarker\r
9229   // @method toGeoJSON(precision?: Number|false): Object\r
9230   // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.\r
9231   // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).\r
9232   Circle.include(PointToGeoJSON);\r
9233   CircleMarker.include(PointToGeoJSON);\r
9234 \r
9235 \r
9236   // @namespace Polyline\r
9237   // @method toGeoJSON(precision?: Number|false): Object\r
9238   // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.\r
9239   // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).\r
9240   Polyline.include({\r
9241         toGeoJSON: function (precision) {\r
9242                 var multi = !isFlat(this._latlngs);\r
9243 \r
9244                 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);\r
9245 \r
9246                 return getFeature(this, {\r
9247                         type: (multi ? 'Multi' : '') + 'LineString',\r
9248                         coordinates: coords\r
9249                 });\r
9250         }\r
9251   });\r
9252 \r
9253   // @namespace Polygon\r
9254   // @method toGeoJSON(precision?: Number|false): Object\r
9255   // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.\r
9256   // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).\r
9257   Polygon.include({\r
9258         toGeoJSON: function (precision) {\r
9259                 var holes = !isFlat(this._latlngs),\r
9260                     multi = holes && !isFlat(this._latlngs[0]);\r
9261 \r
9262                 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);\r
9263 \r
9264                 if (!holes) {\r
9265                         coords = [coords];\r
9266                 }\r
9267 \r
9268                 return getFeature(this, {\r
9269                         type: (multi ? 'Multi' : '') + 'Polygon',\r
9270                         coordinates: coords\r
9271                 });\r
9272         }\r
9273   });\r
9274 \r
9275 \r
9276   // @namespace LayerGroup\r
9277   LayerGroup.include({\r
9278         toMultiPoint: function (precision) {\r
9279                 var coords = [];\r
9280 \r
9281                 this.eachLayer(function (layer) {\r
9282                         coords.push(layer.toGeoJSON(precision).geometry.coordinates);\r
9283                 });\r
9284 \r
9285                 return getFeature(this, {\r
9286                         type: 'MultiPoint',\r
9287                         coordinates: coords\r
9288                 });\r
9289         },\r
9290 \r
9291         // @method toGeoJSON(precision?: Number|false): Object\r
9292         // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.\r
9293         // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).\r
9294         toGeoJSON: function (precision) {\r
9295 \r
9296                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;\r
9297 \r
9298                 if (type === 'MultiPoint') {\r
9299                         return this.toMultiPoint(precision);\r
9300                 }\r
9301 \r
9302                 var isGeometryCollection = type === 'GeometryCollection',\r
9303                     jsons = [];\r
9304 \r
9305                 this.eachLayer(function (layer) {\r
9306                         if (layer.toGeoJSON) {\r
9307                                 var json = layer.toGeoJSON(precision);\r
9308                                 if (isGeometryCollection) {\r
9309                                         jsons.push(json.geometry);\r
9310                                 } else {\r
9311                                         var feature = asFeature(json);\r
9312                                         // Squash nested feature collections\r
9313                                         if (feature.type === 'FeatureCollection') {\r
9314                                                 jsons.push.apply(jsons, feature.features);\r
9315                                         } else {\r
9316                                                 jsons.push(feature);\r
9317                                         }\r
9318                                 }\r
9319                         }\r
9320                 });\r
9321 \r
9322                 if (isGeometryCollection) {\r
9323                         return getFeature(this, {\r
9324                                 geometries: jsons,\r
9325                                 type: 'GeometryCollection'\r
9326                         });\r
9327                 }\r
9328 \r
9329                 return {\r
9330                         type: 'FeatureCollection',\r
9331                         features: jsons\r
9332                 };\r
9333         }\r
9334   });\r
9335 \r
9336   // @namespace GeoJSON\r
9337   // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)\r
9338   // Creates a GeoJSON layer. Optionally accepts an object in\r
9339   // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map\r
9340   // (you can alternatively add it later with `addData` method) and an `options` object.\r
9341   function geoJSON(geojson, options) {\r
9342         return new GeoJSON(geojson, options);\r
9343   }\r
9344 \r
9345   // Backward compatibility.\r
9346   var geoJson = geoJSON;
9347
9348   /*\r
9349    * @class ImageOverlay\r
9350    * @aka L.ImageOverlay\r
9351    * @inherits Interactive layer\r
9352    *\r
9353    * Used to load and display a single image over specific bounds of the map. Extends `Layer`.\r
9354    *\r
9355    * @example\r
9356    *\r
9357    * ```js\r
9358    * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',\r
9359    *    imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];\r
9360    * L.imageOverlay(imageUrl, imageBounds).addTo(map);\r
9361    * ```\r
9362    */\r
9363 \r
9364   var ImageOverlay = Layer.extend({\r
9365 \r
9366         // @section\r
9367         // @aka ImageOverlay options\r
9368         options: {\r
9369                 // @option opacity: Number = 1.0\r
9370                 // The opacity of the image overlay.\r
9371                 opacity: 1,\r
9372 \r
9373                 // @option alt: String = ''\r
9374                 // Text for the `alt` attribute of the image (useful for accessibility).\r
9375                 alt: '',\r
9376 \r
9377                 // @option interactive: Boolean = false\r
9378                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.\r
9379                 interactive: false,\r
9380 \r
9381                 // @option crossOrigin: Boolean|String = false\r
9382                 // Whether the crossOrigin attribute will be added to the image.\r
9383                 // 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.\r
9384                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.\r
9385                 crossOrigin: false,\r
9386 \r
9387                 // @option errorOverlayUrl: String = ''\r
9388                 // URL to the overlay image to show in place of the overlay that failed to load.\r
9389                 errorOverlayUrl: '',\r
9390 \r
9391                 // @option zIndex: Number = 1\r
9392                 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.\r
9393                 zIndex: 1,\r
9394 \r
9395                 // @option className: String = ''\r
9396                 // A custom class name to assign to the image. Empty by default.\r
9397                 className: ''\r
9398         },\r
9399 \r
9400         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)\r
9401                 this._url = url;\r
9402                 this._bounds = toLatLngBounds(bounds);\r
9403 \r
9404                 setOptions(this, options);\r
9405         },\r
9406 \r
9407         onAdd: function () {\r
9408                 if (!this._image) {\r
9409                         this._initImage();\r
9410 \r
9411                         if (this.options.opacity < 1) {\r
9412                                 this._updateOpacity();\r
9413                         }\r
9414                 }\r
9415 \r
9416                 if (this.options.interactive) {\r
9417                         addClass(this._image, 'leaflet-interactive');\r
9418                         this.addInteractiveTarget(this._image);\r
9419                 }\r
9420 \r
9421                 this.getPane().appendChild(this._image);\r
9422                 this._reset();\r
9423         },\r
9424 \r
9425         onRemove: function () {\r
9426                 remove(this._image);\r
9427                 if (this.options.interactive) {\r
9428                         this.removeInteractiveTarget(this._image);\r
9429                 }\r
9430         },\r
9431 \r
9432         // @method setOpacity(opacity: Number): this\r
9433         // Sets the opacity of the overlay.\r
9434         setOpacity: function (opacity) {\r
9435                 this.options.opacity = opacity;\r
9436 \r
9437                 if (this._image) {\r
9438                         this._updateOpacity();\r
9439                 }\r
9440                 return this;\r
9441         },\r
9442 \r
9443         setStyle: function (styleOpts) {\r
9444                 if (styleOpts.opacity) {\r
9445                         this.setOpacity(styleOpts.opacity);\r
9446                 }\r
9447                 return this;\r
9448         },\r
9449 \r
9450         // @method bringToFront(): this\r
9451         // Brings the layer to the top of all overlays.\r
9452         bringToFront: function () {\r
9453                 if (this._map) {\r
9454                         toFront(this._image);\r
9455                 }\r
9456                 return this;\r
9457         },\r
9458 \r
9459         // @method bringToBack(): this\r
9460         // Brings the layer to the bottom of all overlays.\r
9461         bringToBack: function () {\r
9462                 if (this._map) {\r
9463                         toBack(this._image);\r
9464                 }\r
9465                 return this;\r
9466         },\r
9467 \r
9468         // @method setUrl(url: String): this\r
9469         // Changes the URL of the image.\r
9470         setUrl: function (url) {\r
9471                 this._url = url;\r
9472 \r
9473                 if (this._image) {\r
9474                         this._image.src = url;\r
9475                 }\r
9476                 return this;\r
9477         },\r
9478 \r
9479         // @method setBounds(bounds: LatLngBounds): this\r
9480         // Update the bounds that this ImageOverlay covers\r
9481         setBounds: function (bounds) {\r
9482                 this._bounds = toLatLngBounds(bounds);\r
9483 \r
9484                 if (this._map) {\r
9485                         this._reset();\r
9486                 }\r
9487                 return this;\r
9488         },\r
9489 \r
9490         getEvents: function () {\r
9491                 var events = {\r
9492                         zoom: this._reset,\r
9493                         viewreset: this._reset\r
9494                 };\r
9495 \r
9496                 if (this._zoomAnimated) {\r
9497                         events.zoomanim = this._animateZoom;\r
9498                 }\r
9499 \r
9500                 return events;\r
9501         },\r
9502 \r
9503         // @method setZIndex(value: Number): this\r
9504         // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.\r
9505         setZIndex: function (value) {\r
9506                 this.options.zIndex = value;\r
9507                 this._updateZIndex();\r
9508                 return this;\r
9509         },\r
9510 \r
9511         // @method getBounds(): LatLngBounds\r
9512         // Get the bounds that this ImageOverlay covers\r
9513         getBounds: function () {\r
9514                 return this._bounds;\r
9515         },\r
9516 \r
9517         // @method getElement(): HTMLElement\r
9518         // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)\r
9519         // used by this overlay.\r
9520         getElement: function () {\r
9521                 return this._image;\r
9522         },\r
9523 \r
9524         _initImage: function () {\r
9525                 var wasElementSupplied = this._url.tagName === 'IMG';\r
9526                 var img = this._image = wasElementSupplied ? this._url : create$1('img');\r
9527 \r
9528                 addClass(img, 'leaflet-image-layer');\r
9529                 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }\r
9530                 if (this.options.className) { addClass(img, this.options.className); }\r
9531 \r
9532                 img.onselectstart = falseFn;\r
9533                 img.onmousemove = falseFn;\r
9534 \r
9535                 // @event load: Event\r
9536                 // Fired when the ImageOverlay layer has loaded its image\r
9537                 img.onload = bind(this.fire, this, 'load');\r
9538                 img.onerror = bind(this._overlayOnError, this, 'error');\r
9539 \r
9540                 if (this.options.crossOrigin || this.options.crossOrigin === '') {\r
9541                         img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;\r
9542                 }\r
9543 \r
9544                 if (this.options.zIndex) {\r
9545                         this._updateZIndex();\r
9546                 }\r
9547 \r
9548                 if (wasElementSupplied) {\r
9549                         this._url = img.src;\r
9550                         return;\r
9551                 }\r
9552 \r
9553                 img.src = this._url;\r
9554                 img.alt = this.options.alt;\r
9555         },\r
9556 \r
9557         _animateZoom: function (e) {\r
9558                 var scale = this._map.getZoomScale(e.zoom),\r
9559                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;\r
9560 \r
9561                 setTransform(this._image, offset, scale);\r
9562         },\r
9563 \r
9564         _reset: function () {\r
9565                 var image = this._image,\r
9566                     bounds = new Bounds(\r
9567                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),\r
9568                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),\r
9569                     size = bounds.getSize();\r
9570 \r
9571                 setPosition(image, bounds.min);\r
9572 \r
9573                 image.style.width  = size.x + 'px';\r
9574                 image.style.height = size.y + 'px';\r
9575         },\r
9576 \r
9577         _updateOpacity: function () {\r
9578                 setOpacity(this._image, this.options.opacity);\r
9579         },\r
9580 \r
9581         _updateZIndex: function () {\r
9582                 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {\r
9583                         this._image.style.zIndex = this.options.zIndex;\r
9584                 }\r
9585         },\r
9586 \r
9587         _overlayOnError: function () {\r
9588                 // @event error: Event\r
9589                 // Fired when the ImageOverlay layer fails to load its image\r
9590                 this.fire('error');\r
9591 \r
9592                 var errorUrl = this.options.errorOverlayUrl;\r
9593                 if (errorUrl && this._url !== errorUrl) {\r
9594                         this._url = errorUrl;\r
9595                         this._image.src = errorUrl;\r
9596                 }\r
9597         },\r
9598 \r
9599         // @method getCenter(): LatLng\r
9600         // Returns the center of the ImageOverlay.\r
9601         getCenter: function () {\r
9602                 return this._bounds.getCenter();\r
9603         }\r
9604   });\r
9605 \r
9606   // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)\r
9607   // Instantiates an image overlay object given the URL of the image and the\r
9608   // geographical bounds it is tied to.\r
9609   var imageOverlay = function (url, bounds, options) {\r
9610         return new ImageOverlay(url, bounds, options);\r
9611   };
9612
9613   /*\r
9614    * @class VideoOverlay\r
9615    * @aka L.VideoOverlay\r
9616    * @inherits ImageOverlay\r
9617    *\r
9618    * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.\r
9619    *\r
9620    * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)\r
9621    * HTML5 element.\r
9622    *\r
9623    * @example\r
9624    *\r
9625    * ```js\r
9626    * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',\r
9627    *    videoBounds = [[ 32, -130], [ 13, -100]];\r
9628    * L.videoOverlay(videoUrl, videoBounds ).addTo(map);\r
9629    * ```\r
9630    */\r
9631 \r
9632   var VideoOverlay = ImageOverlay.extend({\r
9633 \r
9634         // @section\r
9635         // @aka VideoOverlay options\r
9636         options: {\r
9637                 // @option autoplay: Boolean = true\r
9638                 // Whether the video starts playing automatically when loaded.\r
9639                 // On some browsers autoplay will only work with `muted: true`\r
9640                 autoplay: true,\r
9641 \r
9642                 // @option loop: Boolean = true\r
9643                 // Whether the video will loop back to the beginning when played.\r
9644                 loop: true,\r
9645 \r
9646                 // @option keepAspectRatio: Boolean = true\r
9647                 // Whether the video will save aspect ratio after the projection.\r
9648                 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)\r
9649                 keepAspectRatio: true,\r
9650 \r
9651                 // @option muted: Boolean = false\r
9652                 // Whether the video starts on mute when loaded.\r
9653                 muted: false,\r
9654 \r
9655                 // @option playsInline: Boolean = true\r
9656                 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.\r
9657                 playsInline: true\r
9658         },\r
9659 \r
9660         _initImage: function () {\r
9661                 var wasElementSupplied = this._url.tagName === 'VIDEO';\r
9662                 var vid = this._image = wasElementSupplied ? this._url : create$1('video');\r
9663 \r
9664                 addClass(vid, 'leaflet-image-layer');\r
9665                 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }\r
9666                 if (this.options.className) { addClass(vid, this.options.className); }\r
9667 \r
9668                 vid.onselectstart = falseFn;\r
9669                 vid.onmousemove = falseFn;\r
9670 \r
9671                 // @event load: Event\r
9672                 // Fired when the video has finished loading the first frame\r
9673                 vid.onloadeddata = bind(this.fire, this, 'load');\r
9674 \r
9675                 if (wasElementSupplied) {\r
9676                         var sourceElements = vid.getElementsByTagName('source');\r
9677                         var sources = [];\r
9678                         for (var j = 0; j < sourceElements.length; j++) {\r
9679                                 sources.push(sourceElements[j].src);\r
9680                         }\r
9681 \r
9682                         this._url = (sourceElements.length > 0) ? sources : [vid.src];\r
9683                         return;\r
9684                 }\r
9685 \r
9686                 if (!isArray(this._url)) { this._url = [this._url]; }\r
9687 \r
9688                 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {\r
9689                         vid.style['objectFit'] = 'fill';\r
9690                 }\r
9691                 vid.autoplay = !!this.options.autoplay;\r
9692                 vid.loop = !!this.options.loop;\r
9693                 vid.muted = !!this.options.muted;\r
9694                 vid.playsInline = !!this.options.playsInline;\r
9695                 for (var i = 0; i < this._url.length; i++) {\r
9696                         var source = create$1('source');\r
9697                         source.src = this._url[i];\r
9698                         vid.appendChild(source);\r
9699                 }\r
9700         }\r
9701 \r
9702         // @method getElement(): HTMLVideoElement\r
9703         // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)\r
9704         // used by this overlay.\r
9705   });\r
9706 \r
9707 \r
9708   // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)\r
9709   // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the\r
9710   // geographical bounds it is tied to.\r
9711 \r
9712   function videoOverlay(video, bounds, options) {\r
9713         return new VideoOverlay(video, bounds, options);\r
9714   }
9715
9716   /*
9717    * @class SVGOverlay
9718    * @aka L.SVGOverlay
9719    * @inherits ImageOverlay
9720    *
9721    * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9722    *
9723    * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9724    *
9725    * @example
9726    *
9727    * ```js
9728    * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9729    * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9730    * svgElement.setAttribute('viewBox', "0 0 200 200");
9731    * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
9732    * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9733    * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9734    * ```
9735    */
9736
9737   var SVGOverlay = ImageOverlay.extend({
9738         _initImage: function () {
9739                 var el = this._image = this._url;
9740
9741                 addClass(el, 'leaflet-image-layer');
9742                 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9743                 if (this.options.className) { addClass(el, this.options.className); }
9744
9745                 el.onselectstart = falseFn;
9746                 el.onmousemove = falseFn;
9747         }
9748
9749         // @method getElement(): SVGElement
9750         // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9751         // used by this overlay.
9752   });
9753
9754
9755   // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9756   // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9757   // A viewBox attribute is required on the SVG element to zoom in and out properly.
9758
9759   function svgOverlay(el, bounds, options) {
9760         return new SVGOverlay(el, bounds, options);
9761   }
9762
9763   /*\r
9764    * @class DivOverlay\r
9765    * @inherits Interactive layer\r
9766    * @aka L.DivOverlay\r
9767    * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.\r
9768    */\r
9769 \r
9770   // @namespace DivOverlay\r
9771   var DivOverlay = Layer.extend({\r
9772 \r
9773         // @section\r
9774         // @aka DivOverlay options\r
9775         options: {\r
9776                 // @option interactive: Boolean = false\r
9777                 // If true, the popup/tooltip will listen to the mouse events.\r
9778                 interactive: false,\r
9779 \r
9780                 // @option offset: Point = Point(0, 0)\r
9781                 // The offset of the overlay position.\r
9782                 offset: [0, 0],\r
9783 \r
9784                 // @option className: String = ''\r
9785                 // A custom CSS class name to assign to the overlay.\r
9786                 className: '',\r
9787 \r
9788                 // @option pane: String = undefined\r
9789                 // `Map pane` where the overlay will be added.\r
9790                 pane: undefined,\r
9791 \r
9792                 // @option content: String|HTMLElement|Function = ''\r
9793                 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be\r
9794                 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.\r
9795                 content: ''\r
9796         },\r
9797 \r
9798         initialize: function (options, source) {\r
9799                 if (options && (options instanceof LatLng || isArray(options))) {\r
9800                         this._latlng = toLatLng(options);\r
9801                         setOptions(this, source);\r
9802                 } else {\r
9803                         setOptions(this, options);\r
9804                         this._source = source;\r
9805                 }\r
9806                 if (this.options.content) {\r
9807                         this._content = this.options.content;\r
9808                 }\r
9809         },\r
9810 \r
9811         // @method openOn(map: Map): this\r
9812         // Adds the overlay to the map.\r
9813         // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.\r
9814         openOn: function (map) {\r
9815                 map = arguments.length ? map : this._source._map; // experimental, not the part of public api\r
9816                 if (!map.hasLayer(this)) {\r
9817                         map.addLayer(this);\r
9818                 }\r
9819                 return this;\r
9820         },\r
9821 \r
9822         // @method close(): this\r
9823         // Closes the overlay.\r
9824         // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`\r
9825         // and `layer.closePopup()`/`.closeTooltip()`.\r
9826         close: function () {\r
9827                 if (this._map) {\r
9828                         this._map.removeLayer(this);\r
9829                 }\r
9830                 return this;\r
9831         },\r
9832 \r
9833         // @method toggle(layer?: Layer): this\r
9834         // Opens or closes the overlay bound to layer depending on its current state.\r
9835         // Argument may be omitted only for overlay bound to layer.\r
9836         // Alternative to `layer.togglePopup()`/`.toggleTooltip()`.\r
9837         toggle: function (layer) {\r
9838                 if (this._map) {\r
9839                         this.close();\r
9840                 } else {\r
9841                         if (arguments.length) {\r
9842                                 this._source = layer;\r
9843                         } else {\r
9844                                 layer = this._source;\r
9845                         }\r
9846                         this._prepareOpen();\r
9847 \r
9848                         // open the overlay on the map\r
9849                         this.openOn(layer._map);\r
9850                 }\r
9851                 return this;\r
9852         },\r
9853 \r
9854         onAdd: function (map) {\r
9855                 this._zoomAnimated = map._zoomAnimated;\r
9856 \r
9857                 if (!this._container) {\r
9858                         this._initLayout();\r
9859                 }\r
9860 \r
9861                 if (map._fadeAnimated) {\r
9862                         setOpacity(this._container, 0);\r
9863                 }\r
9864 \r
9865                 clearTimeout(this._removeTimeout);\r
9866                 this.getPane().appendChild(this._container);\r
9867                 this.update();\r
9868 \r
9869                 if (map._fadeAnimated) {\r
9870                         setOpacity(this._container, 1);\r
9871                 }\r
9872 \r
9873                 this.bringToFront();\r
9874 \r
9875                 if (this.options.interactive) {\r
9876                         addClass(this._container, 'leaflet-interactive');\r
9877                         this.addInteractiveTarget(this._container);\r
9878                 }\r
9879         },\r
9880 \r
9881         onRemove: function (map) {\r
9882                 if (map._fadeAnimated) {\r
9883                         setOpacity(this._container, 0);\r
9884                         this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);\r
9885                 } else {\r
9886                         remove(this._container);\r
9887                 }\r
9888 \r
9889                 if (this.options.interactive) {\r
9890                         removeClass(this._container, 'leaflet-interactive');\r
9891                         this.removeInteractiveTarget(this._container);\r
9892                 }\r
9893         },\r
9894 \r
9895         // @namespace DivOverlay\r
9896         // @method getLatLng: LatLng\r
9897         // Returns the geographical point of the overlay.\r
9898         getLatLng: function () {\r
9899                 return this._latlng;\r
9900         },\r
9901 \r
9902         // @method setLatLng(latlng: LatLng): this\r
9903         // Sets the geographical point where the overlay will open.\r
9904         setLatLng: function (latlng) {\r
9905                 this._latlng = toLatLng(latlng);\r
9906                 if (this._map) {\r
9907                         this._updatePosition();\r
9908                         this._adjustPan();\r
9909                 }\r
9910                 return this;\r
9911         },\r
9912 \r
9913         // @method getContent: String|HTMLElement\r
9914         // Returns the content of the overlay.\r
9915         getContent: function () {\r
9916                 return this._content;\r
9917         },\r
9918 \r
9919         // @method setContent(htmlContent: String|HTMLElement|Function): this\r
9920         // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.\r
9921         // The function should return a `String` or `HTMLElement` to be used in the overlay.\r
9922         setContent: function (content) {\r
9923                 this._content = content;\r
9924                 this.update();\r
9925                 return this;\r
9926         },\r
9927 \r
9928         // @method getElement: String|HTMLElement\r
9929         // Returns the HTML container of the overlay.\r
9930         getElement: function () {\r
9931                 return this._container;\r
9932         },\r
9933 \r
9934         // @method update: null\r
9935         // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.\r
9936         update: function () {\r
9937                 if (!this._map) { return; }\r
9938 \r
9939                 this._container.style.visibility = 'hidden';\r
9940 \r
9941                 this._updateContent();\r
9942                 this._updateLayout();\r
9943                 this._updatePosition();\r
9944 \r
9945                 this._container.style.visibility = '';\r
9946 \r
9947                 this._adjustPan();\r
9948         },\r
9949 \r
9950         getEvents: function () {\r
9951                 var events = {\r
9952                         zoom: this._updatePosition,\r
9953                         viewreset: this._updatePosition\r
9954                 };\r
9955 \r
9956                 if (this._zoomAnimated) {\r
9957                         events.zoomanim = this._animateZoom;\r
9958                 }\r
9959                 return events;\r
9960         },\r
9961 \r
9962         // @method isOpen: Boolean\r
9963         // Returns `true` when the overlay is visible on the map.\r
9964         isOpen: function () {\r
9965                 return !!this._map && this._map.hasLayer(this);\r
9966         },\r
9967 \r
9968         // @method bringToFront: this\r
9969         // Brings this overlay in front of other overlays (in the same map pane).\r
9970         bringToFront: function () {\r
9971                 if (this._map) {\r
9972                         toFront(this._container);\r
9973                 }\r
9974                 return this;\r
9975         },\r
9976 \r
9977         // @method bringToBack: this\r
9978         // Brings this overlay to the back of other overlays (in the same map pane).\r
9979         bringToBack: function () {\r
9980                 if (this._map) {\r
9981                         toBack(this._container);\r
9982                 }\r
9983                 return this;\r
9984         },\r
9985 \r
9986         // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)\r
9987         _prepareOpen: function (latlng) {\r
9988                 var source = this._source;\r
9989                 if (!source._map) { return false; }\r
9990 \r
9991                 if (source instanceof FeatureGroup) {\r
9992                         source = null;\r
9993                         var layers = this._source._layers;\r
9994                         for (var id in layers) {\r
9995                                 if (layers[id]._map) {\r
9996                                         source = layers[id];\r
9997                                         break;\r
9998                                 }\r
9999                         }\r
10000                         if (!source) { return false; } // Unable to get source layer.\r
10001 \r
10002                         // set overlay source to this layer\r
10003                         this._source = source;\r
10004                 }\r
10005 \r
10006                 if (!latlng) {\r
10007                         if (source.getCenter) {\r
10008                                 latlng = source.getCenter();\r
10009                         } else if (source.getLatLng) {\r
10010                                 latlng = source.getLatLng();\r
10011                         } else if (source.getBounds) {\r
10012                                 latlng = source.getBounds().getCenter();\r
10013                         } else {\r
10014                                 throw new Error('Unable to get source layer LatLng.');\r
10015                         }\r
10016                 }\r
10017                 this.setLatLng(latlng);\r
10018 \r
10019                 if (this._map) {\r
10020                         // update the overlay (content, layout, etc...)\r
10021                         this.update();\r
10022                 }\r
10023 \r
10024                 return true;\r
10025         },\r
10026 \r
10027         _updateContent: function () {\r
10028                 if (!this._content) { return; }\r
10029 \r
10030                 var node = this._contentNode;\r
10031                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;\r
10032 \r
10033                 if (typeof content === 'string') {\r
10034                         node.innerHTML = content;\r
10035                 } else {\r
10036                         while (node.hasChildNodes()) {\r
10037                                 node.removeChild(node.firstChild);\r
10038                         }\r
10039                         node.appendChild(content);\r
10040                 }\r
10041 \r
10042                 // @namespace DivOverlay\r
10043                 // @section DivOverlay events\r
10044                 // @event contentupdate: Event\r
10045                 // Fired when the content of the overlay is updated\r
10046                 this.fire('contentupdate');\r
10047         },\r
10048 \r
10049         _updatePosition: function () {\r
10050                 if (!this._map) { return; }\r
10051 \r
10052                 var pos = this._map.latLngToLayerPoint(this._latlng),\r
10053                     offset = toPoint(this.options.offset),\r
10054                     anchor = this._getAnchor();\r
10055 \r
10056                 if (this._zoomAnimated) {\r
10057                         setPosition(this._container, pos.add(anchor));\r
10058                 } else {\r
10059                         offset = offset.add(pos).add(anchor);\r
10060                 }\r
10061 \r
10062                 var bottom = this._containerBottom = -offset.y,\r
10063                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;\r
10064 \r
10065                 // bottom position the overlay in case the height of the overlay changes (images loading etc)\r
10066                 this._container.style.bottom = bottom + 'px';\r
10067                 this._container.style.left = left + 'px';\r
10068         },\r
10069 \r
10070         _getAnchor: function () {\r
10071                 return [0, 0];\r
10072         }\r
10073 \r
10074   });\r
10075 \r
10076   Map.include({\r
10077         _initOverlay: function (OverlayClass, content, latlng, options) {\r
10078                 var overlay = content;\r
10079                 if (!(overlay instanceof OverlayClass)) {\r
10080                         overlay = new OverlayClass(options).setContent(content);\r
10081                 }\r
10082                 if (latlng) {\r
10083                         overlay.setLatLng(latlng);\r
10084                 }\r
10085                 return overlay;\r
10086         }\r
10087   });\r
10088 \r
10089 \r
10090   Layer.include({\r
10091         _initOverlay: function (OverlayClass, old, content, options) {\r
10092                 var overlay = content;\r
10093                 if (overlay instanceof OverlayClass) {\r
10094                         setOptions(overlay, options);\r
10095                         overlay._source = this;\r
10096                 } else {\r
10097                         overlay = (old && !options) ? old : new OverlayClass(options, this);\r
10098                         overlay.setContent(content);\r
10099                 }\r
10100                 return overlay;\r
10101         }\r
10102   });
10103
10104   /*\r
10105    * @class Popup\r
10106    * @inherits DivOverlay\r
10107    * @aka L.Popup\r
10108    * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to\r
10109    * open popups while making sure that only one popup is open at one time\r
10110    * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.\r
10111    *\r
10112    * @example\r
10113    *\r
10114    * If you want to just bind a popup to marker click and then open it, it's really easy:\r
10115    *\r
10116    * ```js\r
10117    * marker.bindPopup(popupContent).openPopup();\r
10118    * ```\r
10119    * Path overlays like polylines also have a `bindPopup` method.\r
10120    *\r
10121    * A popup can be also standalone:\r
10122    *\r
10123    * ```js\r
10124    * var popup = L.popup()\r
10125    *    .setLatLng(latlng)\r
10126    *    .setContent('<p>Hello world!<br />This is a nice popup.</p>')\r
10127    *    .openOn(map);\r
10128    * ```\r
10129    * or\r
10130    * ```js\r
10131    * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')\r
10132    *    .openOn(map);\r
10133    * ```\r
10134    */\r
10135 \r
10136 \r
10137   // @namespace Popup\r
10138   var Popup = DivOverlay.extend({\r
10139 \r
10140         // @section\r
10141         // @aka Popup options\r
10142         options: {\r
10143                 // @option pane: String = 'popupPane'\r
10144                 // `Map pane` where the popup will be added.\r
10145                 pane: 'popupPane',\r
10146 \r
10147                 // @option offset: Point = Point(0, 7)\r
10148                 // The offset of the popup position.\r
10149                 offset: [0, 7],\r
10150 \r
10151                 // @option maxWidth: Number = 300\r
10152                 // Max width of the popup, in pixels.\r
10153                 maxWidth: 300,\r
10154 \r
10155                 // @option minWidth: Number = 50\r
10156                 // Min width of the popup, in pixels.\r
10157                 minWidth: 50,\r
10158 \r
10159                 // @option maxHeight: Number = null\r
10160                 // If set, creates a scrollable container of the given height\r
10161                 // inside a popup if its content exceeds it.\r
10162                 // The scrollable container can be styled using the\r
10163                 // `leaflet-popup-scrolled` CSS class selector.\r
10164                 maxHeight: null,\r
10165 \r
10166                 // @option autoPan: Boolean = true\r
10167                 // Set it to `false` if you don't want the map to do panning animation\r
10168                 // to fit the opened popup.\r
10169                 autoPan: true,\r
10170 \r
10171                 // @option autoPanPaddingTopLeft: Point = null\r
10172                 // The margin between the popup and the top left corner of the map\r
10173                 // view after autopanning was performed.\r
10174                 autoPanPaddingTopLeft: null,\r
10175 \r
10176                 // @option autoPanPaddingBottomRight: Point = null\r
10177                 // The margin between the popup and the bottom right corner of the map\r
10178                 // view after autopanning was performed.\r
10179                 autoPanPaddingBottomRight: null,\r
10180 \r
10181                 // @option autoPanPadding: Point = Point(5, 5)\r
10182                 // Equivalent of setting both top left and bottom right autopan padding to the same value.\r
10183                 autoPanPadding: [5, 5],\r
10184 \r
10185                 // @option keepInView: Boolean = false\r
10186                 // Set it to `true` if you want to prevent users from panning the popup\r
10187                 // off of the screen while it is open.\r
10188                 keepInView: false,\r
10189 \r
10190                 // @option closeButton: Boolean = true\r
10191                 // Controls the presence of a close button in the popup.\r
10192                 closeButton: true,\r
10193 \r
10194                 // @option autoClose: Boolean = true\r
10195                 // Set it to `false` if you want to override the default behavior of\r
10196                 // the popup closing when another popup is opened.\r
10197                 autoClose: true,\r
10198 \r
10199                 // @option closeOnEscapeKey: Boolean = true\r
10200                 // Set it to `false` if you want to override the default behavior of\r
10201                 // the ESC key for closing of the popup.\r
10202                 closeOnEscapeKey: true,\r
10203 \r
10204                 // @option closeOnClick: Boolean = *\r
10205                 // Set it if you want to override the default behavior of the popup closing when user clicks\r
10206                 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.\r
10207 \r
10208                 // @option className: String = ''\r
10209                 // A custom CSS class name to assign to the popup.\r
10210                 className: ''\r
10211         },\r
10212 \r
10213         // @namespace Popup\r
10214         // @method openOn(map: Map): this\r
10215         // Alternative to `map.openPopup(popup)`.\r
10216         // Adds the popup to the map and closes the previous one.\r
10217         openOn: function (map) {\r
10218                 map = arguments.length ? map : this._source._map; // experimental, not the part of public api\r
10219 \r
10220                 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {\r
10221                         map.removeLayer(map._popup);\r
10222                 }\r
10223                 map._popup = this;\r
10224 \r
10225                 return DivOverlay.prototype.openOn.call(this, map);\r
10226         },\r
10227 \r
10228         onAdd: function (map) {\r
10229                 DivOverlay.prototype.onAdd.call(this, map);\r
10230 \r
10231                 // @namespace Map\r
10232                 // @section Popup events\r
10233                 // @event popupopen: PopupEvent\r
10234                 // Fired when a popup is opened in the map\r
10235                 map.fire('popupopen', {popup: this});\r
10236 \r
10237                 if (this._source) {\r
10238                         // @namespace Layer\r
10239                         // @section Popup events\r
10240                         // @event popupopen: PopupEvent\r
10241                         // Fired when a popup bound to this layer is opened\r
10242                         this._source.fire('popupopen', {popup: this}, true);\r
10243                         // For non-path layers, we toggle the popup when clicking\r
10244                         // again the layer, so prevent the map to reopen it.\r
10245                         if (!(this._source instanceof Path)) {\r
10246                                 this._source.on('preclick', stopPropagation);\r
10247                         }\r
10248                 }\r
10249         },\r
10250 \r
10251         onRemove: function (map) {\r
10252                 DivOverlay.prototype.onRemove.call(this, map);\r
10253 \r
10254                 // @namespace Map\r
10255                 // @section Popup events\r
10256                 // @event popupclose: PopupEvent\r
10257                 // Fired when a popup in the map is closed\r
10258                 map.fire('popupclose', {popup: this});\r
10259 \r
10260                 if (this._source) {\r
10261                         // @namespace Layer\r
10262                         // @section Popup events\r
10263                         // @event popupclose: PopupEvent\r
10264                         // Fired when a popup bound to this layer is closed\r
10265                         this._source.fire('popupclose', {popup: this}, true);\r
10266                         if (!(this._source instanceof Path)) {\r
10267                                 this._source.off('preclick', stopPropagation);\r
10268                         }\r
10269                 }\r
10270         },\r
10271 \r
10272         getEvents: function () {\r
10273                 var events = DivOverlay.prototype.getEvents.call(this);\r
10274 \r
10275                 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {\r
10276                         events.preclick = this.close;\r
10277                 }\r
10278 \r
10279                 if (this.options.keepInView) {\r
10280                         events.moveend = this._adjustPan;\r
10281                 }\r
10282 \r
10283                 return events;\r
10284         },\r
10285 \r
10286         _initLayout: function () {\r
10287                 var prefix = 'leaflet-popup',\r
10288                     container = this._container = create$1('div',\r
10289                         prefix + ' ' + (this.options.className || '') +\r
10290                         ' leaflet-zoom-animated');\r
10291 \r
10292                 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);\r
10293                 this._contentNode = create$1('div', prefix + '-content', wrapper);\r
10294 \r
10295                 disableClickPropagation(container);\r
10296                 disableScrollPropagation(this._contentNode);\r
10297                 on(container, 'contextmenu', stopPropagation);\r
10298 \r
10299                 this._tipContainer = create$1('div', prefix + '-tip-container', container);\r
10300                 this._tip = create$1('div', prefix + '-tip', this._tipContainer);\r
10301 \r
10302                 if (this.options.closeButton) {\r
10303                         var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);\r
10304                         closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399\r
10305                         closeButton.setAttribute('aria-label', 'Close popup');\r
10306                         closeButton.href = '#close';\r
10307                         closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';\r
10308 \r
10309                         on(closeButton, 'click', function (ev) {\r
10310                                 preventDefault(ev);\r
10311                                 this.close();\r
10312                         }, this);\r
10313                 }\r
10314         },\r
10315 \r
10316         _updateLayout: function () {\r
10317                 var container = this._contentNode,\r
10318                     style = container.style;\r
10319 \r
10320                 style.width = '';\r
10321                 style.whiteSpace = 'nowrap';\r
10322 \r
10323                 var width = container.offsetWidth;\r
10324                 width = Math.min(width, this.options.maxWidth);\r
10325                 width = Math.max(width, this.options.minWidth);\r
10326 \r
10327                 style.width = (width + 1) + 'px';\r
10328                 style.whiteSpace = '';\r
10329 \r
10330                 style.height = '';\r
10331 \r
10332                 var height = container.offsetHeight,\r
10333                     maxHeight = this.options.maxHeight,\r
10334                     scrolledClass = 'leaflet-popup-scrolled';\r
10335 \r
10336                 if (maxHeight && height > maxHeight) {\r
10337                         style.height = maxHeight + 'px';\r
10338                         addClass(container, scrolledClass);\r
10339                 } else {\r
10340                         removeClass(container, scrolledClass);\r
10341                 }\r
10342 \r
10343                 this._containerWidth = this._container.offsetWidth;\r
10344         },\r
10345 \r
10346         _animateZoom: function (e) {\r
10347                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),\r
10348                     anchor = this._getAnchor();\r
10349                 setPosition(this._container, pos.add(anchor));\r
10350         },\r
10351 \r
10352         _adjustPan: function () {\r
10353                 if (!this.options.autoPan) { return; }\r
10354                 if (this._map._panAnim) { this._map._panAnim.stop(); }\r
10355 \r
10356                 // We can endlessly recurse if keepInView is set and the view resets.\r
10357                 // Let's guard against that by exiting early if we're responding to our own autopan.\r
10358                 if (this._autopanning) {\r
10359                         this._autopanning = false;\r
10360                         return;\r
10361                 }\r
10362 \r
10363                 var map = this._map,\r
10364                     marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,\r
10365                     containerHeight = this._container.offsetHeight + marginBottom,\r
10366                     containerWidth = this._containerWidth,\r
10367                     layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);\r
10368 \r
10369                 layerPos._add(getPosition(this._container));\r
10370 \r
10371                 var containerPos = map.layerPointToContainerPoint(layerPos),\r
10372                     padding = toPoint(this.options.autoPanPadding),\r
10373                     paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),\r
10374                     paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),\r
10375                     size = map.getSize(),\r
10376                     dx = 0,\r
10377                     dy = 0;\r
10378 \r
10379                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right\r
10380                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;\r
10381                 }\r
10382                 if (containerPos.x - dx - paddingTL.x < 0) { // left\r
10383                         dx = containerPos.x - paddingTL.x;\r
10384                 }\r
10385                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom\r
10386                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;\r
10387                 }\r
10388                 if (containerPos.y - dy - paddingTL.y < 0) { // top\r
10389                         dy = containerPos.y - paddingTL.y;\r
10390                 }\r
10391 \r
10392                 // @namespace Map\r
10393                 // @section Popup events\r
10394                 // @event autopanstart: Event\r
10395                 // Fired when the map starts autopanning when opening a popup.\r
10396                 if (dx || dy) {\r
10397                         // Track that we're autopanning, as this function will be re-ran on moveend\r
10398                         if (this.options.keepInView) {\r
10399                                 this._autopanning = true;\r
10400                         }\r
10401 \r
10402                         map\r
10403                             .fire('autopanstart')\r
10404                             .panBy([dx, dy]);\r
10405                 }\r
10406         },\r
10407 \r
10408         _getAnchor: function () {\r
10409                 // Where should we anchor the popup on the source layer?\r
10410                 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);\r
10411         }\r
10412 \r
10413   });\r
10414 \r
10415   // @namespace Popup\r
10416   // @factory L.popup(options?: Popup options, source?: Layer)\r
10417   // 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.\r
10418   // @alternative\r
10419   // @factory L.popup(latlng: LatLng, options?: Popup options)\r
10420   // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.\r
10421   var popup = function (options, source) {\r
10422         return new Popup(options, source);\r
10423   };\r
10424 \r
10425 \r
10426   /* @namespace Map\r
10427    * @section Interaction Options\r
10428    * @option closePopupOnClick: Boolean = true\r
10429    * Set it to `false` if you don't want popups to close when user clicks the map.\r
10430    */\r
10431   Map.mergeOptions({\r
10432         closePopupOnClick: true\r
10433   });\r
10434 \r
10435 \r
10436   // @namespace Map\r
10437   // @section Methods for Layers and Controls\r
10438   Map.include({\r
10439         // @method openPopup(popup: Popup): this\r
10440         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).\r
10441         // @alternative\r
10442         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this\r
10443         // Creates a popup with the specified content and options and opens it in the given point on a map.\r
10444         openPopup: function (popup, latlng, options) {\r
10445                 this._initOverlay(Popup, popup, latlng, options)\r
10446                   .openOn(this);\r
10447 \r
10448                 return this;\r
10449         },\r
10450 \r
10451         // @method closePopup(popup?: Popup): this\r
10452         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).\r
10453         closePopup: function (popup) {\r
10454                 popup = arguments.length ? popup : this._popup;\r
10455                 if (popup) {\r
10456                         popup.close();\r
10457                 }\r
10458                 return this;\r
10459         }\r
10460   });\r
10461 \r
10462   /*\r
10463    * @namespace Layer\r
10464    * @section Popup methods example\r
10465    *\r
10466    * All layers share a set of methods convenient for binding popups to it.\r
10467    *\r
10468    * ```js\r
10469    * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);\r
10470    * layer.openPopup();\r
10471    * layer.closePopup();\r
10472    * ```\r
10473    *\r
10474    * 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.\r
10475    */\r
10476 \r
10477   // @section Popup methods\r
10478   Layer.include({\r
10479 \r
10480         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this\r
10481         // Binds a popup to the layer with the passed `content` and sets up the\r
10482         // necessary event listeners. If a `Function` is passed it will receive\r
10483         // the layer as the first argument and should return a `String` or `HTMLElement`.\r
10484         bindPopup: function (content, options) {\r
10485                 this._popup = this._initOverlay(Popup, this._popup, content, options);\r
10486                 if (!this._popupHandlersAdded) {\r
10487                         this.on({\r
10488                                 click: this._openPopup,\r
10489                                 keypress: this._onKeyPress,\r
10490                                 remove: this.closePopup,\r
10491                                 move: this._movePopup\r
10492                         });\r
10493                         this._popupHandlersAdded = true;\r
10494                 }\r
10495 \r
10496                 return this;\r
10497         },\r
10498 \r
10499         // @method unbindPopup(): this\r
10500         // Removes the popup previously bound with `bindPopup`.\r
10501         unbindPopup: function () {\r
10502                 if (this._popup) {\r
10503                         this.off({\r
10504                                 click: this._openPopup,\r
10505                                 keypress: this._onKeyPress,\r
10506                                 remove: this.closePopup,\r
10507                                 move: this._movePopup\r
10508                         });\r
10509                         this._popupHandlersAdded = false;\r
10510                         this._popup = null;\r
10511                 }\r
10512                 return this;\r
10513         },\r
10514 \r
10515         // @method openPopup(latlng?: LatLng): this\r
10516         // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.\r
10517         openPopup: function (latlng) {\r
10518                 if (this._popup) {\r
10519                         if (!(this instanceof FeatureGroup)) {\r
10520                                 this._popup._source = this;\r
10521                         }\r
10522                         if (this._popup._prepareOpen(latlng || this._latlng)) {\r
10523                                 // open the popup on the map\r
10524                                 this._popup.openOn(this._map);\r
10525                         }\r
10526                 }\r
10527                 return this;\r
10528         },\r
10529 \r
10530         // @method closePopup(): this\r
10531         // Closes the popup bound to this layer if it is open.\r
10532         closePopup: function () {\r
10533                 if (this._popup) {\r
10534                         this._popup.close();\r
10535                 }\r
10536                 return this;\r
10537         },\r
10538 \r
10539         // @method togglePopup(): this\r
10540         // Opens or closes the popup bound to this layer depending on its current state.\r
10541         togglePopup: function () {\r
10542                 if (this._popup) {\r
10543                         this._popup.toggle(this);\r
10544                 }\r
10545                 return this;\r
10546         },\r
10547 \r
10548         // @method isPopupOpen(): boolean\r
10549         // Returns `true` if the popup bound to this layer is currently open.\r
10550         isPopupOpen: function () {\r
10551                 return (this._popup ? this._popup.isOpen() : false);\r
10552         },\r
10553 \r
10554         // @method setPopupContent(content: String|HTMLElement|Popup): this\r
10555         // Sets the content of the popup bound to this layer.\r
10556         setPopupContent: function (content) {\r
10557                 if (this._popup) {\r
10558                         this._popup.setContent(content);\r
10559                 }\r
10560                 return this;\r
10561         },\r
10562 \r
10563         // @method getPopup(): Popup\r
10564         // Returns the popup bound to this layer.\r
10565         getPopup: function () {\r
10566                 return this._popup;\r
10567         },\r
10568 \r
10569         _openPopup: function (e) {\r
10570                 if (!this._popup || !this._map) {\r
10571                         return;\r
10572                 }\r
10573                 // prevent map click\r
10574                 stop(e);\r
10575 \r
10576                 var target = e.layer || e.target;\r
10577                 if (this._popup._source === target && !(target instanceof Path)) {\r
10578                         // treat it like a marker and figure out\r
10579                         // if we should toggle it open/closed\r
10580                         if (this._map.hasLayer(this._popup)) {\r
10581                                 this.closePopup();\r
10582                         } else {\r
10583                                 this.openPopup(e.latlng);\r
10584                         }\r
10585                         return;\r
10586                 }\r
10587                 this._popup._source = target;\r
10588                 this.openPopup(e.latlng);\r
10589         },\r
10590 \r
10591         _movePopup: function (e) {\r
10592                 this._popup.setLatLng(e.latlng);\r
10593         },\r
10594 \r
10595         _onKeyPress: function (e) {\r
10596                 if (e.originalEvent.keyCode === 13) {\r
10597                         this._openPopup(e);\r
10598                 }\r
10599         }\r
10600   });
10601
10602   /*
10603    * @class Tooltip
10604    * @inherits DivOverlay
10605    * @aka L.Tooltip
10606    * Used to display small texts on top of map layers.
10607    *
10608    * @example
10609    * If you want to just bind a tooltip to marker:
10610    *
10611    * ```js
10612    * marker.bindTooltip("my tooltip text").openTooltip();
10613    * ```
10614    * Path overlays like polylines also have a `bindTooltip` method.
10615    *
10616    * A tooltip can be also standalone:
10617    *
10618    * ```js
10619    * var tooltip = L.tooltip()
10620    *    .setLatLng(latlng)
10621    *    .setContent('Hello world!<br />This is a nice tooltip.')
10622    *    .addTo(map);
10623    * ```
10624    * or
10625    * ```js
10626    * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
10627    *    .addTo(map);
10628    * ```
10629    *
10630    *
10631    * Note about tooltip offset. Leaflet takes two options in consideration
10632    * for computing tooltip offsetting:
10633    * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10634    *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
10635    *   move it to the bottom. Negatives will move to the left and top.
10636    * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10637    *   should adapt this value if you use a custom icon.
10638    */
10639
10640
10641   // @namespace Tooltip
10642   var Tooltip = DivOverlay.extend({
10643
10644         // @section
10645         // @aka Tooltip options
10646         options: {
10647                 // @option pane: String = 'tooltipPane'
10648                 // `Map pane` where the tooltip will be added.
10649                 pane: 'tooltipPane',
10650
10651                 // @option offset: Point = Point(0, 0)
10652                 // Optional offset of the tooltip position.
10653                 offset: [0, 0],
10654
10655                 // @option direction: String = 'auto'
10656                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10657                 // `top`, `bottom`, `center`, `auto`.
10658                 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10659                 // position on the map.
10660                 direction: 'auto',
10661
10662                 // @option permanent: Boolean = false
10663                 // Whether to open the tooltip permanently or only on mouseover.
10664                 permanent: false,
10665
10666                 // @option sticky: Boolean = false
10667                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10668                 sticky: false,
10669
10670                 // @option opacity: Number = 0.9
10671                 // Tooltip container opacity.
10672                 opacity: 0.9
10673         },
10674
10675         onAdd: function (map) {
10676                 DivOverlay.prototype.onAdd.call(this, map);
10677                 this.setOpacity(this.options.opacity);
10678
10679                 // @namespace Map
10680                 // @section Tooltip events
10681                 // @event tooltipopen: TooltipEvent
10682                 // Fired when a tooltip is opened in the map.
10683                 map.fire('tooltipopen', {tooltip: this});
10684
10685                 if (this._source) {
10686                         this.addEventParent(this._source);
10687
10688                         // @namespace Layer
10689                         // @section Tooltip events
10690                         // @event tooltipopen: TooltipEvent
10691                         // Fired when a tooltip bound to this layer is opened.
10692                         this._source.fire('tooltipopen', {tooltip: this}, true);
10693                 }
10694         },
10695
10696         onRemove: function (map) {
10697                 DivOverlay.prototype.onRemove.call(this, map);
10698
10699                 // @namespace Map
10700                 // @section Tooltip events
10701                 // @event tooltipclose: TooltipEvent
10702                 // Fired when a tooltip in the map is closed.
10703                 map.fire('tooltipclose', {tooltip: this});
10704
10705                 if (this._source) {
10706                         this.removeEventParent(this._source);
10707
10708                         // @namespace Layer
10709                         // @section Tooltip events
10710                         // @event tooltipclose: TooltipEvent
10711                         // Fired when a tooltip bound to this layer is closed.
10712                         this._source.fire('tooltipclose', {tooltip: this}, true);
10713                 }
10714         },
10715
10716         getEvents: function () {
10717                 var events = DivOverlay.prototype.getEvents.call(this);
10718
10719                 if (!this.options.permanent) {
10720                         events.preclick = this.close;
10721                 }
10722
10723                 return events;
10724         },
10725
10726         _initLayout: function () {
10727                 var prefix = 'leaflet-tooltip',
10728                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10729
10730                 this._contentNode = this._container = create$1('div', className);
10731
10732                 this._container.setAttribute('role', 'tooltip');
10733                 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this));
10734         },
10735
10736         _updateLayout: function () {},
10737
10738         _adjustPan: function () {},
10739
10740         _setPosition: function (pos) {
10741                 var subX, subY,
10742                     map = this._map,
10743                     container = this._container,
10744                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
10745                     tooltipPoint = map.layerPointToContainerPoint(pos),
10746                     direction = this.options.direction,
10747                     tooltipWidth = container.offsetWidth,
10748                     tooltipHeight = container.offsetHeight,
10749                     offset = toPoint(this.options.offset),
10750                     anchor = this._getAnchor();
10751
10752                 if (direction === 'top') {
10753                         subX = tooltipWidth / 2;
10754                         subY = tooltipHeight;
10755                 } else if (direction === 'bottom') {
10756                         subX = tooltipWidth / 2;
10757                         subY = 0;
10758                 } else if (direction === 'center') {
10759                         subX = tooltipWidth / 2;
10760                         subY = tooltipHeight / 2;
10761                 } else if (direction === 'right') {
10762                         subX = 0;
10763                         subY = tooltipHeight / 2;
10764                 } else if (direction === 'left') {
10765                         subX = tooltipWidth;
10766                         subY = tooltipHeight / 2;
10767                 } else if (tooltipPoint.x < centerPoint.x) {
10768                         direction = 'right';
10769                         subX = 0;
10770                         subY = tooltipHeight / 2;
10771                 } else {
10772                         direction = 'left';
10773                         subX = tooltipWidth + (offset.x + anchor.x) * 2;
10774                         subY = tooltipHeight / 2;
10775                 }
10776
10777                 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10778
10779                 removeClass(container, 'leaflet-tooltip-right');
10780                 removeClass(container, 'leaflet-tooltip-left');
10781                 removeClass(container, 'leaflet-tooltip-top');
10782                 removeClass(container, 'leaflet-tooltip-bottom');
10783                 addClass(container, 'leaflet-tooltip-' + direction);
10784                 setPosition(container, pos);
10785         },
10786
10787         _updatePosition: function () {
10788                 var pos = this._map.latLngToLayerPoint(this._latlng);
10789                 this._setPosition(pos);
10790         },
10791
10792         setOpacity: function (opacity) {
10793                 this.options.opacity = opacity;
10794
10795                 if (this._container) {
10796                         setOpacity(this._container, opacity);
10797                 }
10798         },
10799
10800         _animateZoom: function (e) {
10801                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10802                 this._setPosition(pos);
10803         },
10804
10805         _getAnchor: function () {
10806                 // Where should we anchor the tooltip on the source layer?
10807                 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10808         }
10809
10810   });
10811
10812   // @namespace Tooltip
10813   // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10814   // 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.
10815   // @alternative
10816   // @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
10817   // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
10818   var tooltip = function (options, source) {
10819         return new Tooltip(options, source);
10820   };
10821
10822   // @namespace Map
10823   // @section Methods for Layers and Controls
10824   Map.include({
10825
10826         // @method openTooltip(tooltip: Tooltip): this
10827         // Opens the specified tooltip.
10828         // @alternative
10829         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10830         // Creates a tooltip with the specified content and options and open it.
10831         openTooltip: function (tooltip, latlng, options) {
10832                 this._initOverlay(Tooltip, tooltip, latlng, options)
10833                   .openOn(this);
10834
10835                 return this;
10836         },
10837
10838         // @method closeTooltip(tooltip: Tooltip): this
10839         // Closes the tooltip given as parameter.
10840         closeTooltip: function (tooltip) {
10841                 tooltip.close();
10842                 return this;
10843         }
10844
10845   });
10846
10847   /*
10848    * @namespace Layer
10849    * @section Tooltip methods example
10850    *
10851    * All layers share a set of methods convenient for binding tooltips to it.
10852    *
10853    * ```js
10854    * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10855    * layer.openTooltip();
10856    * layer.closeTooltip();
10857    * ```
10858    */
10859
10860   // @section Tooltip methods
10861   Layer.include({
10862
10863         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10864         // Binds a tooltip to the layer with the passed `content` and sets up the
10865         // necessary event listeners. If a `Function` is passed it will receive
10866         // the layer as the first argument and should return a `String` or `HTMLElement`.
10867         bindTooltip: function (content, options) {
10868
10869                 if (this._tooltip && this.isTooltipOpen()) {
10870                         this.unbindTooltip();
10871                 }
10872
10873                 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
10874                 this._initTooltipInteractions();
10875
10876                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10877                         this.openTooltip();
10878                 }
10879
10880                 return this;
10881         },
10882
10883         // @method unbindTooltip(): this
10884         // Removes the tooltip previously bound with `bindTooltip`.
10885         unbindTooltip: function () {
10886                 if (this._tooltip) {
10887                         this._initTooltipInteractions(true);
10888                         this.closeTooltip();
10889                         this._tooltip = null;
10890                 }
10891                 return this;
10892         },
10893
10894         _initTooltipInteractions: function (remove) {
10895                 if (!remove && this._tooltipHandlersAdded) { return; }
10896                 var onOff = remove ? 'off' : 'on',
10897                     events = {
10898                         remove: this.closeTooltip,
10899                         move: this._moveTooltip
10900                     };
10901                 if (!this._tooltip.options.permanent) {
10902                         events.mouseover = this._openTooltip;
10903                         events.mouseout = this.closeTooltip;
10904                         events.click = this._openTooltip;
10905                         if (this._map) {
10906                                 this._addFocusListeners();
10907                         } else {
10908                                 events.add = this._addFocusListeners;
10909                         }
10910                 } else {
10911                         events.add = this._openTooltip;
10912                 }
10913                 if (this._tooltip.options.sticky) {
10914                         events.mousemove = this._moveTooltip;
10915                 }
10916                 this[onOff](events);
10917                 this._tooltipHandlersAdded = !remove;
10918         },
10919
10920         // @method openTooltip(latlng?: LatLng): this
10921         // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10922         openTooltip: function (latlng) {
10923                 if (this._tooltip) {
10924                         if (!(this instanceof FeatureGroup)) {
10925                                 this._tooltip._source = this;
10926                         }
10927                         if (this._tooltip._prepareOpen(latlng)) {
10928                                 // open the tooltip on the map
10929                                 this._tooltip.openOn(this._map);
10930
10931                                 if (this.getElement) {
10932                                         this._setAriaDescribedByOnLayer(this);
10933                                 } else if (this.eachLayer) {
10934                                         this.eachLayer(this._setAriaDescribedByOnLayer, this);
10935                                 }
10936                         }
10937                 }
10938                 return this;
10939         },
10940
10941         // @method closeTooltip(): this
10942         // Closes the tooltip bound to this layer if it is open.
10943         closeTooltip: function () {
10944                 if (this._tooltip) {
10945                         return this._tooltip.close();
10946                 }
10947         },
10948
10949         // @method toggleTooltip(): this
10950         // Opens or closes the tooltip bound to this layer depending on its current state.
10951         toggleTooltip: function () {
10952                 if (this._tooltip) {
10953                         this._tooltip.toggle(this);
10954                 }
10955                 return this;
10956         },
10957
10958         // @method isTooltipOpen(): boolean
10959         // Returns `true` if the tooltip bound to this layer is currently open.
10960         isTooltipOpen: function () {
10961                 return this._tooltip.isOpen();
10962         },
10963
10964         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10965         // Sets the content of the tooltip bound to this layer.
10966         setTooltipContent: function (content) {
10967                 if (this._tooltip) {
10968                         this._tooltip.setContent(content);
10969                 }
10970                 return this;
10971         },
10972
10973         // @method getTooltip(): Tooltip
10974         // Returns the tooltip bound to this layer.
10975         getTooltip: function () {
10976                 return this._tooltip;
10977         },
10978
10979         _addFocusListeners: function () {
10980                 if (this.getElement) {
10981                         this._addFocusListenersOnLayer(this);
10982                 } else if (this.eachLayer) {
10983                         this.eachLayer(this._addFocusListenersOnLayer, this);
10984                 }
10985         },
10986
10987         _addFocusListenersOnLayer: function (layer) {
10988                 var el = typeof layer.getElement === 'function' && layer.getElement();
10989                 if (el) {
10990                         on(el, 'focus', function () {
10991                                 this._tooltip._source = layer;
10992                                 this.openTooltip();
10993                         }, this);
10994                         on(el, 'blur', this.closeTooltip, this);
10995                 }
10996         },
10997
10998         _setAriaDescribedByOnLayer: function (layer) {
10999                 var el = typeof layer.getElement === 'function' && layer.getElement();
11000                 if (el) {
11001                         el.setAttribute('aria-describedby', this._tooltip._container.id);
11002                 }
11003         },
11004
11005
11006         _openTooltip: function (e) {
11007                 if (!this._tooltip || !this._map) {
11008                         return;
11009                 }
11010
11011                 // If the map is moving, we will show the tooltip after it's done.
11012                 if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) {
11013                         this._openOnceFlag = true;
11014                         var that = this;
11015                         this._map.once('moveend', function () {
11016                                 that._openOnceFlag = false;
11017                                 that._openTooltip(e);
11018                         });
11019                         return;
11020                 }
11021
11022                 this._tooltip._source = e.layer || e.target;
11023
11024                 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
11025         },
11026
11027         _moveTooltip: function (e) {
11028                 var latlng = e.latlng, containerPoint, layerPoint;
11029                 if (this._tooltip.options.sticky && e.originalEvent) {
11030                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
11031                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
11032                         latlng = this._map.layerPointToLatLng(layerPoint);
11033                 }
11034                 this._tooltip.setLatLng(latlng);
11035         }
11036   });
11037
11038   /*
11039    * @class DivIcon
11040    * @aka L.DivIcon
11041    * @inherits Icon
11042    *
11043    * Represents a lightweight icon for markers that uses a simple `<div>`
11044    * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
11045    *
11046    * @example
11047    * ```js
11048    * var myIcon = L.divIcon({className: 'my-div-icon'});
11049    * // you can set .my-div-icon styles in CSS
11050    *
11051    * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
11052    * ```
11053    *
11054    * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
11055    */
11056
11057   var DivIcon = Icon.extend({
11058         options: {
11059                 // @section
11060                 // @aka DivIcon options
11061                 iconSize: [12, 12], // also can be set through CSS
11062
11063                 // iconAnchor: (Point),
11064                 // popupAnchor: (Point),
11065
11066                 // @option html: String|HTMLElement = ''
11067                 // Custom HTML code to put inside the div element, empty by default. Alternatively,
11068                 // an instance of `HTMLElement`.
11069                 html: false,
11070
11071                 // @option bgPos: Point = [0, 0]
11072                 // Optional relative position of the background, in pixels
11073                 bgPos: null,
11074
11075                 className: 'leaflet-div-icon'
11076         },
11077
11078         createIcon: function (oldIcon) {
11079                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
11080                     options = this.options;
11081
11082                 if (options.html instanceof Element) {
11083                         empty(div);
11084                         div.appendChild(options.html);
11085                 } else {
11086                         div.innerHTML = options.html !== false ? options.html : '';
11087                 }
11088
11089                 if (options.bgPos) {
11090                         var bgPos = toPoint(options.bgPos);
11091                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
11092                 }
11093                 this._setIconStyles(div, 'icon');
11094
11095                 return div;
11096         },
11097
11098         createShadow: function () {
11099                 return null;
11100         }
11101   });
11102
11103   // @factory L.divIcon(options: DivIcon options)
11104   // Creates a `DivIcon` instance with the given options.
11105   function divIcon(options) {
11106         return new DivIcon(options);
11107   }
11108
11109   Icon.Default = IconDefault;
11110
11111   /*
11112    * @class GridLayer
11113    * @inherits Layer
11114    * @aka L.GridLayer
11115    *
11116    * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
11117    * 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.
11118    *
11119    *
11120    * @section Synchronous usage
11121    * @example
11122    *
11123    * 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.
11124    *
11125    * ```js
11126    * var CanvasLayer = L.GridLayer.extend({
11127    *     createTile: function(coords){
11128    *         // create a <canvas> element for drawing
11129    *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11130    *
11131    *         // setup tile width and height according to the options
11132    *         var size = this.getTileSize();
11133    *         tile.width = size.x;
11134    *         tile.height = size.y;
11135    *
11136    *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
11137    *         var ctx = tile.getContext('2d');
11138    *
11139    *         // return the tile so it can be rendered on screen
11140    *         return tile;
11141    *     }
11142    * });
11143    * ```
11144    *
11145    * @section Asynchronous usage
11146    * @example
11147    *
11148    * 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.
11149    *
11150    * ```js
11151    * var CanvasLayer = L.GridLayer.extend({
11152    *     createTile: function(coords, done){
11153    *         var error;
11154    *
11155    *         // create a <canvas> element for drawing
11156    *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11157    *
11158    *         // setup tile width and height according to the options
11159    *         var size = this.getTileSize();
11160    *         tile.width = size.x;
11161    *         tile.height = size.y;
11162    *
11163    *         // draw something asynchronously and pass the tile to the done() callback
11164    *         setTimeout(function() {
11165    *             done(error, tile);
11166    *         }, 1000);
11167    *
11168    *         return tile;
11169    *     }
11170    * });
11171    * ```
11172    *
11173    * @section
11174    */
11175
11176
11177   var GridLayer = Layer.extend({
11178
11179         // @section
11180         // @aka GridLayer options
11181         options: {
11182                 // @option tileSize: Number|Point = 256
11183                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
11184                 tileSize: 256,
11185
11186                 // @option opacity: Number = 1.0
11187                 // Opacity of the tiles. Can be used in the `createTile()` function.
11188                 opacity: 1,
11189
11190                 // @option updateWhenIdle: Boolean = (depends)
11191                 // Load new tiles only when panning ends.
11192                 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
11193                 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
11194                 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
11195                 updateWhenIdle: Browser.mobile,
11196
11197                 // @option updateWhenZooming: Boolean = true
11198                 // 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.
11199                 updateWhenZooming: true,
11200
11201                 // @option updateInterval: Number = 200
11202                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
11203                 updateInterval: 200,
11204
11205                 // @option zIndex: Number = 1
11206                 // The explicit zIndex of the tile layer.
11207                 zIndex: 1,
11208
11209                 // @option bounds: LatLngBounds = undefined
11210                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
11211                 bounds: null,
11212
11213                 // @option minZoom: Number = 0
11214                 // The minimum zoom level down to which this layer will be displayed (inclusive).
11215                 minZoom: 0,
11216
11217                 // @option maxZoom: Number = undefined
11218                 // The maximum zoom level up to which this layer will be displayed (inclusive).
11219                 maxZoom: undefined,
11220
11221                 // @option maxNativeZoom: Number = undefined
11222                 // Maximum zoom number the tile source has available. If it is specified,
11223                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
11224                 // from `maxNativeZoom` level and auto-scaled.
11225                 maxNativeZoom: undefined,
11226
11227                 // @option minNativeZoom: Number = undefined
11228                 // Minimum zoom number the tile source has available. If it is specified,
11229                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
11230                 // from `minNativeZoom` level and auto-scaled.
11231                 minNativeZoom: undefined,
11232
11233                 // @option noWrap: Boolean = false
11234                 // Whether the layer is wrapped around the antimeridian. If `true`, the
11235                 // GridLayer will only be displayed once at low zoom levels. Has no
11236                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
11237                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
11238                 // tiles outside the CRS limits.
11239                 noWrap: false,
11240
11241                 // @option pane: String = 'tilePane'
11242                 // `Map pane` where the grid layer will be added.
11243                 pane: 'tilePane',
11244
11245                 // @option className: String = ''
11246                 // A custom class name to assign to the tile layer. Empty by default.
11247                 className: '',
11248
11249                 // @option keepBuffer: Number = 2
11250                 // When panning the map, keep this many rows and columns of tiles before unloading them.
11251                 keepBuffer: 2
11252         },
11253
11254         initialize: function (options) {
11255                 setOptions(this, options);
11256         },
11257
11258         onAdd: function () {
11259                 this._initContainer();
11260
11261                 this._levels = {};
11262                 this._tiles = {};
11263
11264                 this._resetView(); // implicit _update() call
11265         },
11266
11267         beforeAdd: function (map) {
11268                 map._addZoomLimit(this);
11269         },
11270
11271         onRemove: function (map) {
11272                 this._removeAllTiles();
11273                 remove(this._container);
11274                 map._removeZoomLimit(this);
11275                 this._container = null;
11276                 this._tileZoom = undefined;
11277         },
11278
11279         // @method bringToFront: this
11280         // Brings the tile layer to the top of all tile layers.
11281         bringToFront: function () {
11282                 if (this._map) {
11283                         toFront(this._container);
11284                         this._setAutoZIndex(Math.max);
11285                 }
11286                 return this;
11287         },
11288
11289         // @method bringToBack: this
11290         // Brings the tile layer to the bottom of all tile layers.
11291         bringToBack: function () {
11292                 if (this._map) {
11293                         toBack(this._container);
11294                         this._setAutoZIndex(Math.min);
11295                 }
11296                 return this;
11297         },
11298
11299         // @method getContainer: HTMLElement
11300         // Returns the HTML element that contains the tiles for this layer.
11301         getContainer: function () {
11302                 return this._container;
11303         },
11304
11305         // @method setOpacity(opacity: Number): this
11306         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
11307         setOpacity: function (opacity) {
11308                 this.options.opacity = opacity;
11309                 this._updateOpacity();
11310                 return this;
11311         },
11312
11313         // @method setZIndex(zIndex: Number): this
11314         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
11315         setZIndex: function (zIndex) {
11316                 this.options.zIndex = zIndex;
11317                 this._updateZIndex();
11318
11319                 return this;
11320         },
11321
11322         // @method isLoading: Boolean
11323         // Returns `true` if any tile in the grid layer has not finished loading.
11324         isLoading: function () {
11325                 return this._loading;
11326         },
11327
11328         // @method redraw: this
11329         // Causes the layer to clear all the tiles and request them again.
11330         redraw: function () {
11331                 if (this._map) {
11332                         this._removeAllTiles();
11333                         var tileZoom = this._clampZoom(this._map.getZoom());
11334                         if (tileZoom !== this._tileZoom) {
11335                                 this._tileZoom = tileZoom;
11336                                 this._updateLevels();
11337                         }
11338                         this._update();
11339                 }
11340                 return this;
11341         },
11342
11343         getEvents: function () {
11344                 var events = {
11345                         viewprereset: this._invalidateAll,
11346                         viewreset: this._resetView,
11347                         zoom: this._resetView,
11348                         moveend: this._onMoveEnd
11349                 };
11350
11351                 if (!this.options.updateWhenIdle) {
11352                         // update tiles on move, but not more often than once per given interval
11353                         if (!this._onMove) {
11354                                 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
11355                         }
11356
11357                         events.move = this._onMove;
11358                 }
11359
11360                 if (this._zoomAnimated) {
11361                         events.zoomanim = this._animateZoom;
11362                 }
11363
11364                 return events;
11365         },
11366
11367         // @section Extension methods
11368         // Layers extending `GridLayer` shall reimplement the following method.
11369         // @method createTile(coords: Object, done?: Function): HTMLElement
11370         // Called only internally, must be overridden by classes extending `GridLayer`.
11371         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
11372         // is specified, it must be called when the tile has finished loading and drawing.
11373         createTile: function () {
11374                 return document.createElement('div');
11375         },
11376
11377         // @section
11378         // @method getTileSize: Point
11379         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
11380         getTileSize: function () {
11381                 var s = this.options.tileSize;
11382                 return s instanceof Point ? s : new Point(s, s);
11383         },
11384
11385         _updateZIndex: function () {
11386                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
11387                         this._container.style.zIndex = this.options.zIndex;
11388                 }
11389         },
11390
11391         _setAutoZIndex: function (compare) {
11392                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
11393
11394                 var layers = this.getPane().children,
11395                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
11396
11397                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
11398
11399                         zIndex = layers[i].style.zIndex;
11400
11401                         if (layers[i] !== this._container && zIndex) {
11402                                 edgeZIndex = compare(edgeZIndex, +zIndex);
11403                         }
11404                 }
11405
11406                 if (isFinite(edgeZIndex)) {
11407                         this.options.zIndex = edgeZIndex + compare(-1, 1);
11408                         this._updateZIndex();
11409                 }
11410         },
11411
11412         _updateOpacity: function () {
11413                 if (!this._map) { return; }
11414
11415                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
11416                 if (Browser.ielt9) { return; }
11417
11418                 setOpacity(this._container, this.options.opacity);
11419
11420                 var now = +new Date(),
11421                     nextFrame = false,
11422                     willPrune = false;
11423
11424                 for (var key in this._tiles) {
11425                         var tile = this._tiles[key];
11426                         if (!tile.current || !tile.loaded) { continue; }
11427
11428                         var fade = Math.min(1, (now - tile.loaded) / 200);
11429
11430                         setOpacity(tile.el, fade);
11431                         if (fade < 1) {
11432                                 nextFrame = true;
11433                         } else {
11434                                 if (tile.active) {
11435                                         willPrune = true;
11436                                 } else {
11437                                         this._onOpaqueTile(tile);
11438                                 }
11439                                 tile.active = true;
11440                         }
11441                 }
11442
11443                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
11444
11445                 if (nextFrame) {
11446                         cancelAnimFrame(this._fadeFrame);
11447                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11448                 }
11449         },
11450
11451         _onOpaqueTile: falseFn,
11452
11453         _initContainer: function () {
11454                 if (this._container) { return; }
11455
11456                 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11457                 this._updateZIndex();
11458
11459                 if (this.options.opacity < 1) {
11460                         this._updateOpacity();
11461                 }
11462
11463                 this.getPane().appendChild(this._container);
11464         },
11465
11466         _updateLevels: function () {
11467
11468                 var zoom = this._tileZoom,
11469                     maxZoom = this.options.maxZoom;
11470
11471                 if (zoom === undefined) { return undefined; }
11472
11473                 for (var z in this._levels) {
11474                         z = Number(z);
11475                         if (this._levels[z].el.children.length || z === zoom) {
11476                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11477                                 this._onUpdateLevel(z);
11478                         } else {
11479                                 remove(this._levels[z].el);
11480                                 this._removeTilesAtZoom(z);
11481                                 this._onRemoveLevel(z);
11482                                 delete this._levels[z];
11483                         }
11484                 }
11485
11486                 var level = this._levels[zoom],
11487                     map = this._map;
11488
11489                 if (!level) {
11490                         level = this._levels[zoom] = {};
11491
11492                         level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11493                         level.el.style.zIndex = maxZoom;
11494
11495                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11496                         level.zoom = zoom;
11497
11498                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
11499
11500                         // force the browser to consider the newly added element for transition
11501                         falseFn(level.el.offsetWidth);
11502
11503                         this._onCreateLevel(level);
11504                 }
11505
11506                 this._level = level;
11507
11508                 return level;
11509         },
11510
11511         _onUpdateLevel: falseFn,
11512
11513         _onRemoveLevel: falseFn,
11514
11515         _onCreateLevel: falseFn,
11516
11517         _pruneTiles: function () {
11518                 if (!this._map) {
11519                         return;
11520                 }
11521
11522                 var key, tile;
11523
11524                 var zoom = this._map.getZoom();
11525                 if (zoom > this.options.maxZoom ||
11526                         zoom < this.options.minZoom) {
11527                         this._removeAllTiles();
11528                         return;
11529                 }
11530
11531                 for (key in this._tiles) {
11532                         tile = this._tiles[key];
11533                         tile.retain = tile.current;
11534                 }
11535
11536                 for (key in this._tiles) {
11537                         tile = this._tiles[key];
11538                         if (tile.current && !tile.active) {
11539                                 var coords = tile.coords;
11540                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11541                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11542                                 }
11543                         }
11544                 }
11545
11546                 for (key in this._tiles) {
11547                         if (!this._tiles[key].retain) {
11548                                 this._removeTile(key);
11549                         }
11550                 }
11551         },
11552
11553         _removeTilesAtZoom: function (zoom) {
11554                 for (var key in this._tiles) {
11555                         if (this._tiles[key].coords.z !== zoom) {
11556                                 continue;
11557                         }
11558                         this._removeTile(key);
11559                 }
11560         },
11561
11562         _removeAllTiles: function () {
11563                 for (var key in this._tiles) {
11564                         this._removeTile(key);
11565                 }
11566         },
11567
11568         _invalidateAll: function () {
11569                 for (var z in this._levels) {
11570                         remove(this._levels[z].el);
11571                         this._onRemoveLevel(Number(z));
11572                         delete this._levels[z];
11573                 }
11574                 this._removeAllTiles();
11575
11576                 this._tileZoom = undefined;
11577         },
11578
11579         _retainParent: function (x, y, z, minZoom) {
11580                 var x2 = Math.floor(x / 2),
11581                     y2 = Math.floor(y / 2),
11582                     z2 = z - 1,
11583                     coords2 = new Point(+x2, +y2);
11584                 coords2.z = +z2;
11585
11586                 var key = this._tileCoordsToKey(coords2),
11587                     tile = this._tiles[key];
11588
11589                 if (tile && tile.active) {
11590                         tile.retain = true;
11591                         return true;
11592
11593                 } else if (tile && tile.loaded) {
11594                         tile.retain = true;
11595                 }
11596
11597                 if (z2 > minZoom) {
11598                         return this._retainParent(x2, y2, z2, minZoom);
11599                 }
11600
11601                 return false;
11602         },
11603
11604         _retainChildren: function (x, y, z, maxZoom) {
11605
11606                 for (var i = 2 * x; i < 2 * x + 2; i++) {
11607                         for (var j = 2 * y; j < 2 * y + 2; j++) {
11608
11609                                 var coords = new Point(i, j);
11610                                 coords.z = z + 1;
11611
11612                                 var key = this._tileCoordsToKey(coords),
11613                                     tile = this._tiles[key];
11614
11615                                 if (tile && tile.active) {
11616                                         tile.retain = true;
11617                                         continue;
11618
11619                                 } else if (tile && tile.loaded) {
11620                                         tile.retain = true;
11621                                 }
11622
11623                                 if (z + 1 < maxZoom) {
11624                                         this._retainChildren(i, j, z + 1, maxZoom);
11625                                 }
11626                         }
11627                 }
11628         },
11629
11630         _resetView: function (e) {
11631                 var animating = e && (e.pinch || e.flyTo);
11632                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11633         },
11634
11635         _animateZoom: function (e) {
11636                 this._setView(e.center, e.zoom, true, e.noUpdate);
11637         },
11638
11639         _clampZoom: function (zoom) {
11640                 var options = this.options;
11641
11642                 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11643                         return options.minNativeZoom;
11644                 }
11645
11646                 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11647                         return options.maxNativeZoom;
11648                 }
11649
11650                 return zoom;
11651         },
11652
11653         _setView: function (center, zoom, noPrune, noUpdate) {
11654                 var tileZoom = Math.round(zoom);
11655                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11656                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11657                         tileZoom = undefined;
11658                 } else {
11659                         tileZoom = this._clampZoom(tileZoom);
11660                 }
11661
11662                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11663
11664                 if (!noUpdate || tileZoomChanged) {
11665
11666                         this._tileZoom = tileZoom;
11667
11668                         if (this._abortLoading) {
11669                                 this._abortLoading();
11670                         }
11671
11672                         this._updateLevels();
11673                         this._resetGrid();
11674
11675                         if (tileZoom !== undefined) {
11676                                 this._update(center);
11677                         }
11678
11679                         if (!noPrune) {
11680                                 this._pruneTiles();
11681                         }
11682
11683                         // Flag to prevent _updateOpacity from pruning tiles during
11684                         // a zoom anim or a pinch gesture
11685                         this._noPrune = !!noPrune;
11686                 }
11687
11688                 this._setZoomTransforms(center, zoom);
11689         },
11690
11691         _setZoomTransforms: function (center, zoom) {
11692                 for (var i in this._levels) {
11693                         this._setZoomTransform(this._levels[i], center, zoom);
11694                 }
11695         },
11696
11697         _setZoomTransform: function (level, center, zoom) {
11698                 var scale = this._map.getZoomScale(zoom, level.zoom),
11699                     translate = level.origin.multiplyBy(scale)
11700                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11701
11702                 if (Browser.any3d) {
11703                         setTransform(level.el, translate, scale);
11704                 } else {
11705                         setPosition(level.el, translate);
11706                 }
11707         },
11708
11709         _resetGrid: function () {
11710                 var map = this._map,
11711                     crs = map.options.crs,
11712                     tileSize = this._tileSize = this.getTileSize(),
11713                     tileZoom = this._tileZoom;
11714
11715                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11716                 if (bounds) {
11717                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
11718                 }
11719
11720                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11721                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11722                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11723                 ];
11724                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11725                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11726                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11727                 ];
11728         },
11729
11730         _onMoveEnd: function () {
11731                 if (!this._map || this._map._animatingZoom) { return; }
11732
11733                 this._update();
11734         },
11735
11736         _getTiledPixelBounds: function (center) {
11737                 var map = this._map,
11738                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11739                     scale = map.getZoomScale(mapZoom, this._tileZoom),
11740                     pixelCenter = map.project(center, this._tileZoom).floor(),
11741                     halfSize = map.getSize().divideBy(scale * 2);
11742
11743                 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11744         },
11745
11746         // Private method to load tiles in the grid's active zoom level according to map bounds
11747         _update: function (center) {
11748                 var map = this._map;
11749                 if (!map) { return; }
11750                 var zoom = this._clampZoom(map.getZoom());
11751
11752                 if (center === undefined) { center = map.getCenter(); }
11753                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
11754
11755                 var pixelBounds = this._getTiledPixelBounds(center),
11756                     tileRange = this._pxBoundsToTileRange(pixelBounds),
11757                     tileCenter = tileRange.getCenter(),
11758                     queue = [],
11759                     margin = this.options.keepBuffer,
11760                     noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11761                                               tileRange.getTopRight().add([margin, -margin]));
11762
11763                 // Sanity check: panic if the tile range contains Infinity somewhere.
11764                 if (!(isFinite(tileRange.min.x) &&
11765                       isFinite(tileRange.min.y) &&
11766                       isFinite(tileRange.max.x) &&
11767                       isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11768
11769                 for (var key in this._tiles) {
11770                         var c = this._tiles[key].coords;
11771                         if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11772                                 this._tiles[key].current = false;
11773                         }
11774                 }
11775
11776                 // _update just loads more tiles. If the tile zoom level differs too much
11777                 // from the map's, let _setView reset levels and prune old tiles.
11778                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11779
11780                 // create a queue of coordinates to load tiles from
11781                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11782                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11783                                 var coords = new Point(i, j);
11784                                 coords.z = this._tileZoom;
11785
11786                                 if (!this._isValidTile(coords)) { continue; }
11787
11788                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
11789                                 if (tile) {
11790                                         tile.current = true;
11791                                 } else {
11792                                         queue.push(coords);
11793                                 }
11794                         }
11795                 }
11796
11797                 // sort tile queue to load tiles in order of their distance to center
11798                 queue.sort(function (a, b) {
11799                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11800                 });
11801
11802                 if (queue.length !== 0) {
11803                         // if it's the first batch of tiles to load
11804                         if (!this._loading) {
11805                                 this._loading = true;
11806                                 // @event loading: Event
11807                                 // Fired when the grid layer starts loading tiles.
11808                                 this.fire('loading');
11809                         }
11810
11811                         // create DOM fragment to append tiles in one batch
11812                         var fragment = document.createDocumentFragment();
11813
11814                         for (i = 0; i < queue.length; i++) {
11815                                 this._addTile(queue[i], fragment);
11816                         }
11817
11818                         this._level.el.appendChild(fragment);
11819                 }
11820         },
11821
11822         _isValidTile: function (coords) {
11823                 var crs = this._map.options.crs;
11824
11825                 if (!crs.infinite) {
11826                         // don't load tile if it's out of bounds and not wrapped
11827                         var bounds = this._globalTileRange;
11828                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11829                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11830                 }
11831
11832                 if (!this.options.bounds) { return true; }
11833
11834                 // don't load tile if it doesn't intersect the bounds in options
11835                 var tileBounds = this._tileCoordsToBounds(coords);
11836                 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11837         },
11838
11839         _keyToBounds: function (key) {
11840                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11841         },
11842
11843         _tileCoordsToNwSe: function (coords) {
11844                 var map = this._map,
11845                     tileSize = this.getTileSize(),
11846                     nwPoint = coords.scaleBy(tileSize),
11847                     sePoint = nwPoint.add(tileSize),
11848                     nw = map.unproject(nwPoint, coords.z),
11849                     se = map.unproject(sePoint, coords.z);
11850                 return [nw, se];
11851         },
11852
11853         // converts tile coordinates to its geographical bounds
11854         _tileCoordsToBounds: function (coords) {
11855                 var bp = this._tileCoordsToNwSe(coords),
11856                     bounds = new LatLngBounds(bp[0], bp[1]);
11857
11858                 if (!this.options.noWrap) {
11859                         bounds = this._map.wrapLatLngBounds(bounds);
11860                 }
11861                 return bounds;
11862         },
11863         // converts tile coordinates to key for the tile cache
11864         _tileCoordsToKey: function (coords) {
11865                 return coords.x + ':' + coords.y + ':' + coords.z;
11866         },
11867
11868         // converts tile cache key to coordinates
11869         _keyToTileCoords: function (key) {
11870                 var k = key.split(':'),
11871                     coords = new Point(+k[0], +k[1]);
11872                 coords.z = +k[2];
11873                 return coords;
11874         },
11875
11876         _removeTile: function (key) {
11877                 var tile = this._tiles[key];
11878                 if (!tile) { return; }
11879
11880                 remove(tile.el);
11881
11882                 delete this._tiles[key];
11883
11884                 // @event tileunload: TileEvent
11885                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11886                 this.fire('tileunload', {
11887                         tile: tile.el,
11888                         coords: this._keyToTileCoords(key)
11889                 });
11890         },
11891
11892         _initTile: function (tile) {
11893                 addClass(tile, 'leaflet-tile');
11894
11895                 var tileSize = this.getTileSize();
11896                 tile.style.width = tileSize.x + 'px';
11897                 tile.style.height = tileSize.y + 'px';
11898
11899                 tile.onselectstart = falseFn;
11900                 tile.onmousemove = falseFn;
11901
11902                 // update opacity on tiles in IE7-8 because of filter inheritance problems
11903                 if (Browser.ielt9 && this.options.opacity < 1) {
11904                         setOpacity(tile, this.options.opacity);
11905                 }
11906         },
11907
11908         _addTile: function (coords, container) {
11909                 var tilePos = this._getTilePos(coords),
11910                     key = this._tileCoordsToKey(coords);
11911
11912                 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11913
11914                 this._initTile(tile);
11915
11916                 // if createTile is defined with a second argument ("done" callback),
11917                 // we know that tile is async and will be ready later; otherwise
11918                 if (this.createTile.length < 2) {
11919                         // mark tile as ready, but delay one frame for opacity animation to happen
11920                         requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11921                 }
11922
11923                 setPosition(tile, tilePos);
11924
11925                 // save tile in cache
11926                 this._tiles[key] = {
11927                         el: tile,
11928                         coords: coords,
11929                         current: true
11930                 };
11931
11932                 container.appendChild(tile);
11933                 // @event tileloadstart: TileEvent
11934                 // Fired when a tile is requested and starts loading.
11935                 this.fire('tileloadstart', {
11936                         tile: tile,
11937                         coords: coords
11938                 });
11939         },
11940
11941         _tileReady: function (coords, err, tile) {
11942                 if (err) {
11943                         // @event tileerror: TileErrorEvent
11944                         // Fired when there is an error loading a tile.
11945                         this.fire('tileerror', {
11946                                 error: err,
11947                                 tile: tile,
11948                                 coords: coords
11949                         });
11950                 }
11951
11952                 var key = this._tileCoordsToKey(coords);
11953
11954                 tile = this._tiles[key];
11955                 if (!tile) { return; }
11956
11957                 tile.loaded = +new Date();
11958                 if (this._map._fadeAnimated) {
11959                         setOpacity(tile.el, 0);
11960                         cancelAnimFrame(this._fadeFrame);
11961                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11962                 } else {
11963                         tile.active = true;
11964                         this._pruneTiles();
11965                 }
11966
11967                 if (!err) {
11968                         addClass(tile.el, 'leaflet-tile-loaded');
11969
11970                         // @event tileload: TileEvent
11971                         // Fired when a tile loads.
11972                         this.fire('tileload', {
11973                                 tile: tile.el,
11974                                 coords: coords
11975                         });
11976                 }
11977
11978                 if (this._noTilesToLoad()) {
11979                         this._loading = false;
11980                         // @event load: Event
11981                         // Fired when the grid layer loaded all visible tiles.
11982                         this.fire('load');
11983
11984                         if (Browser.ielt9 || !this._map._fadeAnimated) {
11985                                 requestAnimFrame(this._pruneTiles, this);
11986                         } else {
11987                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11988                                 // to trigger a pruning.
11989                                 setTimeout(bind(this._pruneTiles, this), 250);
11990                         }
11991                 }
11992         },
11993
11994         _getTilePos: function (coords) {
11995                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11996         },
11997
11998         _wrapCoords: function (coords) {
11999                 var newCoords = new Point(
12000                         this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
12001                         this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
12002                 newCoords.z = coords.z;
12003                 return newCoords;
12004         },
12005
12006         _pxBoundsToTileRange: function (bounds) {
12007                 var tileSize = this.getTileSize();
12008                 return new Bounds(
12009                         bounds.min.unscaleBy(tileSize).floor(),
12010                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
12011         },
12012
12013         _noTilesToLoad: function () {
12014                 for (var key in this._tiles) {
12015                         if (!this._tiles[key].loaded) { return false; }
12016                 }
12017                 return true;
12018         }
12019   });
12020
12021   // @factory L.gridLayer(options?: GridLayer options)
12022   // Creates a new instance of GridLayer with the supplied options.
12023   function gridLayer(options) {
12024         return new GridLayer(options);
12025   }
12026
12027   /*\r
12028    * @class TileLayer\r
12029    * @inherits GridLayer\r
12030    * @aka L.TileLayer\r
12031    * 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`.\r
12032    *\r
12033    * @example\r
12034    *\r
12035    * ```js\r
12036    * L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
12037    * ```\r
12038    *\r
12039    * @section URL template\r
12040    * @example\r
12041    *\r
12042    * A string of the following form:\r
12043    *\r
12044    * ```\r
12045    * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'\r
12046    * ```\r
12047    *\r
12048    * `{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.\r
12049    *\r
12050    * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:\r
12051    *\r
12052    * ```\r
12053    * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});\r
12054    * ```\r
12055    */\r
12056 \r
12057 \r
12058   var TileLayer = GridLayer.extend({\r
12059 \r
12060         // @section\r
12061         // @aka TileLayer options\r
12062         options: {\r
12063                 // @option minZoom: Number = 0\r
12064                 // The minimum zoom level down to which this layer will be displayed (inclusive).\r
12065                 minZoom: 0,\r
12066 \r
12067                 // @option maxZoom: Number = 18\r
12068                 // The maximum zoom level up to which this layer will be displayed (inclusive).\r
12069                 maxZoom: 18,\r
12070 \r
12071                 // @option subdomains: String|String[] = 'abc'\r
12072                 // 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.\r
12073                 subdomains: 'abc',\r
12074 \r
12075                 // @option errorTileUrl: String = ''\r
12076                 // URL to the tile image to show in place of the tile that failed to load.\r
12077                 errorTileUrl: '',\r
12078 \r
12079                 // @option zoomOffset: Number = 0\r
12080                 // The zoom number used in tile URLs will be offset with this value.\r
12081                 zoomOffset: 0,\r
12082 \r
12083                 // @option tms: Boolean = false\r
12084                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).\r
12085                 tms: false,\r
12086 \r
12087                 // @option zoomReverse: Boolean = false\r
12088                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)\r
12089                 zoomReverse: false,\r
12090 \r
12091                 // @option detectRetina: Boolean = false\r
12092                 // 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.\r
12093                 detectRetina: false,\r
12094 \r
12095                 // @option crossOrigin: Boolean|String = false\r
12096                 // Whether the crossOrigin attribute will be added to the tiles.\r
12097                 // 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.\r
12098                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.\r
12099                 crossOrigin: false,\r
12100 \r
12101                 // @option referrerPolicy: Boolean|String = false\r
12102                 // Whether the referrerPolicy attribute will be added to the tiles.\r
12103                 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.\r
12104                 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer\r
12105                 // (e.g. to validate an API token).\r
12106                 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.\r
12107                 referrerPolicy: false\r
12108         },\r
12109 \r
12110         initialize: function (url, options) {\r
12111 \r
12112                 this._url = url;\r
12113 \r
12114                 options = setOptions(this, options);\r
12115 \r
12116                 // detecting retina displays, adjusting tileSize and zoom levels\r
12117                 if (options.detectRetina && Browser.retina && options.maxZoom > 0) {\r
12118 \r
12119                         options.tileSize = Math.floor(options.tileSize / 2);\r
12120 \r
12121                         if (!options.zoomReverse) {\r
12122                                 options.zoomOffset++;\r
12123                                 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);\r
12124                         } else {\r
12125                                 options.zoomOffset--;\r
12126                                 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);\r
12127                         }\r
12128 \r
12129                         options.minZoom = Math.max(0, options.minZoom);\r
12130                 } else if (!options.zoomReverse) {\r
12131                         // make sure maxZoom is gte minZoom\r
12132                         options.maxZoom = Math.max(options.minZoom, options.maxZoom);\r
12133                 } else {\r
12134                         // make sure minZoom is lte maxZoom\r
12135                         options.minZoom = Math.min(options.maxZoom, options.minZoom);\r
12136                 }\r
12137 \r
12138                 if (typeof options.subdomains === 'string') {\r
12139                         options.subdomains = options.subdomains.split('');\r
12140                 }\r
12141 \r
12142                 this.on('tileunload', this._onTileRemove);\r
12143         },\r
12144 \r
12145         // @method setUrl(url: String, noRedraw?: Boolean): this\r
12146         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).\r
12147         // If the URL does not change, the layer will not be redrawn unless\r
12148         // the noRedraw parameter is set to false.\r
12149         setUrl: function (url, noRedraw) {\r
12150                 if (this._url === url && noRedraw === undefined) {\r
12151                         noRedraw = true;\r
12152                 }\r
12153 \r
12154                 this._url = url;\r
12155 \r
12156                 if (!noRedraw) {\r
12157                         this.redraw();\r
12158                 }\r
12159                 return this;\r
12160         },\r
12161 \r
12162         // @method createTile(coords: Object, done?: Function): HTMLElement\r
12163         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)\r
12164         // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`\r
12165         // callback is called when the tile has been loaded.\r
12166         createTile: function (coords, done) {\r
12167                 var tile = document.createElement('img');\r
12168 \r
12169                 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));\r
12170                 on(tile, 'error', bind(this._tileOnError, this, done, tile));\r
12171 \r
12172                 if (this.options.crossOrigin || this.options.crossOrigin === '') {\r
12173                         tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;\r
12174                 }\r
12175 \r
12176                 // for this new option we follow the documented behavior\r
12177                 // more closely by only setting the property when string\r
12178                 if (typeof this.options.referrerPolicy === 'string') {\r
12179                         tile.referrerPolicy = this.options.referrerPolicy;\r
12180                 }\r
12181 \r
12182                 // The alt attribute is set to the empty string,\r
12183                 // allowing screen readers to ignore the decorative image tiles.\r
12184                 // https://www.w3.org/WAI/tutorials/images/decorative/\r
12185                 // https://www.w3.org/TR/html-aria/#el-img-empty-alt\r
12186                 tile.alt = '';\r
12187 \r
12188                 tile.src = this.getTileUrl(coords);\r
12189 \r
12190                 return tile;\r
12191         },\r
12192 \r
12193         // @section Extension methods\r
12194         // @uninheritable\r
12195         // Layers extending `TileLayer` might reimplement the following method.\r
12196         // @method getTileUrl(coords: Object): String\r
12197         // Called only internally, returns the URL for a tile given its coordinates.\r
12198         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.\r
12199         getTileUrl: function (coords) {\r
12200                 var data = {\r
12201                         r: Browser.retina ? '@2x' : '',\r
12202                         s: this._getSubdomain(coords),\r
12203                         x: coords.x,\r
12204                         y: coords.y,\r
12205                         z: this._getZoomForUrl()\r
12206                 };\r
12207                 if (this._map && !this._map.options.crs.infinite) {\r
12208                         var invertedY = this._globalTileRange.max.y - coords.y;\r
12209                         if (this.options.tms) {\r
12210                                 data['y'] = invertedY;\r
12211                         }\r
12212                         data['-y'] = invertedY;\r
12213                 }\r
12214 \r
12215                 return template(this._url, extend(data, this.options));\r
12216         },\r
12217 \r
12218         _tileOnLoad: function (done, tile) {\r
12219                 // For https://github.com/Leaflet/Leaflet/issues/3332\r
12220                 if (Browser.ielt9) {\r
12221                         setTimeout(bind(done, this, null, tile), 0);\r
12222                 } else {\r
12223                         done(null, tile);\r
12224                 }\r
12225         },\r
12226 \r
12227         _tileOnError: function (done, tile, e) {\r
12228                 var errorUrl = this.options.errorTileUrl;\r
12229                 if (errorUrl && tile.getAttribute('src') !== errorUrl) {\r
12230                         tile.src = errorUrl;\r
12231                 }\r
12232                 done(e, tile);\r
12233         },\r
12234 \r
12235         _onTileRemove: function (e) {\r
12236                 e.tile.onload = null;\r
12237         },\r
12238 \r
12239         _getZoomForUrl: function () {\r
12240                 var zoom = this._tileZoom,\r
12241                 maxZoom = this.options.maxZoom,\r
12242                 zoomReverse = this.options.zoomReverse,\r
12243                 zoomOffset = this.options.zoomOffset;\r
12244 \r
12245                 if (zoomReverse) {\r
12246                         zoom = maxZoom - zoom;\r
12247                 }\r
12248 \r
12249                 return zoom + zoomOffset;\r
12250         },\r
12251 \r
12252         _getSubdomain: function (tilePoint) {\r
12253                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;\r
12254                 return this.options.subdomains[index];\r
12255         },\r
12256 \r
12257         // stops loading all tiles in the background layer\r
12258         _abortLoading: function () {\r
12259                 var i, tile;\r
12260                 for (i in this._tiles) {\r
12261                         if (this._tiles[i].coords.z !== this._tileZoom) {\r
12262                                 tile = this._tiles[i].el;\r
12263 \r
12264                                 tile.onload = falseFn;\r
12265                                 tile.onerror = falseFn;\r
12266 \r
12267                                 if (!tile.complete) {\r
12268                                         tile.src = emptyImageUrl;\r
12269                                         var coords = this._tiles[i].coords;\r
12270                                         remove(tile);\r
12271                                         delete this._tiles[i];\r
12272                                         // @event tileabort: TileEvent\r
12273                                         // Fired when a tile was loading but is now not wanted.\r
12274                                         this.fire('tileabort', {\r
12275                                                 tile: tile,\r
12276                                                 coords: coords\r
12277                                         });\r
12278                                 }\r
12279                         }\r
12280                 }\r
12281         },\r
12282 \r
12283         _removeTile: function (key) {\r
12284                 var tile = this._tiles[key];\r
12285                 if (!tile) { return; }\r
12286 \r
12287                 // Cancels any pending http requests associated with the tile\r
12288                 tile.el.setAttribute('src', emptyImageUrl);\r
12289 \r
12290                 return GridLayer.prototype._removeTile.call(this, key);\r
12291         },\r
12292 \r
12293         _tileReady: function (coords, err, tile) {\r
12294                 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {\r
12295                         return;\r
12296                 }\r
12297 \r
12298                 return GridLayer.prototype._tileReady.call(this, coords, err, tile);\r
12299         }\r
12300   });\r
12301 \r
12302 \r
12303   // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)\r
12304   // Instantiates a tile layer object given a `URL template` and optionally an options object.\r
12305 \r
12306   function tileLayer(url, options) {\r
12307         return new TileLayer(url, options);\r
12308   }
12309
12310   /*\r
12311    * @class TileLayer.WMS\r
12312    * @inherits TileLayer\r
12313    * @aka L.TileLayer.WMS\r
12314    * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.\r
12315    *\r
12316    * @example\r
12317    *\r
12318    * ```js\r
12319    * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {\r
12320    *    layers: 'nexrad-n0r-900913',\r
12321    *    format: 'image/png',\r
12322    *    transparent: true,\r
12323    *    attribution: "Weather data © 2012 IEM Nexrad"\r
12324    * });\r
12325    * ```\r
12326    */\r
12327 \r
12328   var TileLayerWMS = TileLayer.extend({\r
12329 \r
12330         // @section\r
12331         // @aka TileLayer.WMS options\r
12332         // If any custom options not documented here are used, they will be sent to the\r
12333         // WMS server as extra parameters in each request URL. This can be useful for\r
12334         // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).\r
12335         defaultWmsParams: {\r
12336                 service: 'WMS',\r
12337                 request: 'GetMap',\r
12338 \r
12339                 // @option layers: String = ''\r
12340                 // **(required)** Comma-separated list of WMS layers to show.\r
12341                 layers: '',\r
12342 \r
12343                 // @option styles: String = ''\r
12344                 // Comma-separated list of WMS styles.\r
12345                 styles: '',\r
12346 \r
12347                 // @option format: String = 'image/jpeg'\r
12348                 // WMS image format (use `'image/png'` for layers with transparency).\r
12349                 format: 'image/jpeg',\r
12350 \r
12351                 // @option transparent: Boolean = false\r
12352                 // If `true`, the WMS service will return images with transparency.\r
12353                 transparent: false,\r
12354 \r
12355                 // @option version: String = '1.1.1'\r
12356                 // Version of the WMS service to use\r
12357                 version: '1.1.1'\r
12358         },\r
12359 \r
12360         options: {\r
12361                 // @option crs: CRS = null\r
12362                 // Coordinate Reference System to use for the WMS requests, defaults to\r
12363                 // map CRS. Don't change this if you're not sure what it means.\r
12364                 crs: null,\r
12365 \r
12366                 // @option uppercase: Boolean = false\r
12367                 // If `true`, WMS request parameter keys will be uppercase.\r
12368                 uppercase: false\r
12369         },\r
12370 \r
12371         initialize: function (url, options) {\r
12372 \r
12373                 this._url = url;\r
12374 \r
12375                 var wmsParams = extend({}, this.defaultWmsParams);\r
12376 \r
12377                 // all keys that are not TileLayer options go to WMS params\r
12378                 for (var i in options) {\r
12379                         if (!(i in this.options)) {\r
12380                                 wmsParams[i] = options[i];\r
12381                         }\r
12382                 }\r
12383 \r
12384                 options = setOptions(this, options);\r
12385 \r
12386                 var realRetina = options.detectRetina && Browser.retina ? 2 : 1;\r
12387                 var tileSize = this.getTileSize();\r
12388                 wmsParams.width = tileSize.x * realRetina;\r
12389                 wmsParams.height = tileSize.y * realRetina;\r
12390 \r
12391                 this.wmsParams = wmsParams;\r
12392         },\r
12393 \r
12394         onAdd: function (map) {\r
12395 \r
12396                 this._crs = this.options.crs || map.options.crs;\r
12397                 this._wmsVersion = parseFloat(this.wmsParams.version);\r
12398 \r
12399                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';\r
12400                 this.wmsParams[projectionKey] = this._crs.code;\r
12401 \r
12402                 TileLayer.prototype.onAdd.call(this, map);\r
12403         },\r
12404 \r
12405         getTileUrl: function (coords) {\r
12406 \r
12407                 var tileBounds = this._tileCoordsToNwSe(coords),\r
12408                     crs = this._crs,\r
12409                     bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),\r
12410                     min = bounds.min,\r
12411                     max = bounds.max,\r
12412                     bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?\r
12413                     [min.y, min.x, max.y, max.x] :\r
12414                     [min.x, min.y, max.x, max.y]).join(','),\r
12415                     url = TileLayer.prototype.getTileUrl.call(this, coords);\r
12416                 return url +\r
12417                         getParamString(this.wmsParams, url, this.options.uppercase) +\r
12418                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;\r
12419         },\r
12420 \r
12421         // @method setParams(params: Object, noRedraw?: Boolean): this\r
12422         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).\r
12423         setParams: function (params, noRedraw) {\r
12424 \r
12425                 extend(this.wmsParams, params);\r
12426 \r
12427                 if (!noRedraw) {\r
12428                         this.redraw();\r
12429                 }\r
12430 \r
12431                 return this;\r
12432         }\r
12433   });\r
12434 \r
12435 \r
12436   // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)\r
12437   // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.\r
12438   function tileLayerWMS(url, options) {\r
12439         return new TileLayerWMS(url, options);\r
12440   }
12441
12442   TileLayer.WMS = TileLayerWMS;
12443   tileLayer.wms = tileLayerWMS;
12444
12445   /*
12446    * @class Renderer
12447    * @inherits Layer
12448    * @aka L.Renderer
12449    *
12450    * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
12451    * DOM container of the renderer, its bounds, and its zoom animation.
12452    *
12453    * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
12454    * itself can be added or removed to the map. All paths use a renderer, which can
12455    * be implicit (the map will decide the type of renderer and use it automatically)
12456    * or explicit (using the [`renderer`](#path-renderer) option of the path).
12457    *
12458    * Do not use this class directly, use `SVG` and `Canvas` instead.
12459    *
12460    * @event update: Event
12461    * Fired when the renderer updates its bounds, center and zoom, for example when
12462    * its map has moved
12463    */
12464
12465   var Renderer = Layer.extend({
12466
12467         // @section
12468         // @aka Renderer options
12469         options: {
12470                 // @option padding: Number = 0.1
12471                 // How much to extend the clip area around the map view (relative to its size)
12472                 // e.g. 0.1 would be 10% of map view in each direction
12473                 padding: 0.1
12474         },
12475
12476         initialize: function (options) {
12477                 setOptions(this, options);
12478                 stamp(this);
12479                 this._layers = this._layers || {};
12480         },
12481
12482         onAdd: function () {
12483                 if (!this._container) {
12484                         this._initContainer(); // defined by renderer implementations
12485
12486                         // always keep transform-origin as 0 0
12487                         addClass(this._container, 'leaflet-zoom-animated');
12488                 }
12489
12490                 this.getPane().appendChild(this._container);
12491                 this._update();
12492                 this.on('update', this._updatePaths, this);
12493         },
12494
12495         onRemove: function () {
12496                 this.off('update', this._updatePaths, this);
12497                 this._destroyContainer();
12498         },
12499
12500         getEvents: function () {
12501                 var events = {
12502                         viewreset: this._reset,
12503                         zoom: this._onZoom,
12504                         moveend: this._update,
12505                         zoomend: this._onZoomEnd
12506                 };
12507                 if (this._zoomAnimated) {
12508                         events.zoomanim = this._onAnimZoom;
12509                 }
12510                 return events;
12511         },
12512
12513         _onAnimZoom: function (ev) {
12514                 this._updateTransform(ev.center, ev.zoom);
12515         },
12516
12517         _onZoom: function () {
12518                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12519         },
12520
12521         _updateTransform: function (center, zoom) {
12522                 var scale = this._map.getZoomScale(zoom, this._zoom),
12523                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12524                     currentCenterPoint = this._map.project(this._center, zoom),
12525
12526                     topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
12527                                   .subtract(this._map._getNewPixelOrigin(center, zoom));
12528
12529                 if (Browser.any3d) {
12530                         setTransform(this._container, topLeftOffset, scale);
12531                 } else {
12532                         setPosition(this._container, topLeftOffset);
12533                 }
12534         },
12535
12536         _reset: function () {
12537                 this._update();
12538                 this._updateTransform(this._center, this._zoom);
12539
12540                 for (var id in this._layers) {
12541                         this._layers[id]._reset();
12542                 }
12543         },
12544
12545         _onZoomEnd: function () {
12546                 for (var id in this._layers) {
12547                         this._layers[id]._project();
12548                 }
12549         },
12550
12551         _updatePaths: function () {
12552                 for (var id in this._layers) {
12553                         this._layers[id]._update();
12554                 }
12555         },
12556
12557         _update: function () {
12558                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12559                 // Subclasses are responsible of firing the 'update' event.
12560                 var p = this.options.padding,
12561                     size = this._map.getSize(),
12562                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12563
12564                 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12565
12566                 this._center = this._map.getCenter();
12567                 this._zoom = this._map.getZoom();
12568         }
12569   });
12570
12571   /*
12572    * @class Canvas
12573    * @inherits Renderer
12574    * @aka L.Canvas
12575    *
12576    * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12577    * Inherits `Renderer`.
12578    *
12579    * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
12580    * available in all web browsers, notably IE8, and overlapping geometries might
12581    * not display properly in some edge cases.
12582    *
12583    * @example
12584    *
12585    * Use Canvas by default for all paths in the map:
12586    *
12587    * ```js
12588    * var map = L.map('map', {
12589    *    renderer: L.canvas()
12590    * });
12591    * ```
12592    *
12593    * Use a Canvas renderer with extra padding for specific vector geometries:
12594    *
12595    * ```js
12596    * var map = L.map('map');
12597    * var myRenderer = L.canvas({ padding: 0.5 });
12598    * var line = L.polyline( coordinates, { renderer: myRenderer } );
12599    * var circle = L.circle( center, { renderer: myRenderer } );
12600    * ```
12601    */
12602
12603   var Canvas = Renderer.extend({
12604
12605         // @section
12606         // @aka Canvas options
12607         options: {
12608                 // @option tolerance: Number = 0
12609                 // How much to extend the click tolerance around a path/object on the map.
12610                 tolerance: 0
12611         },
12612
12613         getEvents: function () {
12614                 var events = Renderer.prototype.getEvents.call(this);
12615                 events.viewprereset = this._onViewPreReset;
12616                 return events;
12617         },
12618
12619         _onViewPreReset: function () {
12620                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12621                 this._postponeUpdatePaths = true;
12622         },
12623
12624         onAdd: function () {
12625                 Renderer.prototype.onAdd.call(this);
12626
12627                 // Redraw vectors since canvas is cleared upon removal,
12628                 // in case of removing the renderer itself from the map.
12629                 this._draw();
12630         },
12631
12632         _initContainer: function () {
12633                 var container = this._container = document.createElement('canvas');
12634
12635                 on(container, 'mousemove', this._onMouseMove, this);
12636                 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12637                 on(container, 'mouseout', this._handleMouseOut, this);
12638                 container['_leaflet_disable_events'] = true;
12639
12640                 this._ctx = container.getContext('2d');
12641         },
12642
12643         _destroyContainer: function () {
12644                 cancelAnimFrame(this._redrawRequest);
12645                 delete this._ctx;
12646                 remove(this._container);
12647                 off(this._container);
12648                 delete this._container;
12649         },
12650
12651         _updatePaths: function () {
12652                 if (this._postponeUpdatePaths) { return; }
12653
12654                 var layer;
12655                 this._redrawBounds = null;
12656                 for (var id in this._layers) {
12657                         layer = this._layers[id];
12658                         layer._update();
12659                 }
12660                 this._redraw();
12661         },
12662
12663         _update: function () {
12664                 if (this._map._animatingZoom && this._bounds) { return; }
12665
12666                 Renderer.prototype._update.call(this);
12667
12668                 var b = this._bounds,
12669                     container = this._container,
12670                     size = b.getSize(),
12671                     m = Browser.retina ? 2 : 1;
12672
12673                 setPosition(container, b.min);
12674
12675                 // set canvas size (also clearing it); use double size on retina
12676                 container.width = m * size.x;
12677                 container.height = m * size.y;
12678                 container.style.width = size.x + 'px';
12679                 container.style.height = size.y + 'px';
12680
12681                 if (Browser.retina) {
12682                         this._ctx.scale(2, 2);
12683                 }
12684
12685                 // translate so we use the same path coordinates after canvas element moves
12686                 this._ctx.translate(-b.min.x, -b.min.y);
12687
12688                 // Tell paths to redraw themselves
12689                 this.fire('update');
12690         },
12691
12692         _reset: function () {
12693                 Renderer.prototype._reset.call(this);
12694
12695                 if (this._postponeUpdatePaths) {
12696                         this._postponeUpdatePaths = false;
12697                         this._updatePaths();
12698                 }
12699         },
12700
12701         _initPath: function (layer) {
12702                 this._updateDashArray(layer);
12703                 this._layers[stamp(layer)] = layer;
12704
12705                 var order = layer._order = {
12706                         layer: layer,
12707                         prev: this._drawLast,
12708                         next: null
12709                 };
12710                 if (this._drawLast) { this._drawLast.next = order; }
12711                 this._drawLast = order;
12712                 this._drawFirst = this._drawFirst || this._drawLast;
12713         },
12714
12715         _addPath: function (layer) {
12716                 this._requestRedraw(layer);
12717         },
12718
12719         _removePath: function (layer) {
12720                 var order = layer._order;
12721                 var next = order.next;
12722                 var prev = order.prev;
12723
12724                 if (next) {
12725                         next.prev = prev;
12726                 } else {
12727                         this._drawLast = prev;
12728                 }
12729                 if (prev) {
12730                         prev.next = next;
12731                 } else {
12732                         this._drawFirst = next;
12733                 }
12734
12735                 delete layer._order;
12736
12737                 delete this._layers[stamp(layer)];
12738
12739                 this._requestRedraw(layer);
12740         },
12741
12742         _updatePath: function (layer) {
12743                 // Redraw the union of the layer's old pixel
12744                 // bounds and the new pixel bounds.
12745                 this._extendRedrawBounds(layer);
12746                 layer._project();
12747                 layer._update();
12748                 // The redraw will extend the redraw bounds
12749                 // with the new pixel bounds.
12750                 this._requestRedraw(layer);
12751         },
12752
12753         _updateStyle: function (layer) {
12754                 this._updateDashArray(layer);
12755                 this._requestRedraw(layer);
12756         },
12757
12758         _updateDashArray: function (layer) {
12759                 if (typeof layer.options.dashArray === 'string') {
12760                         var parts = layer.options.dashArray.split(/[, ]+/),
12761                             dashArray = [],
12762                             dashValue,
12763                             i;
12764                         for (i = 0; i < parts.length; i++) {
12765                                 dashValue = Number(parts[i]);
12766                                 // Ignore dash array containing invalid lengths
12767                                 if (isNaN(dashValue)) { return; }
12768                                 dashArray.push(dashValue);
12769                         }
12770                         layer.options._dashArray = dashArray;
12771                 } else {
12772                         layer.options._dashArray = layer.options.dashArray;
12773                 }
12774         },
12775
12776         _requestRedraw: function (layer) {
12777                 if (!this._map) { return; }
12778
12779                 this._extendRedrawBounds(layer);
12780                 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12781         },
12782
12783         _extendRedrawBounds: function (layer) {
12784                 if (layer._pxBounds) {
12785                         var padding = (layer.options.weight || 0) + 1;
12786                         this._redrawBounds = this._redrawBounds || new Bounds();
12787                         this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12788                         this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12789                 }
12790         },
12791
12792         _redraw: function () {
12793                 this._redrawRequest = null;
12794
12795                 if (this._redrawBounds) {
12796                         this._redrawBounds.min._floor();
12797                         this._redrawBounds.max._ceil();
12798                 }
12799
12800                 this._clear(); // clear layers in redraw bounds
12801                 this._draw(); // draw layers
12802
12803                 this._redrawBounds = null;
12804         },
12805
12806         _clear: function () {
12807                 var bounds = this._redrawBounds;
12808                 if (bounds) {
12809                         var size = bounds.getSize();
12810                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12811                 } else {
12812                         this._ctx.save();
12813                         this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12814                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12815                         this._ctx.restore();
12816                 }
12817         },
12818
12819         _draw: function () {
12820                 var layer, bounds = this._redrawBounds;
12821                 this._ctx.save();
12822                 if (bounds) {
12823                         var size = bounds.getSize();
12824                         this._ctx.beginPath();
12825                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12826                         this._ctx.clip();
12827                 }
12828
12829                 this._drawing = true;
12830
12831                 for (var order = this._drawFirst; order; order = order.next) {
12832                         layer = order.layer;
12833                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12834                                 layer._updatePath();
12835                         }
12836                 }
12837
12838                 this._drawing = false;
12839
12840                 this._ctx.restore();  // Restore state before clipping.
12841         },
12842
12843         _updatePoly: function (layer, closed) {
12844                 if (!this._drawing) { return; }
12845
12846                 var i, j, len2, p,
12847                     parts = layer._parts,
12848                     len = parts.length,
12849                     ctx = this._ctx;
12850
12851                 if (!len) { return; }
12852
12853                 ctx.beginPath();
12854
12855                 for (i = 0; i < len; i++) {
12856                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
12857                                 p = parts[i][j];
12858                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12859                         }
12860                         if (closed) {
12861                                 ctx.closePath();
12862                         }
12863                 }
12864
12865                 this._fillStroke(ctx, layer);
12866
12867                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12868         },
12869
12870         _updateCircle: function (layer) {
12871
12872                 if (!this._drawing || layer._empty()) { return; }
12873
12874                 var p = layer._point,
12875                     ctx = this._ctx,
12876                     r = Math.max(Math.round(layer._radius), 1),
12877                     s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12878
12879                 if (s !== 1) {
12880                         ctx.save();
12881                         ctx.scale(1, s);
12882                 }
12883
12884                 ctx.beginPath();
12885                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12886
12887                 if (s !== 1) {
12888                         ctx.restore();
12889                 }
12890
12891                 this._fillStroke(ctx, layer);
12892         },
12893
12894         _fillStroke: function (ctx, layer) {
12895                 var options = layer.options;
12896
12897                 if (options.fill) {
12898                         ctx.globalAlpha = options.fillOpacity;
12899                         ctx.fillStyle = options.fillColor || options.color;
12900                         ctx.fill(options.fillRule || 'evenodd');
12901                 }
12902
12903                 if (options.stroke && options.weight !== 0) {
12904                         if (ctx.setLineDash) {
12905                                 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12906                         }
12907                         ctx.globalAlpha = options.opacity;
12908                         ctx.lineWidth = options.weight;
12909                         ctx.strokeStyle = options.color;
12910                         ctx.lineCap = options.lineCap;
12911                         ctx.lineJoin = options.lineJoin;
12912                         ctx.stroke();
12913                 }
12914         },
12915
12916         // Canvas obviously doesn't have mouse events for individual drawn objects,
12917         // so we emulate that by calculating what's under the mouse on mousemove/click manually
12918
12919         _onClick: function (e) {
12920                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12921
12922                 for (var order = this._drawFirst; order; order = order.next) {
12923                         layer = order.layer;
12924                         if (layer.options.interactive && layer._containsPoint(point)) {
12925                                 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
12926                                         clickedLayer = layer;
12927                                 }
12928                         }
12929                 }
12930                 this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
12931         },
12932
12933         _onMouseMove: function (e) {
12934                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12935
12936                 var point = this._map.mouseEventToLayerPoint(e);
12937                 this._handleMouseHover(e, point);
12938         },
12939
12940
12941         _handleMouseOut: function (e) {
12942                 var layer = this._hoveredLayer;
12943                 if (layer) {
12944                         // if we're leaving the layer, fire mouseout
12945                         removeClass(this._container, 'leaflet-interactive');
12946                         this._fireEvent([layer], e, 'mouseout');
12947                         this._hoveredLayer = null;
12948                         this._mouseHoverThrottled = false;
12949                 }
12950         },
12951
12952         _handleMouseHover: function (e, point) {
12953                 if (this._mouseHoverThrottled) {
12954                         return;
12955                 }
12956
12957                 var layer, candidateHoveredLayer;
12958
12959                 for (var order = this._drawFirst; order; order = order.next) {
12960                         layer = order.layer;
12961                         if (layer.options.interactive && layer._containsPoint(point)) {
12962                                 candidateHoveredLayer = layer;
12963                         }
12964                 }
12965
12966                 if (candidateHoveredLayer !== this._hoveredLayer) {
12967                         this._handleMouseOut(e);
12968
12969                         if (candidateHoveredLayer) {
12970                                 addClass(this._container, 'leaflet-interactive'); // change cursor
12971                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12972                                 this._hoveredLayer = candidateHoveredLayer;
12973                         }
12974                 }
12975
12976                 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
12977
12978                 this._mouseHoverThrottled = true;
12979                 setTimeout(bind(function () {
12980                         this._mouseHoverThrottled = false;
12981                 }, this), 32);
12982         },
12983
12984         _fireEvent: function (layers, e, type) {
12985                 this._map._fireDOMEvent(e, type || e.type, layers);
12986         },
12987
12988         _bringToFront: function (layer) {
12989                 var order = layer._order;
12990
12991                 if (!order) { return; }
12992
12993                 var next = order.next;
12994                 var prev = order.prev;
12995
12996                 if (next) {
12997                         next.prev = prev;
12998                 } else {
12999                         // Already last
13000                         return;
13001                 }
13002                 if (prev) {
13003                         prev.next = next;
13004                 } else if (next) {
13005                         // Update first entry unless this is the
13006                         // single entry
13007                         this._drawFirst = next;
13008                 }
13009
13010                 order.prev = this._drawLast;
13011                 this._drawLast.next = order;
13012
13013                 order.next = null;
13014                 this._drawLast = order;
13015
13016                 this._requestRedraw(layer);
13017         },
13018
13019         _bringToBack: function (layer) {
13020                 var order = layer._order;
13021
13022                 if (!order) { return; }
13023
13024                 var next = order.next;
13025                 var prev = order.prev;
13026
13027                 if (prev) {
13028                         prev.next = next;
13029                 } else {
13030                         // Already first
13031                         return;
13032                 }
13033                 if (next) {
13034                         next.prev = prev;
13035                 } else if (prev) {
13036                         // Update last entry unless this is the
13037                         // single entry
13038                         this._drawLast = prev;
13039                 }
13040
13041                 order.prev = null;
13042
13043                 order.next = this._drawFirst;
13044                 this._drawFirst.prev = order;
13045                 this._drawFirst = order;
13046
13047                 this._requestRedraw(layer);
13048         }
13049   });
13050
13051   // @factory L.canvas(options?: Renderer options)
13052   // Creates a Canvas renderer with the given options.
13053   function canvas(options) {
13054         return Browser.canvas ? new Canvas(options) : null;
13055   }
13056
13057   /*
13058    * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
13059    */
13060
13061
13062   var vmlCreate = (function () {
13063         try {
13064                 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
13065                 return function (name) {
13066                         return document.createElement('<lvml:' + name + ' class="lvml">');
13067                 };
13068         } catch (e) {
13069                 // Do not return fn from catch block so `e` can be garbage collected
13070                 // See https://github.com/Leaflet/Leaflet/pull/7279
13071         }
13072         return function (name) {
13073                 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
13074         };
13075   })();
13076
13077
13078   /*
13079    * @class SVG
13080    *
13081    *
13082    * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
13083    * with old versions of Internet Explorer.
13084    */
13085
13086   // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
13087   var vmlMixin = {
13088
13089         _initContainer: function () {
13090                 this._container = create$1('div', 'leaflet-vml-container');
13091         },
13092
13093         _update: function () {
13094                 if (this._map._animatingZoom) { return; }
13095                 Renderer.prototype._update.call(this);
13096                 this.fire('update');
13097         },
13098
13099         _initPath: function (layer) {
13100                 var container = layer._container = vmlCreate('shape');
13101
13102                 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
13103
13104                 container.coordsize = '1 1';
13105
13106                 layer._path = vmlCreate('path');
13107                 container.appendChild(layer._path);
13108
13109                 this._updateStyle(layer);
13110                 this._layers[stamp(layer)] = layer;
13111         },
13112
13113         _addPath: function (layer) {
13114                 var container = layer._container;
13115                 this._container.appendChild(container);
13116
13117                 if (layer.options.interactive) {
13118                         layer.addInteractiveTarget(container);
13119                 }
13120         },
13121
13122         _removePath: function (layer) {
13123                 var container = layer._container;
13124                 remove(container);
13125                 layer.removeInteractiveTarget(container);
13126                 delete this._layers[stamp(layer)];
13127         },
13128
13129         _updateStyle: function (layer) {
13130                 var stroke = layer._stroke,
13131                     fill = layer._fill,
13132                     options = layer.options,
13133                     container = layer._container;
13134
13135                 container.stroked = !!options.stroke;
13136                 container.filled = !!options.fill;
13137
13138                 if (options.stroke) {
13139                         if (!stroke) {
13140                                 stroke = layer._stroke = vmlCreate('stroke');
13141                         }
13142                         container.appendChild(stroke);
13143                         stroke.weight = options.weight + 'px';
13144                         stroke.color = options.color;
13145                         stroke.opacity = options.opacity;
13146
13147                         if (options.dashArray) {
13148                                 stroke.dashStyle = isArray(options.dashArray) ?
13149                                     options.dashArray.join(' ') :
13150                                     options.dashArray.replace(/( *, *)/g, ' ');
13151                         } else {
13152                                 stroke.dashStyle = '';
13153                         }
13154                         stroke.endcap = options.lineCap.replace('butt', 'flat');
13155                         stroke.joinstyle = options.lineJoin;
13156
13157                 } else if (stroke) {
13158                         container.removeChild(stroke);
13159                         layer._stroke = null;
13160                 }
13161
13162                 if (options.fill) {
13163                         if (!fill) {
13164                                 fill = layer._fill = vmlCreate('fill');
13165                         }
13166                         container.appendChild(fill);
13167                         fill.color = options.fillColor || options.color;
13168                         fill.opacity = options.fillOpacity;
13169
13170                 } else if (fill) {
13171                         container.removeChild(fill);
13172                         layer._fill = null;
13173                 }
13174         },
13175
13176         _updateCircle: function (layer) {
13177                 var p = layer._point.round(),
13178                     r = Math.round(layer._radius),
13179                     r2 = Math.round(layer._radiusY || r);
13180
13181                 this._setPath(layer, layer._empty() ? 'M0 0' :
13182                         'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
13183         },
13184
13185         _setPath: function (layer, path) {
13186                 layer._path.v = path;
13187         },
13188
13189         _bringToFront: function (layer) {
13190                 toFront(layer._container);
13191         },
13192
13193         _bringToBack: function (layer) {
13194                 toBack(layer._container);
13195         }
13196   };
13197
13198   var create = Browser.vml ? vmlCreate : svgCreate;
13199
13200   /*
13201    * @class SVG
13202    * @inherits Renderer
13203    * @aka L.SVG
13204    *
13205    * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
13206    * Inherits `Renderer`.
13207    *
13208    * Due to [technical limitations](https://caniuse.com/svg), SVG is not
13209    * available in all web browsers, notably Android 2.x and 3.x.
13210    *
13211    * Although SVG is not available on IE7 and IE8, these browsers support
13212    * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
13213    * (a now deprecated technology), and the SVG renderer will fall back to VML in
13214    * this case.
13215    *
13216    * @example
13217    *
13218    * Use SVG by default for all paths in the map:
13219    *
13220    * ```js
13221    * var map = L.map('map', {
13222    *    renderer: L.svg()
13223    * });
13224    * ```
13225    *
13226    * Use a SVG renderer with extra padding for specific vector geometries:
13227    *
13228    * ```js
13229    * var map = L.map('map');
13230    * var myRenderer = L.svg({ padding: 0.5 });
13231    * var line = L.polyline( coordinates, { renderer: myRenderer } );
13232    * var circle = L.circle( center, { renderer: myRenderer } );
13233    * ```
13234    */
13235
13236   var SVG = Renderer.extend({
13237
13238         _initContainer: function () {
13239                 this._container = create('svg');
13240
13241                 // makes it possible to click through svg root; we'll reset it back in individual paths
13242                 this._container.setAttribute('pointer-events', 'none');
13243
13244                 this._rootGroup = create('g');
13245                 this._container.appendChild(this._rootGroup);
13246         },
13247
13248         _destroyContainer: function () {
13249                 remove(this._container);
13250                 off(this._container);
13251                 delete this._container;
13252                 delete this._rootGroup;
13253                 delete this._svgSize;
13254         },
13255
13256         _update: function () {
13257                 if (this._map._animatingZoom && this._bounds) { return; }
13258
13259                 Renderer.prototype._update.call(this);
13260
13261                 var b = this._bounds,
13262                     size = b.getSize(),
13263                     container = this._container;
13264
13265                 // set size of svg-container if changed
13266                 if (!this._svgSize || !this._svgSize.equals(size)) {
13267                         this._svgSize = size;
13268                         container.setAttribute('width', size.x);
13269                         container.setAttribute('height', size.y);
13270                 }
13271
13272                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
13273                 setPosition(container, b.min);
13274                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
13275
13276                 this.fire('update');
13277         },
13278
13279         // methods below are called by vector layers implementations
13280
13281         _initPath: function (layer) {
13282                 var path = layer._path = create('path');
13283
13284                 // @namespace Path
13285                 // @option className: String = null
13286                 // Custom class name set on an element. Only for SVG renderer.
13287                 if (layer.options.className) {
13288                         addClass(path, layer.options.className);
13289                 }
13290
13291                 if (layer.options.interactive) {
13292                         addClass(path, 'leaflet-interactive');
13293                 }
13294
13295                 this._updateStyle(layer);
13296                 this._layers[stamp(layer)] = layer;
13297         },
13298
13299         _addPath: function (layer) {
13300                 if (!this._rootGroup) { this._initContainer(); }
13301                 this._rootGroup.appendChild(layer._path);
13302                 layer.addInteractiveTarget(layer._path);
13303         },
13304
13305         _removePath: function (layer) {
13306                 remove(layer._path);
13307                 layer.removeInteractiveTarget(layer._path);
13308                 delete this._layers[stamp(layer)];
13309         },
13310
13311         _updatePath: function (layer) {
13312                 layer._project();
13313                 layer._update();
13314         },
13315
13316         _updateStyle: function (layer) {
13317                 var path = layer._path,
13318                     options = layer.options;
13319
13320                 if (!path) { return; }
13321
13322                 if (options.stroke) {
13323                         path.setAttribute('stroke', options.color);
13324                         path.setAttribute('stroke-opacity', options.opacity);
13325                         path.setAttribute('stroke-width', options.weight);
13326                         path.setAttribute('stroke-linecap', options.lineCap);
13327                         path.setAttribute('stroke-linejoin', options.lineJoin);
13328
13329                         if (options.dashArray) {
13330                                 path.setAttribute('stroke-dasharray', options.dashArray);
13331                         } else {
13332                                 path.removeAttribute('stroke-dasharray');
13333                         }
13334
13335                         if (options.dashOffset) {
13336                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
13337                         } else {
13338                                 path.removeAttribute('stroke-dashoffset');
13339                         }
13340                 } else {
13341                         path.setAttribute('stroke', 'none');
13342                 }
13343
13344                 if (options.fill) {
13345                         path.setAttribute('fill', options.fillColor || options.color);
13346                         path.setAttribute('fill-opacity', options.fillOpacity);
13347                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
13348                 } else {
13349                         path.setAttribute('fill', 'none');
13350                 }
13351         },
13352
13353         _updatePoly: function (layer, closed) {
13354                 this._setPath(layer, pointsToPath(layer._parts, closed));
13355         },
13356
13357         _updateCircle: function (layer) {
13358                 var p = layer._point,
13359                     r = Math.max(Math.round(layer._radius), 1),
13360                     r2 = Math.max(Math.round(layer._radiusY), 1) || r,
13361                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
13362
13363                 // drawing a circle with two half-arcs
13364                 var d = layer._empty() ? 'M0 0' :
13365                         'M' + (p.x - r) + ',' + p.y +
13366                         arc + (r * 2) + ',0 ' +
13367                         arc + (-r * 2) + ',0 ';
13368
13369                 this._setPath(layer, d);
13370         },
13371
13372         _setPath: function (layer, path) {
13373                 layer._path.setAttribute('d', path);
13374         },
13375
13376         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
13377         _bringToFront: function (layer) {
13378                 toFront(layer._path);
13379         },
13380
13381         _bringToBack: function (layer) {
13382                 toBack(layer._path);
13383         }
13384   });
13385
13386   if (Browser.vml) {
13387         SVG.include(vmlMixin);
13388   }
13389
13390   // @namespace SVG
13391   // @factory L.svg(options?: Renderer options)
13392   // Creates a SVG renderer with the given options.
13393   function svg(options) {
13394         return Browser.svg || Browser.vml ? new SVG(options) : null;
13395   }
13396
13397   Map.include({
13398         // @namespace Map; @method getRenderer(layer: Path): Renderer
13399         // Returns the instance of `Renderer` that should be used to render the given
13400         // `Path`. It will ensure that the `renderer` options of the map and paths
13401         // are respected, and that the renderers do exist on the map.
13402         getRenderer: function (layer) {
13403                 // @namespace Path; @option renderer: Renderer
13404                 // Use this specific instance of `Renderer` for this path. Takes
13405                 // precedence over the map's [default renderer](#map-renderer).
13406                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
13407
13408                 if (!renderer) {
13409                         renderer = this._renderer = this._createRenderer();
13410                 }
13411
13412                 if (!this.hasLayer(renderer)) {
13413                         this.addLayer(renderer);
13414                 }
13415                 return renderer;
13416         },
13417
13418         _getPaneRenderer: function (name) {
13419                 if (name === 'overlayPane' || name === undefined) {
13420                         return false;
13421                 }
13422
13423                 var renderer = this._paneRenderers[name];
13424                 if (renderer === undefined) {
13425                         renderer = this._createRenderer({pane: name});
13426                         this._paneRenderers[name] = renderer;
13427                 }
13428                 return renderer;
13429         },
13430
13431         _createRenderer: function (options) {
13432                 // @namespace Map; @option preferCanvas: Boolean = false
13433                 // Whether `Path`s should be rendered on a `Canvas` renderer.
13434                 // By default, all `Path`s are rendered in a `SVG` renderer.
13435                 return (this.options.preferCanvas && canvas(options)) || svg(options);
13436         }
13437   });
13438
13439   /*
13440    * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
13441    */
13442
13443   /*
13444    * @class Rectangle
13445    * @aka L.Rectangle
13446    * @inherits Polygon
13447    *
13448    * A class for drawing rectangle overlays on a map. Extends `Polygon`.
13449    *
13450    * @example
13451    *
13452    * ```js
13453    * // define rectangle geographical bounds
13454    * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
13455    *
13456    * // create an orange rectangle
13457    * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
13458    *
13459    * // zoom the map to the rectangle bounds
13460    * map.fitBounds(bounds);
13461    * ```
13462    *
13463    */
13464
13465
13466   var Rectangle = Polygon.extend({
13467         initialize: function (latLngBounds, options) {
13468                 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13469         },
13470
13471         // @method setBounds(latLngBounds: LatLngBounds): this
13472         // Redraws the rectangle with the passed bounds.
13473         setBounds: function (latLngBounds) {
13474                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13475         },
13476
13477         _boundsToLatLngs: function (latLngBounds) {
13478                 latLngBounds = toLatLngBounds(latLngBounds);
13479                 return [
13480                         latLngBounds.getSouthWest(),
13481                         latLngBounds.getNorthWest(),
13482                         latLngBounds.getNorthEast(),
13483                         latLngBounds.getSouthEast()
13484                 ];
13485         }
13486   });
13487
13488
13489   // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13490   function rectangle(latLngBounds, options) {
13491         return new Rectangle(latLngBounds, options);
13492   }
13493
13494   SVG.create = create;
13495   SVG.pointsToPath = pointsToPath;
13496
13497   GeoJSON.geometryToLayer = geometryToLayer;
13498   GeoJSON.coordsToLatLng = coordsToLatLng;
13499   GeoJSON.coordsToLatLngs = coordsToLatLngs;
13500   GeoJSON.latLngToCoords = latLngToCoords;
13501   GeoJSON.latLngsToCoords = latLngsToCoords;
13502   GeoJSON.getFeature = getFeature;
13503   GeoJSON.asFeature = asFeature;
13504
13505   /*
13506    * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13507    * (zoom to a selected bounding box), enabled by default.
13508    */
13509
13510   // @namespace Map
13511   // @section Interaction Options
13512   Map.mergeOptions({
13513         // @option boxZoom: Boolean = true
13514         // Whether the map can be zoomed to a rectangular area specified by
13515         // dragging the mouse while pressing the shift key.
13516         boxZoom: true
13517   });
13518
13519   var BoxZoom = Handler.extend({
13520         initialize: function (map) {
13521                 this._map = map;
13522                 this._container = map._container;
13523                 this._pane = map._panes.overlayPane;
13524                 this._resetStateTimeout = 0;
13525                 map.on('unload', this._destroy, this);
13526         },
13527
13528         addHooks: function () {
13529                 on(this._container, 'mousedown', this._onMouseDown, this);
13530         },
13531
13532         removeHooks: function () {
13533                 off(this._container, 'mousedown', this._onMouseDown, this);
13534         },
13535
13536         moved: function () {
13537                 return this._moved;
13538         },
13539
13540         _destroy: function () {
13541                 remove(this._pane);
13542                 delete this._pane;
13543         },
13544
13545         _resetState: function () {
13546                 this._resetStateTimeout = 0;
13547                 this._moved = false;
13548         },
13549
13550         _clearDeferredResetState: function () {
13551                 if (this._resetStateTimeout !== 0) {
13552                         clearTimeout(this._resetStateTimeout);
13553                         this._resetStateTimeout = 0;
13554                 }
13555         },
13556
13557         _onMouseDown: function (e) {
13558                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13559
13560                 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13561                 // will interrupt the interaction and orphan a box element in the container.
13562                 this._clearDeferredResetState();
13563                 this._resetState();
13564
13565                 disableTextSelection();
13566                 disableImageDrag();
13567
13568                 this._startPoint = this._map.mouseEventToContainerPoint(e);
13569
13570                 on(document, {
13571                         contextmenu: stop,
13572                         mousemove: this._onMouseMove,
13573                         mouseup: this._onMouseUp,
13574                         keydown: this._onKeyDown
13575                 }, this);
13576         },
13577
13578         _onMouseMove: function (e) {
13579                 if (!this._moved) {
13580                         this._moved = true;
13581
13582                         this._box = create$1('div', 'leaflet-zoom-box', this._container);
13583                         addClass(this._container, 'leaflet-crosshair');
13584
13585                         this._map.fire('boxzoomstart');
13586                 }
13587
13588                 this._point = this._map.mouseEventToContainerPoint(e);
13589
13590                 var bounds = new Bounds(this._point, this._startPoint),
13591                     size = bounds.getSize();
13592
13593                 setPosition(this._box, bounds.min);
13594
13595                 this._box.style.width  = size.x + 'px';
13596                 this._box.style.height = size.y + 'px';
13597         },
13598
13599         _finish: function () {
13600                 if (this._moved) {
13601                         remove(this._box);
13602                         removeClass(this._container, 'leaflet-crosshair');
13603                 }
13604
13605                 enableTextSelection();
13606                 enableImageDrag();
13607
13608                 off(document, {
13609                         contextmenu: stop,
13610                         mousemove: this._onMouseMove,
13611                         mouseup: this._onMouseUp,
13612                         keydown: this._onKeyDown
13613                 }, this);
13614         },
13615
13616         _onMouseUp: function (e) {
13617                 if ((e.which !== 1) && (e.button !== 1)) { return; }
13618
13619                 this._finish();
13620
13621                 if (!this._moved) { return; }
13622                 // Postpone to next JS tick so internal click event handling
13623                 // still see it as "moved".
13624                 this._clearDeferredResetState();
13625                 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13626
13627                 var bounds = new LatLngBounds(
13628                         this._map.containerPointToLatLng(this._startPoint),
13629                         this._map.containerPointToLatLng(this._point));
13630
13631                 this._map
13632                         .fitBounds(bounds)
13633                         .fire('boxzoomend', {boxZoomBounds: bounds});
13634         },
13635
13636         _onKeyDown: function (e) {
13637                 if (e.keyCode === 27) {
13638                         this._finish();
13639                         this._clearDeferredResetState();
13640                         this._resetState();
13641                 }
13642         }
13643   });
13644
13645   // @section Handlers
13646   // @property boxZoom: Handler
13647   // Box (shift-drag with mouse) zoom handler.
13648   Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13649
13650   /*
13651    * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13652    */
13653
13654   // @namespace Map
13655   // @section Interaction Options
13656
13657   Map.mergeOptions({
13658         // @option doubleClickZoom: Boolean|String = true
13659         // Whether the map can be zoomed in by double clicking on it and
13660         // zoomed out by double clicking while holding shift. If passed
13661         // `'center'`, double-click zoom will zoom to the center of the
13662         //  view regardless of where the mouse was.
13663         doubleClickZoom: true
13664   });
13665
13666   var DoubleClickZoom = Handler.extend({
13667         addHooks: function () {
13668                 this._map.on('dblclick', this._onDoubleClick, this);
13669         },
13670
13671         removeHooks: function () {
13672                 this._map.off('dblclick', this._onDoubleClick, this);
13673         },
13674
13675         _onDoubleClick: function (e) {
13676                 var map = this._map,
13677                     oldZoom = map.getZoom(),
13678                     delta = map.options.zoomDelta,
13679                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13680
13681                 if (map.options.doubleClickZoom === 'center') {
13682                         map.setZoom(zoom);
13683                 } else {
13684                         map.setZoomAround(e.containerPoint, zoom);
13685                 }
13686         }
13687   });
13688
13689   // @section Handlers
13690   //
13691   // Map properties include interaction handlers that allow you to control
13692   // interaction behavior in runtime, enabling or disabling certain features such
13693   // as dragging or touch zoom (see `Handler` methods). For example:
13694   //
13695   // ```js
13696   // map.doubleClickZoom.disable();
13697   // ```
13698   //
13699   // @property doubleClickZoom: Handler
13700   // Double click zoom handler.
13701   Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13702
13703   /*
13704    * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13705    */
13706
13707   // @namespace Map
13708   // @section Interaction Options
13709   Map.mergeOptions({
13710         // @option dragging: Boolean = true
13711         // Whether the map is draggable with mouse/touch or not.
13712         dragging: true,
13713
13714         // @section Panning Inertia Options
13715         // @option inertia: Boolean = *
13716         // If enabled, panning of the map will have an inertia effect where
13717         // the map builds momentum while dragging and continues moving in
13718         // the same direction for some time. Feels especially nice on touch
13719         // devices. Enabled by default.
13720         inertia: true,
13721
13722         // @option inertiaDeceleration: Number = 3000
13723         // The rate with which the inertial movement slows down, in pixels/second².
13724         inertiaDeceleration: 3400, // px/s^2
13725
13726         // @option inertiaMaxSpeed: Number = Infinity
13727         // Max speed of the inertial movement, in pixels/second.
13728         inertiaMaxSpeed: Infinity, // px/s
13729
13730         // @option easeLinearity: Number = 0.2
13731         easeLinearity: 0.2,
13732
13733         // TODO refactor, move to CRS
13734         // @option worldCopyJump: Boolean = false
13735         // With this option enabled, the map tracks when you pan to another "copy"
13736         // of the world and seamlessly jumps to the original one so that all overlays
13737         // like markers and vector layers are still visible.
13738         worldCopyJump: false,
13739
13740         // @option maxBoundsViscosity: Number = 0.0
13741         // If `maxBounds` is set, this option will control how solid the bounds
13742         // are when dragging the map around. The default value of `0.0` allows the
13743         // user to drag outside the bounds at normal speed, higher values will
13744         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13745         // solid, preventing the user from dragging outside the bounds.
13746         maxBoundsViscosity: 0.0
13747   });
13748
13749   var Drag = Handler.extend({
13750         addHooks: function () {
13751                 if (!this._draggable) {
13752                         var map = this._map;
13753
13754                         this._draggable = new Draggable(map._mapPane, map._container);
13755
13756                         this._draggable.on({
13757                                 dragstart: this._onDragStart,
13758                                 drag: this._onDrag,
13759                                 dragend: this._onDragEnd
13760                         }, this);
13761
13762                         this._draggable.on('predrag', this._onPreDragLimit, this);
13763                         if (map.options.worldCopyJump) {
13764                                 this._draggable.on('predrag', this._onPreDragWrap, this);
13765                                 map.on('zoomend', this._onZoomEnd, this);
13766
13767                                 map.whenReady(this._onZoomEnd, this);
13768                         }
13769                 }
13770                 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13771                 this._draggable.enable();
13772                 this._positions = [];
13773                 this._times = [];
13774         },
13775
13776         removeHooks: function () {
13777                 removeClass(this._map._container, 'leaflet-grab');
13778                 removeClass(this._map._container, 'leaflet-touch-drag');
13779                 this._draggable.disable();
13780         },
13781
13782         moved: function () {
13783                 return this._draggable && this._draggable._moved;
13784         },
13785
13786         moving: function () {
13787                 return this._draggable && this._draggable._moving;
13788         },
13789
13790         _onDragStart: function () {
13791                 var map = this._map;
13792
13793                 map._stop();
13794                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13795                         var bounds = toLatLngBounds(this._map.options.maxBounds);
13796
13797                         this._offsetLimit = toBounds(
13798                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13799                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13800                                         .add(this._map.getSize()));
13801
13802                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13803                 } else {
13804                         this._offsetLimit = null;
13805                 }
13806
13807                 map
13808                     .fire('movestart')
13809                     .fire('dragstart');
13810
13811                 if (map.options.inertia) {
13812                         this._positions = [];
13813                         this._times = [];
13814                 }
13815         },
13816
13817         _onDrag: function (e) {
13818                 if (this._map.options.inertia) {
13819                         var time = this._lastTime = +new Date(),
13820                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13821
13822                         this._positions.push(pos);
13823                         this._times.push(time);
13824
13825                         this._prunePositions(time);
13826                 }
13827
13828                 this._map
13829                     .fire('move', e)
13830                     .fire('drag', e);
13831         },
13832
13833         _prunePositions: function (time) {
13834                 while (this._positions.length > 1 && time - this._times[0] > 50) {
13835                         this._positions.shift();
13836                         this._times.shift();
13837                 }
13838         },
13839
13840         _onZoomEnd: function () {
13841                 var pxCenter = this._map.getSize().divideBy(2),
13842                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13843
13844                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13845                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13846         },
13847
13848         _viscousLimit: function (value, threshold) {
13849                 return value - (value - threshold) * this._viscosity;
13850         },
13851
13852         _onPreDragLimit: function () {
13853                 if (!this._viscosity || !this._offsetLimit) { return; }
13854
13855                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13856
13857                 var limit = this._offsetLimit;
13858                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13859                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13860                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13861                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13862
13863                 this._draggable._newPos = this._draggable._startPos.add(offset);
13864         },
13865
13866         _onPreDragWrap: function () {
13867                 // TODO refactor to be able to adjust map pane position after zoom
13868                 var worldWidth = this._worldWidth,
13869                     halfWidth = Math.round(worldWidth / 2),
13870                     dx = this._initialWorldOffset,
13871                     x = this._draggable._newPos.x,
13872                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13873                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13874                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13875
13876                 this._draggable._absPos = this._draggable._newPos.clone();
13877                 this._draggable._newPos.x = newX;
13878         },
13879
13880         _onDragEnd: function (e) {
13881                 var map = this._map,
13882                     options = map.options,
13883
13884                     noInertia = !options.inertia || e.noInertia || this._times.length < 2;
13885
13886                 map.fire('dragend', e);
13887
13888                 if (noInertia) {
13889                         map.fire('moveend');
13890
13891                 } else {
13892                         this._prunePositions(+new Date());
13893
13894                         var direction = this._lastPos.subtract(this._positions[0]),
13895                             duration = (this._lastTime - this._times[0]) / 1000,
13896                             ease = options.easeLinearity,
13897
13898                             speedVector = direction.multiplyBy(ease / duration),
13899                             speed = speedVector.distanceTo([0, 0]),
13900
13901                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13902                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13903
13904                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13905                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13906
13907                         if (!offset.x && !offset.y) {
13908                                 map.fire('moveend');
13909
13910                         } else {
13911                                 offset = map._limitOffset(offset, map.options.maxBounds);
13912
13913                                 requestAnimFrame(function () {
13914                                         map.panBy(offset, {
13915                                                 duration: decelerationDuration,
13916                                                 easeLinearity: ease,
13917                                                 noMoveStart: true,
13918                                                 animate: true
13919                                         });
13920                                 });
13921                         }
13922                 }
13923         }
13924   });
13925
13926   // @section Handlers
13927   // @property dragging: Handler
13928   // Map dragging handler (by both mouse and touch).
13929   Map.addInitHook('addHandler', 'dragging', Drag);
13930
13931   /*
13932    * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13933    */
13934
13935   // @namespace Map
13936   // @section Keyboard Navigation Options
13937   Map.mergeOptions({
13938         // @option keyboard: Boolean = true
13939         // Makes the map focusable and allows users to navigate the map with keyboard
13940         // arrows and `+`/`-` keys.
13941         keyboard: true,
13942
13943         // @option keyboardPanDelta: Number = 80
13944         // Amount of pixels to pan when pressing an arrow key.
13945         keyboardPanDelta: 80
13946   });
13947
13948   var Keyboard = Handler.extend({
13949
13950         keyCodes: {
13951                 left:    [37],
13952                 right:   [39],
13953                 down:    [40],
13954                 up:      [38],
13955                 zoomIn:  [187, 107, 61, 171],
13956                 zoomOut: [189, 109, 54, 173]
13957         },
13958
13959         initialize: function (map) {
13960                 this._map = map;
13961
13962                 this._setPanDelta(map.options.keyboardPanDelta);
13963                 this._setZoomDelta(map.options.zoomDelta);
13964         },
13965
13966         addHooks: function () {
13967                 var container = this._map._container;
13968
13969                 // make the container focusable by tabbing
13970                 if (container.tabIndex <= 0) {
13971                         container.tabIndex = '0';
13972                 }
13973
13974                 on(container, {
13975                         focus: this._onFocus,
13976                         blur: this._onBlur,
13977                         mousedown: this._onMouseDown
13978                 }, this);
13979
13980                 this._map.on({
13981                         focus: this._addHooks,
13982                         blur: this._removeHooks
13983                 }, this);
13984         },
13985
13986         removeHooks: function () {
13987                 this._removeHooks();
13988
13989                 off(this._map._container, {
13990                         focus: this._onFocus,
13991                         blur: this._onBlur,
13992                         mousedown: this._onMouseDown
13993                 }, this);
13994
13995                 this._map.off({
13996                         focus: this._addHooks,
13997                         blur: this._removeHooks
13998                 }, this);
13999         },
14000
14001         _onMouseDown: function () {
14002                 if (this._focused) { return; }
14003
14004                 var body = document.body,
14005                     docEl = document.documentElement,
14006                     top = body.scrollTop || docEl.scrollTop,
14007                     left = body.scrollLeft || docEl.scrollLeft;
14008
14009                 this._map._container.focus();
14010
14011                 window.scrollTo(left, top);
14012         },
14013
14014         _onFocus: function () {
14015                 this._focused = true;
14016                 this._map.fire('focus');
14017         },
14018
14019         _onBlur: function () {
14020                 this._focused = false;
14021                 this._map.fire('blur');
14022         },
14023
14024         _setPanDelta: function (panDelta) {
14025                 var keys = this._panKeys = {},
14026                     codes = this.keyCodes,
14027                     i, len;
14028
14029                 for (i = 0, len = codes.left.length; i < len; i++) {
14030                         keys[codes.left[i]] = [-1 * panDelta, 0];
14031                 }
14032                 for (i = 0, len = codes.right.length; i < len; i++) {
14033                         keys[codes.right[i]] = [panDelta, 0];
14034                 }
14035                 for (i = 0, len = codes.down.length; i < len; i++) {
14036                         keys[codes.down[i]] = [0, panDelta];
14037                 }
14038                 for (i = 0, len = codes.up.length; i < len; i++) {
14039                         keys[codes.up[i]] = [0, -1 * panDelta];
14040                 }
14041         },
14042
14043         _setZoomDelta: function (zoomDelta) {
14044                 var keys = this._zoomKeys = {},
14045                     codes = this.keyCodes,
14046                     i, len;
14047
14048                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
14049                         keys[codes.zoomIn[i]] = zoomDelta;
14050                 }
14051                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
14052                         keys[codes.zoomOut[i]] = -zoomDelta;
14053                 }
14054         },
14055
14056         _addHooks: function () {
14057                 on(document, 'keydown', this._onKeyDown, this);
14058         },
14059
14060         _removeHooks: function () {
14061                 off(document, 'keydown', this._onKeyDown, this);
14062         },
14063
14064         _onKeyDown: function (e) {
14065                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
14066
14067                 var key = e.keyCode,
14068                     map = this._map,
14069                     offset;
14070
14071                 if (key in this._panKeys) {
14072                         if (!map._panAnim || !map._panAnim._inProgress) {
14073                                 offset = this._panKeys[key];
14074                                 if (e.shiftKey) {
14075                                         offset = toPoint(offset).multiplyBy(3);
14076                                 }
14077
14078                                 if (map.options.maxBounds) {
14079                                         offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
14080                                 }
14081
14082                                 if (map.options.worldCopyJump) {
14083                                         var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
14084                                         map.panTo(newLatLng);
14085                                 } else {
14086                                         map.panBy(offset);
14087                                 }
14088                         }
14089                 } else if (key in this._zoomKeys) {
14090                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
14091
14092                 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
14093                         map.closePopup();
14094
14095                 } else {
14096                         return;
14097                 }
14098
14099                 stop(e);
14100         }
14101   });
14102
14103   // @section Handlers
14104   // @section Handlers
14105   // @property keyboard: Handler
14106   // Keyboard navigation handler.
14107   Map.addInitHook('addHandler', 'keyboard', Keyboard);
14108
14109   /*
14110    * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
14111    */
14112
14113   // @namespace Map
14114   // @section Interaction Options
14115   Map.mergeOptions({
14116         // @section Mouse wheel options
14117         // @option scrollWheelZoom: Boolean|String = true
14118         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
14119         // it will zoom to the center of the view regardless of where the mouse was.
14120         scrollWheelZoom: true,
14121
14122         // @option wheelDebounceTime: Number = 40
14123         // Limits the rate at which a wheel can fire (in milliseconds). By default
14124         // user can't zoom via wheel more often than once per 40 ms.
14125         wheelDebounceTime: 40,
14126
14127         // @option wheelPxPerZoomLevel: Number = 60
14128         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
14129         // mean a change of one full zoom level. Smaller values will make wheel-zooming
14130         // faster (and vice versa).
14131         wheelPxPerZoomLevel: 60
14132   });
14133
14134   var ScrollWheelZoom = Handler.extend({
14135         addHooks: function () {
14136                 on(this._map._container, 'wheel', this._onWheelScroll, this);
14137
14138                 this._delta = 0;
14139         },
14140
14141         removeHooks: function () {
14142                 off(this._map._container, 'wheel', this._onWheelScroll, this);
14143         },
14144
14145         _onWheelScroll: function (e) {
14146                 var delta = getWheelDelta(e);
14147
14148                 var debounce = this._map.options.wheelDebounceTime;
14149
14150                 this._delta += delta;
14151                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
14152
14153                 if (!this._startTime) {
14154                         this._startTime = +new Date();
14155                 }
14156
14157                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
14158
14159                 clearTimeout(this._timer);
14160                 this._timer = setTimeout(bind(this._performZoom, this), left);
14161
14162                 stop(e);
14163         },
14164
14165         _performZoom: function () {
14166                 var map = this._map,
14167                     zoom = map.getZoom(),
14168                     snap = this._map.options.zoomSnap || 0;
14169
14170                 map._stop(); // stop panning and fly animations if any
14171
14172                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
14173                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
14174                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
14175                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
14176                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
14177
14178                 this._delta = 0;
14179                 this._startTime = null;
14180
14181                 if (!delta) { return; }
14182
14183                 if (map.options.scrollWheelZoom === 'center') {
14184                         map.setZoom(zoom + delta);
14185                 } else {
14186                         map.setZoomAround(this._lastMousePos, zoom + delta);
14187                 }
14188         }
14189   });
14190
14191   // @section Handlers
14192   // @property scrollWheelZoom: Handler
14193   // Scroll wheel zoom handler.
14194   Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
14195
14196   /*
14197    * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
14198    * which otherwise is not fired by mobile Safari.
14199    */
14200
14201   var tapHoldDelay = 600;
14202
14203   // @namespace Map
14204   // @section Interaction Options
14205   Map.mergeOptions({
14206         // @section Touch interaction options
14207         // @option tapHold: Boolean
14208         // Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
14209         tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
14210
14211         // @option tapTolerance: Number = 15
14212         // The max number of pixels a user can shift his finger during touch
14213         // for it to be considered a valid tap.
14214         tapTolerance: 15
14215   });
14216
14217   var TapHold = Handler.extend({
14218         addHooks: function () {
14219                 on(this._map._container, 'touchstart', this._onDown, this);
14220         },
14221
14222         removeHooks: function () {
14223                 off(this._map._container, 'touchstart', this._onDown, this);
14224         },
14225
14226         _onDown: function (e) {
14227                 clearTimeout(this._holdTimeout);
14228                 if (e.touches.length !== 1) { return; }
14229
14230                 var first = e.touches[0];
14231                 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
14232
14233                 this._holdTimeout = setTimeout(bind(function () {
14234                         this._cancel();
14235                         if (!this._isTapValid()) { return; }
14236
14237                         // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
14238                         on(document, 'touchend', preventDefault);
14239                         on(document, 'touchend touchcancel', this._cancelClickPrevent);
14240                         this._simulateEvent('contextmenu', first);
14241                 }, this), tapHoldDelay);
14242
14243                 on(document, 'touchend touchcancel contextmenu', this._cancel, this);
14244                 on(document, 'touchmove', this._onMove, this);
14245         },
14246
14247         _cancelClickPrevent: function cancelClickPrevent() {
14248                 off(document, 'touchend', preventDefault);
14249                 off(document, 'touchend touchcancel', cancelClickPrevent);
14250         },
14251
14252         _cancel: function () {
14253                 clearTimeout(this._holdTimeout);
14254                 off(document, 'touchend touchcancel contextmenu', this._cancel, this);
14255                 off(document, 'touchmove', this._onMove, this);
14256         },
14257
14258         _onMove: function (e) {
14259                 var first = e.touches[0];
14260                 this._newPos = new Point(first.clientX, first.clientY);
14261         },
14262
14263         _isTapValid: function () {
14264                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
14265         },
14266
14267         _simulateEvent: function (type, e) {
14268                 var simulatedEvent = new MouseEvent(type, {
14269                         bubbles: true,
14270                         cancelable: true,
14271                         view: window,
14272                         // detail: 1,
14273                         screenX: e.screenX,
14274                         screenY: e.screenY,
14275                         clientX: e.clientX,
14276                         clientY: e.clientY,
14277                         // button: 2,
14278                         // buttons: 2
14279                 });
14280
14281                 simulatedEvent._simulated = true;
14282
14283                 e.target.dispatchEvent(simulatedEvent);
14284         }
14285   });
14286
14287   // @section Handlers
14288   // @property tapHold: Handler
14289   // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
14290   Map.addInitHook('addHandler', 'tapHold', TapHold);
14291
14292   /*
14293    * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
14294    */
14295
14296   // @namespace Map
14297   // @section Interaction Options
14298   Map.mergeOptions({
14299         // @section Touch interaction options
14300         // @option touchZoom: Boolean|String = *
14301         // Whether the map can be zoomed by touch-dragging with two fingers. If
14302         // passed `'center'`, it will zoom to the center of the view regardless of
14303         // where the touch events (fingers) were. Enabled for touch-capable web
14304         // browsers.
14305         touchZoom: Browser.touch,
14306
14307         // @option bounceAtZoomLimits: Boolean = true
14308         // Set it to false if you don't want the map to zoom beyond min/max zoom
14309         // and then bounce back when pinch-zooming.
14310         bounceAtZoomLimits: true
14311   });
14312
14313   var TouchZoom = Handler.extend({
14314         addHooks: function () {
14315                 addClass(this._map._container, 'leaflet-touch-zoom');
14316                 on(this._map._container, 'touchstart', this._onTouchStart, this);
14317         },
14318
14319         removeHooks: function () {
14320                 removeClass(this._map._container, 'leaflet-touch-zoom');
14321                 off(this._map._container, 'touchstart', this._onTouchStart, this);
14322         },
14323
14324         _onTouchStart: function (e) {
14325                 var map = this._map;
14326                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
14327
14328                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
14329                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
14330
14331                 this._centerPoint = map.getSize()._divideBy(2);
14332                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
14333                 if (map.options.touchZoom !== 'center') {
14334                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
14335                 }
14336
14337                 this._startDist = p1.distanceTo(p2);
14338                 this._startZoom = map.getZoom();
14339
14340                 this._moved = false;
14341                 this._zooming = true;
14342
14343                 map._stop();
14344
14345                 on(document, 'touchmove', this._onTouchMove, this);
14346                 on(document, 'touchend touchcancel', this._onTouchEnd, this);
14347
14348                 preventDefault(e);
14349         },
14350
14351         _onTouchMove: function (e) {
14352                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
14353
14354                 var map = this._map,
14355                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
14356                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
14357                     scale = p1.distanceTo(p2) / this._startDist;
14358
14359                 this._zoom = map.getScaleZoom(scale, this._startZoom);
14360
14361                 if (!map.options.bounceAtZoomLimits && (
14362                         (this._zoom < map.getMinZoom() && scale < 1) ||
14363                         (this._zoom > map.getMaxZoom() && scale > 1))) {
14364                         this._zoom = map._limitZoom(this._zoom);
14365                 }
14366
14367                 if (map.options.touchZoom === 'center') {
14368                         this._center = this._startLatLng;
14369                         if (scale === 1) { return; }
14370                 } else {
14371                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
14372                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
14373                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
14374                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
14375                 }
14376
14377                 if (!this._moved) {
14378                         map._moveStart(true, false);
14379                         this._moved = true;
14380                 }
14381
14382                 cancelAnimFrame(this._animRequest);
14383
14384                 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
14385                 this._animRequest = requestAnimFrame(moveFn, this, true);
14386
14387                 preventDefault(e);
14388         },
14389
14390         _onTouchEnd: function () {
14391                 if (!this._moved || !this._zooming) {
14392                         this._zooming = false;
14393                         return;
14394                 }
14395
14396                 this._zooming = false;
14397                 cancelAnimFrame(this._animRequest);
14398
14399                 off(document, 'touchmove', this._onTouchMove, this);
14400                 off(document, 'touchend touchcancel', this._onTouchEnd, this);
14401
14402                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
14403                 if (this._map.options.zoomAnimation) {
14404                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
14405                 } else {
14406                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
14407                 }
14408         }
14409   });
14410
14411   // @section Handlers
14412   // @property touchZoom: Handler
14413   // Touch zoom handler.
14414   Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
14415
14416   Map.BoxZoom = BoxZoom;
14417   Map.DoubleClickZoom = DoubleClickZoom;
14418   Map.Drag = Drag;
14419   Map.Keyboard = Keyboard;
14420   Map.ScrollWheelZoom = ScrollWheelZoom;
14421   Map.TapHold = TapHold;
14422   Map.TouchZoom = TouchZoom;
14423
14424   exports.Bounds = Bounds;
14425   exports.Browser = Browser;
14426   exports.CRS = CRS;
14427   exports.Canvas = Canvas;
14428   exports.Circle = Circle;
14429   exports.CircleMarker = CircleMarker;
14430   exports.Class = Class;
14431   exports.Control = Control;
14432   exports.DivIcon = DivIcon;
14433   exports.DivOverlay = DivOverlay;
14434   exports.DomEvent = DomEvent;
14435   exports.DomUtil = DomUtil;
14436   exports.Draggable = Draggable;
14437   exports.Evented = Evented;
14438   exports.FeatureGroup = FeatureGroup;
14439   exports.GeoJSON = GeoJSON;
14440   exports.GridLayer = GridLayer;
14441   exports.Handler = Handler;
14442   exports.Icon = Icon;
14443   exports.ImageOverlay = ImageOverlay;
14444   exports.LatLng = LatLng;
14445   exports.LatLngBounds = LatLngBounds;
14446   exports.Layer = Layer;
14447   exports.LayerGroup = LayerGroup;
14448   exports.LineUtil = LineUtil;
14449   exports.Map = Map;
14450   exports.Marker = Marker;
14451   exports.Mixin = Mixin;
14452   exports.Path = Path;
14453   exports.Point = Point;
14454   exports.PolyUtil = PolyUtil;
14455   exports.Polygon = Polygon;
14456   exports.Polyline = Polyline;
14457   exports.Popup = Popup;
14458   exports.PosAnimation = PosAnimation;
14459   exports.Projection = index;
14460   exports.Rectangle = Rectangle;
14461   exports.Renderer = Renderer;
14462   exports.SVG = SVG;
14463   exports.SVGOverlay = SVGOverlay;
14464   exports.TileLayer = TileLayer;
14465   exports.Tooltip = Tooltip;
14466   exports.Transformation = Transformation;
14467   exports.Util = Util;
14468   exports.VideoOverlay = VideoOverlay;
14469   exports.bind = bind;
14470   exports.bounds = toBounds;
14471   exports.canvas = canvas;
14472   exports.circle = circle;
14473   exports.circleMarker = circleMarker;
14474   exports.control = control;
14475   exports.divIcon = divIcon;
14476   exports.extend = extend;
14477   exports.featureGroup = featureGroup;
14478   exports.geoJSON = geoJSON;
14479   exports.geoJson = geoJson;
14480   exports.gridLayer = gridLayer;
14481   exports.icon = icon;
14482   exports.imageOverlay = imageOverlay;
14483   exports.latLng = toLatLng;
14484   exports.latLngBounds = toLatLngBounds;
14485   exports.layerGroup = layerGroup;
14486   exports.map = createMap;
14487   exports.marker = marker;
14488   exports.point = toPoint;
14489   exports.polygon = polygon;
14490   exports.polyline = polyline;
14491   exports.popup = popup;
14492   exports.rectangle = rectangle;
14493   exports.setOptions = setOptions;
14494   exports.stamp = stamp;
14495   exports.svg = svg;
14496   exports.svgOverlay = svgOverlay;
14497   exports.tileLayer = tileLayer;
14498   exports.tooltip = tooltip;
14499   exports.transformation = toTransformation;
14500   exports.version = version;
14501   exports.videoOverlay = videoOverlay;
14502
14503   var oldL = window.L;
14504   exports.noConflict = function() {
14505         window.L = oldL;
14506         return this;
14507   }
14508   // Always export us to window global (see #2364)
14509   window.L = exports;
14510
14511 }));
14512 //# sourceMappingURL=leaflet-src.js.map