minor fix
[wolnelektury.git] / src / wolnelektury / static / contrib / leaflet-1.9.4 / leaflet-src.esm.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 var version = "1.9.4";
7
8 /*\r
9  * @namespace Util\r
10  *\r
11  * Various utility functions, used by Leaflet internally.\r
12  */\r
13 \r
14 // @function extend(dest: Object, src?: Object): Object\r
15 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.\r
16 function extend(dest) {\r
17         var i, j, len, src;\r
18 \r
19         for (j = 1, len = arguments.length; j < len; j++) {\r
20                 src = arguments[j];\r
21                 for (i in src) {\r
22                         dest[i] = src[i];\r
23                 }\r
24         }\r
25         return dest;\r
26 }\r
27 \r
28 // @function create(proto: Object, properties?: Object): Object\r
29 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)\r
30 var create$2 = Object.create || (function () {\r
31         function F() {}\r
32         return function (proto) {\r
33                 F.prototype = proto;\r
34                 return new F();\r
35         };\r
36 })();\r
37 \r
38 // @function bind(fn: Function, …): Function\r
39 // 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
40 // Has a `L.bind()` shortcut.\r
41 function bind(fn, obj) {\r
42         var slice = Array.prototype.slice;\r
43 \r
44         if (fn.bind) {\r
45                 return fn.bind.apply(fn, slice.call(arguments, 1));\r
46         }\r
47 \r
48         var args = slice.call(arguments, 2);\r
49 \r
50         return function () {\r
51                 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);\r
52         };\r
53 }\r
54 \r
55 // @property lastId: Number\r
56 // Last unique ID used by [`stamp()`](#util-stamp)\r
57 var lastId = 0;\r
58 \r
59 // @function stamp(obj: Object): Number\r
60 // Returns the unique ID of an object, assigning it one if it doesn't have it.\r
61 function stamp(obj) {\r
62         if (!('_leaflet_id' in obj)) {\r
63                 obj['_leaflet_id'] = ++lastId;\r
64         }\r
65         return obj._leaflet_id;\r
66 }\r
67 \r
68 // @function throttle(fn: Function, time: Number, context: Object): Function\r
69 // Returns a function which executes function `fn` with the given scope `context`\r
70 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function\r
71 // `fn` will be called no more than one time per given amount of `time`. The arguments\r
72 // received by the bound function will be any arguments passed when binding the\r
73 // function, followed by any arguments passed when invoking the bound function.\r
74 // Has an `L.throttle` shortcut.\r
75 function throttle(fn, time, context) {\r
76         var lock, args, wrapperFn, later;\r
77 \r
78         later = function () {\r
79                 // reset lock and call if queued\r
80                 lock = false;\r
81                 if (args) {\r
82                         wrapperFn.apply(context, args);\r
83                         args = false;\r
84                 }\r
85         };\r
86 \r
87         wrapperFn = function () {\r
88                 if (lock) {\r
89                         // called too soon, queue to call later\r
90                         args = arguments;\r
91 \r
92                 } else {\r
93                         // call and lock until later\r
94                         fn.apply(context, arguments);\r
95                         setTimeout(later, time);\r
96                         lock = true;\r
97                 }\r
98         };\r
99 \r
100         return wrapperFn;\r
101 }\r
102 \r
103 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number\r
104 // Returns the number `num` modulo `range` in such a way so it lies within\r
105 // `range[0]` and `range[1]`. The returned value will be always smaller than\r
106 // `range[1]` unless `includeMax` is set to `true`.\r
107 function wrapNum(x, range, includeMax) {\r
108         var max = range[1],\r
109             min = range[0],\r
110             d = max - min;\r
111         return x === max && includeMax ? x : ((x - min) % d + d) % d + min;\r
112 }\r
113 \r
114 // @function falseFn(): Function\r
115 // Returns a function which always returns `false`.\r
116 function falseFn() { return false; }\r
117 \r
118 // @function formatNum(num: Number, precision?: Number|false): Number\r
119 // Returns the number `num` rounded with specified `precision`.\r
120 // The default `precision` value is 6 decimal places.\r
121 // `false` can be passed to skip any processing (can be useful to avoid round-off errors).\r
122 function formatNum(num, precision) {\r
123         if (precision === false) { return num; }\r
124         var pow = Math.pow(10, precision === undefined ? 6 : precision);\r
125         return Math.round(num * pow) / pow;\r
126 }\r
127 \r
128 // @function trim(str: String): String\r
129 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)\r
130 function trim(str) {\r
131         return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');\r
132 }\r
133 \r
134 // @function splitWords(str: String): String[]\r
135 // Trims and splits the string on whitespace and returns the array of parts.\r
136 function splitWords(str) {\r
137         return trim(str).split(/\s+/);\r
138 }\r
139 \r
140 // @function setOptions(obj: Object, options: Object): Object\r
141 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.\r
142 function setOptions(obj, options) {\r
143         if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {\r
144                 obj.options = obj.options ? create$2(obj.options) : {};\r
145         }\r
146         for (var i in options) {\r
147                 obj.options[i] = options[i];\r
148         }\r
149         return obj.options;\r
150 }\r
151 \r
152 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String\r
153 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`\r
154 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will\r
155 // be appended at the end. If `uppercase` is `true`, the parameter names will\r
156 // be uppercased (e.g. `'?A=foo&B=bar'`)\r
157 function getParamString(obj, existingUrl, uppercase) {\r
158         var params = [];\r
159         for (var i in obj) {\r
160                 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));\r
161         }\r
162         return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');\r
163 }\r
164 \r
165 var templateRe = /\{ *([\w_ -]+) *\}/g;\r
166 \r
167 // @function template(str: String, data: Object): String\r
168 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`\r
169 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string\r
170 // `('Hello foo, bar')`. You can also specify functions instead of strings for\r
171 // data values — they will be evaluated passing `data` as an argument.\r
172 function template(str, data) {\r
173         return str.replace(templateRe, function (str, key) {\r
174                 var value = data[key];\r
175 \r
176                 if (value === undefined) {\r
177                         throw new Error('No value provided for variable ' + str);\r
178 \r
179                 } else if (typeof value === 'function') {\r
180                         value = value(data);\r
181                 }\r
182                 return value;\r
183         });\r
184 }\r
185 \r
186 // @function isArray(obj): Boolean\r
187 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)\r
188 var isArray = Array.isArray || function (obj) {\r
189         return (Object.prototype.toString.call(obj) === '[object Array]');\r
190 };\r
191 \r
192 // @function indexOf(array: Array, el: Object): Number\r
193 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)\r
194 function indexOf(array, el) {\r
195         for (var i = 0; i < array.length; i++) {\r
196                 if (array[i] === el) { return i; }\r
197         }\r
198         return -1;\r
199 }\r
200 \r
201 // @property emptyImageUrl: String\r
202 // Data URI string containing a base64-encoded empty GIF image.\r
203 // Used as a hack to free memory from unused images on WebKit-powered\r
204 // mobile devices (by setting image `src` to this string).\r
205 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';\r
206 \r
207 // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/\r
208 \r
209 function getPrefixed(name) {\r
210         return window['webkit' + name] || window['moz' + name] || window['ms' + name];\r
211 }\r
212 \r
213 var lastTime = 0;\r
214 \r
215 // fallback for IE 7-8\r
216 function timeoutDefer(fn) {\r
217         var time = +new Date(),\r
218             timeToCall = Math.max(0, 16 - (time - lastTime));\r
219 \r
220         lastTime = time + timeToCall;\r
221         return window.setTimeout(fn, timeToCall);\r
222 }\r
223 \r
224 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;\r
225 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||\r
226                 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };\r
227 \r
228 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number\r
229 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to\r
230 // `context` if given. When `immediate` is set, `fn` is called immediately if\r
231 // the browser doesn't have native support for\r
232 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),\r
233 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.\r
234 function requestAnimFrame(fn, context, immediate) {\r
235         if (immediate && requestFn === timeoutDefer) {\r
236                 fn.call(context);\r
237         } else {\r
238                 return requestFn.call(window, bind(fn, context));\r
239         }\r
240 }\r
241 \r
242 // @function cancelAnimFrame(id: Number): undefined\r
243 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).\r
244 function cancelAnimFrame(id) {\r
245         if (id) {\r
246                 cancelFn.call(window, id);\r
247         }\r
248 }
249
250 var Util = {
251   __proto__: null,
252   extend: extend,
253   create: create$2,
254   bind: bind,
255   get lastId () { return lastId; },
256   stamp: stamp,
257   throttle: throttle,
258   wrapNum: wrapNum,
259   falseFn: falseFn,
260   formatNum: formatNum,
261   trim: trim,
262   splitWords: splitWords,
263   setOptions: setOptions,
264   getParamString: getParamString,
265   template: template,
266   isArray: isArray,
267   indexOf: indexOf,
268   emptyImageUrl: emptyImageUrl,
269   requestFn: requestFn,
270   cancelFn: cancelFn,
271   requestAnimFrame: requestAnimFrame,
272   cancelAnimFrame: cancelAnimFrame
273 };
274
275 // @class Class\r
276 // @aka L.Class\r
277 \r
278 // @section\r
279 // @uninheritable\r
280 \r
281 // Thanks to John Resig and Dean Edwards for inspiration!\r
282 \r
283 function Class() {}\r
284 \r
285 Class.extend = function (props) {\r
286 \r
287         // @function extend(props: Object): Function\r
288         // [Extends the current class](#class-inheritance) given the properties to be included.\r
289         // Returns a Javascript function that is a class constructor (to be called with `new`).\r
290         var NewClass = function () {\r
291 \r
292                 setOptions(this);\r
293 \r
294                 // call the constructor\r
295                 if (this.initialize) {\r
296                         this.initialize.apply(this, arguments);\r
297                 }\r
298 \r
299                 // call all constructor hooks\r
300                 this.callInitHooks();\r
301         };\r
302 \r
303         var parentProto = NewClass.__super__ = this.prototype;\r
304 \r
305         var proto = create$2(parentProto);\r
306         proto.constructor = NewClass;\r
307 \r
308         NewClass.prototype = proto;\r
309 \r
310         // inherit parent's statics\r
311         for (var i in this) {\r
312                 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {\r
313                         NewClass[i] = this[i];\r
314                 }\r
315         }\r
316 \r
317         // mix static properties into the class\r
318         if (props.statics) {\r
319                 extend(NewClass, props.statics);\r
320         }\r
321 \r
322         // mix includes into the prototype\r
323         if (props.includes) {\r
324                 checkDeprecatedMixinEvents(props.includes);\r
325                 extend.apply(null, [proto].concat(props.includes));\r
326         }\r
327 \r
328         // mix given properties into the prototype\r
329         extend(proto, props);\r
330         delete proto.statics;\r
331         delete proto.includes;\r
332 \r
333         // merge options\r
334         if (proto.options) {\r
335                 proto.options = parentProto.options ? create$2(parentProto.options) : {};\r
336                 extend(proto.options, props.options);\r
337         }\r
338 \r
339         proto._initHooks = [];\r
340 \r
341         // add method for calling all hooks\r
342         proto.callInitHooks = function () {\r
343 \r
344                 if (this._initHooksCalled) { return; }\r
345 \r
346                 if (parentProto.callInitHooks) {\r
347                         parentProto.callInitHooks.call(this);\r
348                 }\r
349 \r
350                 this._initHooksCalled = true;\r
351 \r
352                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {\r
353                         proto._initHooks[i].call(this);\r
354                 }\r
355         };\r
356 \r
357         return NewClass;\r
358 };\r
359 \r
360 \r
361 // @function include(properties: Object): this\r
362 // [Includes a mixin](#class-includes) into the current class.\r
363 Class.include = function (props) {\r
364         var parentOptions = this.prototype.options;\r
365         extend(this.prototype, props);\r
366         if (props.options) {\r
367                 this.prototype.options = parentOptions;\r
368                 this.mergeOptions(props.options);\r
369         }\r
370         return this;\r
371 };\r
372 \r
373 // @function mergeOptions(options: Object): this\r
374 // [Merges `options`](#class-options) into the defaults of the class.\r
375 Class.mergeOptions = function (options) {\r
376         extend(this.prototype.options, options);\r
377         return this;\r
378 };\r
379 \r
380 // @function addInitHook(fn: Function): this\r
381 // Adds a [constructor hook](#class-constructor-hooks) to the class.\r
382 Class.addInitHook = function (fn) { // (Function) || (String, args...)\r
383         var args = Array.prototype.slice.call(arguments, 1);\r
384 \r
385         var init = typeof fn === 'function' ? fn : function () {\r
386                 this[fn].apply(this, args);\r
387         };\r
388 \r
389         this.prototype._initHooks = this.prototype._initHooks || [];\r
390         this.prototype._initHooks.push(init);\r
391         return this;\r
392 };\r
393 \r
394 function checkDeprecatedMixinEvents(includes) {\r
395         /* global L: true */\r
396         if (typeof L === 'undefined' || !L || !L.Mixin) { return; }\r
397 \r
398         includes = isArray(includes) ? includes : [includes];\r
399 \r
400         for (var i = 0; i < includes.length; i++) {\r
401                 if (includes[i] === L.Mixin.Events) {\r
402                         console.warn('Deprecated include of L.Mixin.Events: ' +\r
403                                 'this property will be removed in future releases, ' +\r
404                                 'please inherit from L.Evented instead.', new Error().stack);\r
405                 }\r
406         }\r
407 }
408
409 /*\r
410  * @class Evented\r
411  * @aka L.Evented\r
412  * @inherits Class\r
413  *\r
414  * 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
415  *\r
416  * @example\r
417  *\r
418  * ```js\r
419  * map.on('click', function(e) {\r
420  *      alert(e.latlng);\r
421  * } );\r
422  * ```\r
423  *\r
424  * 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
425  *\r
426  * ```js\r
427  * function onClick(e) { ... }\r
428  *\r
429  * map.on('click', onClick);\r
430  * map.off('click', onClick);\r
431  * ```\r
432  */\r
433 \r
434 var Events = {\r
435         /* @method on(type: String, fn: Function, context?: Object): this\r
436          * 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
437          *\r
438          * @alternative\r
439          * @method on(eventMap: Object): this\r
440          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`\r
441          */\r
442         on: function (types, fn, context) {\r
443 \r
444                 // types can be a map of types/handlers\r
445                 if (typeof types === 'object') {\r
446                         for (var type in types) {\r
447                                 // we don't process space-separated events here for performance;\r
448                                 // it's a hot path since Layer uses the on(obj) syntax\r
449                                 this._on(type, types[type], fn);\r
450                         }\r
451 \r
452                 } else {\r
453                         // types can be a string of space-separated words\r
454                         types = splitWords(types);\r
455 \r
456                         for (var i = 0, len = types.length; i < len; i++) {\r
457                                 this._on(types[i], fn, context);\r
458                         }\r
459                 }\r
460 \r
461                 return this;\r
462         },\r
463 \r
464         /* @method off(type: String, fn?: Function, context?: Object): this\r
465          * 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
466          *\r
467          * @alternative\r
468          * @method off(eventMap: Object): this\r
469          * Removes a set of type/listener pairs.\r
470          *\r
471          * @alternative\r
472          * @method off: this\r
473          * Removes all listeners to all events on the object. This includes implicitly attached events.\r
474          */\r
475         off: function (types, fn, context) {\r
476 \r
477                 if (!arguments.length) {\r
478                         // clear all listeners if called without arguments\r
479                         delete this._events;\r
480 \r
481                 } else if (typeof types === 'object') {\r
482                         for (var type in types) {\r
483                                 this._off(type, types[type], fn);\r
484                         }\r
485 \r
486                 } else {\r
487                         types = splitWords(types);\r
488 \r
489                         var removeAll = arguments.length === 1;\r
490                         for (var i = 0, len = types.length; i < len; i++) {\r
491                                 if (removeAll) {\r
492                                         this._off(types[i]);\r
493                                 } else {\r
494                                         this._off(types[i], fn, context);\r
495                                 }\r
496                         }\r
497                 }\r
498 \r
499                 return this;\r
500         },\r
501 \r
502         // attach listener (without syntactic sugar now)\r
503         _on: function (type, fn, context, _once) {\r
504                 if (typeof fn !== 'function') {\r
505                         console.warn('wrong listener type: ' + typeof fn);\r
506                         return;\r
507                 }\r
508 \r
509                 // check if fn already there\r
510                 if (this._listens(type, fn, context) !== false) {\r
511                         return;\r
512                 }\r
513 \r
514                 if (context === this) {\r
515                         // Less memory footprint.\r
516                         context = undefined;\r
517                 }\r
518 \r
519                 var newListener = {fn: fn, ctx: context};\r
520                 if (_once) {\r
521                         newListener.once = true;\r
522                 }\r
523 \r
524                 this._events = this._events || {};\r
525                 this._events[type] = this._events[type] || [];\r
526                 this._events[type].push(newListener);\r
527         },\r
528 \r
529         _off: function (type, fn, context) {\r
530                 var listeners,\r
531                     i,\r
532                     len;\r
533 \r
534                 if (!this._events) {\r
535                         return;\r
536                 }\r
537 \r
538                 listeners = this._events[type];\r
539                 if (!listeners) {\r
540                         return;\r
541                 }\r
542 \r
543                 if (arguments.length === 1) { // remove all\r
544                         if (this._firingCount) {\r
545                                 // Set all removed listeners to noop\r
546                                 // so they are not called if remove happens in fire\r
547                                 for (i = 0, len = listeners.length; i < len; i++) {\r
548                                         listeners[i].fn = falseFn;\r
549                                 }\r
550                         }\r
551                         // clear all listeners for a type if function isn't specified\r
552                         delete this._events[type];\r
553                         return;\r
554                 }\r
555 \r
556                 if (typeof fn !== 'function') {\r
557                         console.warn('wrong listener type: ' + typeof fn);\r
558                         return;\r
559                 }\r
560 \r
561                 // find fn and remove it\r
562                 var index = this._listens(type, fn, context);\r
563                 if (index !== false) {\r
564                         var listener = listeners[index];\r
565                         if (this._firingCount) {\r
566                                 // set the removed listener to noop so that's not called if remove happens in fire\r
567                                 listener.fn = falseFn;\r
568 \r
569                                 /* copy array in case events are being fired */\r
570                                 this._events[type] = listeners = listeners.slice();\r
571                         }\r
572                         listeners.splice(index, 1);\r
573                 }\r
574         },\r
575 \r
576         // @method fire(type: String, data?: Object, propagate?: Boolean): this\r
577         // Fires an event of the specified type. You can optionally provide a data\r
578         // object — the first argument of the listener function will contain its\r
579         // properties. The event can optionally be propagated to event parents.\r
580         fire: function (type, data, propagate) {\r
581                 if (!this.listens(type, propagate)) { return this; }\r
582 \r
583                 var event = extend({}, data, {\r
584                         type: type,\r
585                         target: this,\r
586                         sourceTarget: data && data.sourceTarget || this\r
587                 });\r
588 \r
589                 if (this._events) {\r
590                         var listeners = this._events[type];\r
591                         if (listeners) {\r
592                                 this._firingCount = (this._firingCount + 1) || 1;\r
593                                 for (var i = 0, len = listeners.length; i < len; i++) {\r
594                                         var l = listeners[i];\r
595                                         // off overwrites l.fn, so we need to copy fn to a var\r
596                                         var fn = l.fn;\r
597                                         if (l.once) {\r
598                                                 this.off(type, fn, l.ctx);\r
599                                         }\r
600                                         fn.call(l.ctx || this, event);\r
601                                 }\r
602 \r
603                                 this._firingCount--;\r
604                         }\r
605                 }\r
606 \r
607                 if (propagate) {\r
608                         // propagate the event to parents (set with addEventParent)\r
609                         this._propagateEvent(event);\r
610                 }\r
611 \r
612                 return this;\r
613         },\r
614 \r
615         // @method listens(type: String, propagate?: Boolean): Boolean\r
616         // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean\r
617         // Returns `true` if a particular event type has any listeners attached to it.\r
618         // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.\r
619         listens: function (type, fn, context, propagate) {\r
620                 if (typeof type !== 'string') {\r
621                         console.warn('"string" type argument expected');\r
622                 }\r
623 \r
624                 // we don't overwrite the input `fn` value, because we need to use it for propagation\r
625                 var _fn = fn;\r
626                 if (typeof fn !== 'function') {\r
627                         propagate = !!fn;\r
628                         _fn = undefined;\r
629                         context = undefined;\r
630                 }\r
631 \r
632                 var listeners = this._events && this._events[type];\r
633                 if (listeners && listeners.length) {\r
634                         if (this._listens(type, _fn, context) !== false) {\r
635                                 return true;\r
636                         }\r
637                 }\r
638 \r
639                 if (propagate) {\r
640                         // also check parents for listeners if event propagates\r
641                         for (var id in this._eventParents) {\r
642                                 if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }\r
643                         }\r
644                 }\r
645                 return false;\r
646         },\r
647 \r
648         // returns the index (number) or false\r
649         _listens: function (type, fn, context) {\r
650                 if (!this._events) {\r
651                         return false;\r
652                 }\r
653 \r
654                 var listeners = this._events[type] || [];\r
655                 if (!fn) {\r
656                         return !!listeners.length;\r
657                 }\r
658 \r
659                 if (context === this) {\r
660                         // Less memory footprint.\r
661                         context = undefined;\r
662                 }\r
663 \r
664                 for (var i = 0, len = listeners.length; i < len; i++) {\r
665                         if (listeners[i].fn === fn && listeners[i].ctx === context) {\r
666                                 return i;\r
667                         }\r
668                 }\r
669                 return false;\r
670 \r
671         },\r
672 \r
673         // @method once(…): this\r
674         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.\r
675         once: function (types, fn, context) {\r
676 \r
677                 // types can be a map of types/handlers\r
678                 if (typeof types === 'object') {\r
679                         for (var type in types) {\r
680                                 // we don't process space-separated events here for performance;\r
681                                 // it's a hot path since Layer uses the on(obj) syntax\r
682                                 this._on(type, types[type], fn, true);\r
683                         }\r
684 \r
685                 } else {\r
686                         // types can be a string of space-separated words\r
687                         types = splitWords(types);\r
688 \r
689                         for (var i = 0, len = types.length; i < len; i++) {\r
690                                 this._on(types[i], fn, context, true);\r
691                         }\r
692                 }\r
693 \r
694                 return this;\r
695         },\r
696 \r
697         // @method addEventParent(obj: Evented): this\r
698         // Adds an event parent - an `Evented` that will receive propagated events\r
699         addEventParent: function (obj) {\r
700                 this._eventParents = this._eventParents || {};\r
701                 this._eventParents[stamp(obj)] = obj;\r
702                 return this;\r
703         },\r
704 \r
705         // @method removeEventParent(obj: Evented): this\r
706         // Removes an event parent, so it will stop receiving propagated events\r
707         removeEventParent: function (obj) {\r
708                 if (this._eventParents) {\r
709                         delete this._eventParents[stamp(obj)];\r
710                 }\r
711                 return this;\r
712         },\r
713 \r
714         _propagateEvent: function (e) {\r
715                 for (var id in this._eventParents) {\r
716                         this._eventParents[id].fire(e.type, extend({\r
717                                 layer: e.target,\r
718                                 propagatedFrom: e.target\r
719                         }, e), true);\r
720                 }\r
721         }\r
722 };\r
723 \r
724 // aliases; we should ditch those eventually\r
725 \r
726 // @method addEventListener(…): this\r
727 // Alias to [`on(…)`](#evented-on)\r
728 Events.addEventListener = Events.on;\r
729 \r
730 // @method removeEventListener(…): this\r
731 // Alias to [`off(…)`](#evented-off)\r
732 \r
733 // @method clearAllEventListeners(…): this\r
734 // Alias to [`off()`](#evented-off)\r
735 Events.removeEventListener = Events.clearAllEventListeners = Events.off;\r
736 \r
737 // @method addOneTimeEventListener(…): this\r
738 // Alias to [`once(…)`](#evented-once)\r
739 Events.addOneTimeEventListener = Events.once;\r
740 \r
741 // @method fireEvent(…): this\r
742 // Alias to [`fire(…)`](#evented-fire)\r
743 Events.fireEvent = Events.fire;\r
744 \r
745 // @method hasEventListeners(…): Boolean\r
746 // Alias to [`listens(…)`](#evented-listens)\r
747 Events.hasEventListeners = Events.listens;\r
748 \r
749 var Evented = Class.extend(Events);
750
751 /*\r
752  * @class Point\r
753  * @aka L.Point\r
754  *\r
755  * Represents a point with `x` and `y` coordinates in pixels.\r
756  *\r
757  * @example\r
758  *\r
759  * ```js\r
760  * var point = L.point(200, 300);\r
761  * ```\r
762  *\r
763  * 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
764  *\r
765  * ```js\r
766  * map.panBy([200, 300]);\r
767  * map.panBy(L.point(200, 300));\r
768  * ```\r
769  *\r
770  * Note that `Point` does not inherit from Leaflet's `Class` object,\r
771  * which means new classes can't inherit from it, and new methods\r
772  * can't be added to it with the `include` function.\r
773  */\r
774 \r
775 function Point(x, y, round) {\r
776         // @property x: Number; The `x` coordinate of the point\r
777         this.x = (round ? Math.round(x) : x);\r
778         // @property y: Number; The `y` coordinate of the point\r
779         this.y = (round ? Math.round(y) : y);\r
780 }\r
781 \r
782 var trunc = Math.trunc || function (v) {\r
783         return v > 0 ? Math.floor(v) : Math.ceil(v);\r
784 };\r
785 \r
786 Point.prototype = {\r
787 \r
788         // @method clone(): Point\r
789         // Returns a copy of the current point.\r
790         clone: function () {\r
791                 return new Point(this.x, this.y);\r
792         },\r
793 \r
794         // @method add(otherPoint: Point): Point\r
795         // Returns the result of addition of the current and the given points.\r
796         add: function (point) {\r
797                 // non-destructive, returns a new point\r
798                 return this.clone()._add(toPoint(point));\r
799         },\r
800 \r
801         _add: function (point) {\r
802                 // destructive, used directly for performance in situations where it's safe to modify existing point\r
803                 this.x += point.x;\r
804                 this.y += point.y;\r
805                 return this;\r
806         },\r
807 \r
808         // @method subtract(otherPoint: Point): Point\r
809         // Returns the result of subtraction of the given point from the current.\r
810         subtract: function (point) {\r
811                 return this.clone()._subtract(toPoint(point));\r
812         },\r
813 \r
814         _subtract: function (point) {\r
815                 this.x -= point.x;\r
816                 this.y -= point.y;\r
817                 return this;\r
818         },\r
819 \r
820         // @method divideBy(num: Number): Point\r
821         // Returns the result of division of the current point by the given number.\r
822         divideBy: function (num) {\r
823                 return this.clone()._divideBy(num);\r
824         },\r
825 \r
826         _divideBy: function (num) {\r
827                 this.x /= num;\r
828                 this.y /= num;\r
829                 return this;\r
830         },\r
831 \r
832         // @method multiplyBy(num: Number): Point\r
833         // Returns the result of multiplication of the current point by the given number.\r
834         multiplyBy: function (num) {\r
835                 return this.clone()._multiplyBy(num);\r
836         },\r
837 \r
838         _multiplyBy: function (num) {\r
839                 this.x *= num;\r
840                 this.y *= num;\r
841                 return this;\r
842         },\r
843 \r
844         // @method scaleBy(scale: Point): Point\r
845         // Multiply each coordinate of the current point by each coordinate of\r
846         // `scale`. In linear algebra terms, multiply the point by the\r
847         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)\r
848         // defined by `scale`.\r
849         scaleBy: function (point) {\r
850                 return new Point(this.x * point.x, this.y * point.y);\r
851         },\r
852 \r
853         // @method unscaleBy(scale: Point): Point\r
854         // Inverse of `scaleBy`. Divide each coordinate of the current point by\r
855         // each coordinate of `scale`.\r
856         unscaleBy: function (point) {\r
857                 return new Point(this.x / point.x, this.y / point.y);\r
858         },\r
859 \r
860         // @method round(): Point\r
861         // Returns a copy of the current point with rounded coordinates.\r
862         round: function () {\r
863                 return this.clone()._round();\r
864         },\r
865 \r
866         _round: function () {\r
867                 this.x = Math.round(this.x);\r
868                 this.y = Math.round(this.y);\r
869                 return this;\r
870         },\r
871 \r
872         // @method floor(): Point\r
873         // Returns a copy of the current point with floored coordinates (rounded down).\r
874         floor: function () {\r
875                 return this.clone()._floor();\r
876         },\r
877 \r
878         _floor: function () {\r
879                 this.x = Math.floor(this.x);\r
880                 this.y = Math.floor(this.y);\r
881                 return this;\r
882         },\r
883 \r
884         // @method ceil(): Point\r
885         // Returns a copy of the current point with ceiled coordinates (rounded up).\r
886         ceil: function () {\r
887                 return this.clone()._ceil();\r
888         },\r
889 \r
890         _ceil: function () {\r
891                 this.x = Math.ceil(this.x);\r
892                 this.y = Math.ceil(this.y);\r
893                 return this;\r
894         },\r
895 \r
896         // @method trunc(): Point\r
897         // Returns a copy of the current point with truncated coordinates (rounded towards zero).\r
898         trunc: function () {\r
899                 return this.clone()._trunc();\r
900         },\r
901 \r
902         _trunc: function () {\r
903                 this.x = trunc(this.x);\r
904                 this.y = trunc(this.y);\r
905                 return this;\r
906         },\r
907 \r
908         // @method distanceTo(otherPoint: Point): Number\r
909         // Returns the cartesian distance between the current and the given points.\r
910         distanceTo: function (point) {\r
911                 point = toPoint(point);\r
912 \r
913                 var x = point.x - this.x,\r
914                     y = point.y - this.y;\r
915 \r
916                 return Math.sqrt(x * x + y * y);\r
917         },\r
918 \r
919         // @method equals(otherPoint: Point): Boolean\r
920         // Returns `true` if the given point has the same coordinates.\r
921         equals: function (point) {\r
922                 point = toPoint(point);\r
923 \r
924                 return point.x === this.x &&\r
925                        point.y === this.y;\r
926         },\r
927 \r
928         // @method contains(otherPoint: Point): Boolean\r
929         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).\r
930         contains: function (point) {\r
931                 point = toPoint(point);\r
932 \r
933                 return Math.abs(point.x) <= Math.abs(this.x) &&\r
934                        Math.abs(point.y) <= Math.abs(this.y);\r
935         },\r
936 \r
937         // @method toString(): String\r
938         // Returns a string representation of the point for debugging purposes.\r
939         toString: function () {\r
940                 return 'Point(' +\r
941                         formatNum(this.x) + ', ' +\r
942                         formatNum(this.y) + ')';\r
943         }\r
944 };\r
945 \r
946 // @factory L.point(x: Number, y: Number, round?: Boolean)\r
947 // 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
948 \r
949 // @alternative\r
950 // @factory L.point(coords: Number[])\r
951 // Expects an array of the form `[x, y]` instead.\r
952 \r
953 // @alternative\r
954 // @factory L.point(coords: Object)\r
955 // Expects a plain object of the form `{x: Number, y: Number}` instead.\r
956 function toPoint(x, y, round) {\r
957         if (x instanceof Point) {\r
958                 return x;\r
959         }\r
960         if (isArray(x)) {\r
961                 return new Point(x[0], x[1]);\r
962         }\r
963         if (x === undefined || x === null) {\r
964                 return x;\r
965         }\r
966         if (typeof x === 'object' && 'x' in x && 'y' in x) {\r
967                 return new Point(x.x, x.y);\r
968         }\r
969         return new Point(x, y, round);\r
970 }
971
972 /*\r
973  * @class Bounds\r
974  * @aka L.Bounds\r
975  *\r
976  * Represents a rectangular area in pixel coordinates.\r
977  *\r
978  * @example\r
979  *\r
980  * ```js\r
981  * var p1 = L.point(10, 10),\r
982  * p2 = L.point(40, 60),\r
983  * bounds = L.bounds(p1, p2);\r
984  * ```\r
985  *\r
986  * 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
987  *\r
988  * ```js\r
989  * otherBounds.intersects([[10, 10], [40, 60]]);\r
990  * ```\r
991  *\r
992  * Note that `Bounds` does not inherit from Leaflet's `Class` object,\r
993  * which means new classes can't inherit from it, and new methods\r
994  * can't be added to it with the `include` function.\r
995  */\r
996 \r
997 function Bounds(a, b) {\r
998         if (!a) { return; }\r
999 \r
1000         var points = b ? [a, b] : a;\r
1001 \r
1002         for (var i = 0, len = points.length; i < len; i++) {\r
1003                 this.extend(points[i]);\r
1004         }\r
1005 }\r
1006 \r
1007 Bounds.prototype = {\r
1008         // @method extend(point: Point): this\r
1009         // Extends the bounds to contain the given point.\r
1010 \r
1011         // @alternative\r
1012         // @method extend(otherBounds: Bounds): this\r
1013         // Extend the bounds to contain the given bounds\r
1014         extend: function (obj) {\r
1015                 var min2, max2;\r
1016                 if (!obj) { return this; }\r
1017 \r
1018                 if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {\r
1019                         min2 = max2 = toPoint(obj);\r
1020                 } else {\r
1021                         obj = toBounds(obj);\r
1022                         min2 = obj.min;\r
1023                         max2 = obj.max;\r
1024 \r
1025                         if (!min2 || !max2) { return this; }\r
1026                 }\r
1027 \r
1028                 // @property min: Point\r
1029                 // The top left corner of the rectangle.\r
1030                 // @property max: Point\r
1031                 // The bottom right corner of the rectangle.\r
1032                 if (!this.min && !this.max) {\r
1033                         this.min = min2.clone();\r
1034                         this.max = max2.clone();\r
1035                 } else {\r
1036                         this.min.x = Math.min(min2.x, this.min.x);\r
1037                         this.max.x = Math.max(max2.x, this.max.x);\r
1038                         this.min.y = Math.min(min2.y, this.min.y);\r
1039                         this.max.y = Math.max(max2.y, this.max.y);\r
1040                 }\r
1041                 return this;\r
1042         },\r
1043 \r
1044         // @method getCenter(round?: Boolean): Point\r
1045         // Returns the center point of the bounds.\r
1046         getCenter: function (round) {\r
1047                 return toPoint(\r
1048                         (this.min.x + this.max.x) / 2,\r
1049                         (this.min.y + this.max.y) / 2, round);\r
1050         },\r
1051 \r
1052         // @method getBottomLeft(): Point\r
1053         // Returns the bottom-left point of the bounds.\r
1054         getBottomLeft: function () {\r
1055                 return toPoint(this.min.x, this.max.y);\r
1056         },\r
1057 \r
1058         // @method getTopRight(): Point\r
1059         // Returns the top-right point of the bounds.\r
1060         getTopRight: function () { // -> Point\r
1061                 return toPoint(this.max.x, this.min.y);\r
1062         },\r
1063 \r
1064         // @method getTopLeft(): Point\r
1065         // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).\r
1066         getTopLeft: function () {\r
1067                 return this.min; // left, top\r
1068         },\r
1069 \r
1070         // @method getBottomRight(): Point\r
1071         // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).\r
1072         getBottomRight: function () {\r
1073                 return this.max; // right, bottom\r
1074         },\r
1075 \r
1076         // @method getSize(): Point\r
1077         // Returns the size of the given bounds\r
1078         getSize: function () {\r
1079                 return this.max.subtract(this.min);\r
1080         },\r
1081 \r
1082         // @method contains(otherBounds: Bounds): Boolean\r
1083         // Returns `true` if the rectangle contains the given one.\r
1084         // @alternative\r
1085         // @method contains(point: Point): Boolean\r
1086         // Returns `true` if the rectangle contains the given point.\r
1087         contains: function (obj) {\r
1088                 var min, max;\r
1089 \r
1090                 if (typeof obj[0] === 'number' || obj instanceof Point) {\r
1091                         obj = toPoint(obj);\r
1092                 } else {\r
1093                         obj = toBounds(obj);\r
1094                 }\r
1095 \r
1096                 if (obj instanceof Bounds) {\r
1097                         min = obj.min;\r
1098                         max = obj.max;\r
1099                 } else {\r
1100                         min = max = obj;\r
1101                 }\r
1102 \r
1103                 return (min.x >= this.min.x) &&\r
1104                        (max.x <= this.max.x) &&\r
1105                        (min.y >= this.min.y) &&\r
1106                        (max.y <= this.max.y);\r
1107         },\r
1108 \r
1109         // @method intersects(otherBounds: Bounds): Boolean\r
1110         // Returns `true` if the rectangle intersects the given bounds. Two bounds\r
1111         // intersect if they have at least one point in common.\r
1112         intersects: function (bounds) { // (Bounds) -> Boolean\r
1113                 bounds = toBounds(bounds);\r
1114 \r
1115                 var min = this.min,\r
1116                     max = this.max,\r
1117                     min2 = bounds.min,\r
1118                     max2 = bounds.max,\r
1119                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),\r
1120                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);\r
1121 \r
1122                 return xIntersects && yIntersects;\r
1123         },\r
1124 \r
1125         // @method overlaps(otherBounds: Bounds): Boolean\r
1126         // Returns `true` if the rectangle overlaps the given bounds. Two bounds\r
1127         // overlap if their intersection is an area.\r
1128         overlaps: function (bounds) { // (Bounds) -> Boolean\r
1129                 bounds = toBounds(bounds);\r
1130 \r
1131                 var min = this.min,\r
1132                     max = this.max,\r
1133                     min2 = bounds.min,\r
1134                     max2 = bounds.max,\r
1135                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),\r
1136                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);\r
1137 \r
1138                 return xOverlaps && yOverlaps;\r
1139         },\r
1140 \r
1141         // @method isValid(): Boolean\r
1142         // Returns `true` if the bounds are properly initialized.\r
1143         isValid: function () {\r
1144                 return !!(this.min && this.max);\r
1145         },\r
1146 \r
1147 \r
1148         // @method pad(bufferRatio: Number): Bounds\r
1149         // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.\r
1150         // For example, a ratio of 0.5 extends the bounds by 50% in each direction.\r
1151         // Negative values will retract the bounds.\r
1152         pad: function (bufferRatio) {\r
1153                 var min = this.min,\r
1154                 max = this.max,\r
1155                 heightBuffer = Math.abs(min.x - max.x) * bufferRatio,\r
1156                 widthBuffer = Math.abs(min.y - max.y) * bufferRatio;\r
1157 \r
1158 \r
1159                 return toBounds(\r
1160                         toPoint(min.x - heightBuffer, min.y - widthBuffer),\r
1161                         toPoint(max.x + heightBuffer, max.y + widthBuffer));\r
1162         },\r
1163 \r
1164 \r
1165         // @method equals(otherBounds: Bounds): Boolean\r
1166         // Returns `true` if the rectangle is equivalent to the given bounds.\r
1167         equals: function (bounds) {\r
1168                 if (!bounds) { return false; }\r
1169 \r
1170                 bounds = toBounds(bounds);\r
1171 \r
1172                 return this.min.equals(bounds.getTopLeft()) &&\r
1173                         this.max.equals(bounds.getBottomRight());\r
1174         },\r
1175 };\r
1176 \r
1177 \r
1178 // @factory L.bounds(corner1: Point, corner2: Point)\r
1179 // Creates a Bounds object from two corners coordinate pairs.\r
1180 // @alternative\r
1181 // @factory L.bounds(points: Point[])\r
1182 // Creates a Bounds object from the given array of points.\r
1183 function toBounds(a, b) {\r
1184         if (!a || a instanceof Bounds) {\r
1185                 return a;\r
1186         }\r
1187         return new Bounds(a, b);\r
1188 }
1189
1190 /*\r
1191  * @class LatLngBounds\r
1192  * @aka L.LatLngBounds\r
1193  *\r
1194  * Represents a rectangular geographical area on a map.\r
1195  *\r
1196  * @example\r
1197  *\r
1198  * ```js\r
1199  * var corner1 = L.latLng(40.712, -74.227),\r
1200  * corner2 = L.latLng(40.774, -74.125),\r
1201  * bounds = L.latLngBounds(corner1, corner2);\r
1202  * ```\r
1203  *\r
1204  * 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
1205  *\r
1206  * ```js\r
1207  * map.fitBounds([\r
1208  *      [40.712, -74.227],\r
1209  *      [40.774, -74.125]\r
1210  * ]);\r
1211  * ```\r
1212  *\r
1213  * 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
1214  *\r
1215  * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,\r
1216  * which means new classes can't inherit from it, and new methods\r
1217  * can't be added to it with the `include` function.\r
1218  */\r
1219 \r
1220 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])\r
1221         if (!corner1) { return; }\r
1222 \r
1223         var latlngs = corner2 ? [corner1, corner2] : corner1;\r
1224 \r
1225         for (var i = 0, len = latlngs.length; i < len; i++) {\r
1226                 this.extend(latlngs[i]);\r
1227         }\r
1228 }\r
1229 \r
1230 LatLngBounds.prototype = {\r
1231 \r
1232         // @method extend(latlng: LatLng): this\r
1233         // Extend the bounds to contain the given point\r
1234 \r
1235         // @alternative\r
1236         // @method extend(otherBounds: LatLngBounds): this\r
1237         // Extend the bounds to contain the given bounds\r
1238         extend: function (obj) {\r
1239                 var sw = this._southWest,\r
1240                     ne = this._northEast,\r
1241                     sw2, ne2;\r
1242 \r
1243                 if (obj instanceof LatLng) {\r
1244                         sw2 = obj;\r
1245                         ne2 = obj;\r
1246 \r
1247                 } else if (obj instanceof LatLngBounds) {\r
1248                         sw2 = obj._southWest;\r
1249                         ne2 = obj._northEast;\r
1250 \r
1251                         if (!sw2 || !ne2) { return this; }\r
1252 \r
1253                 } else {\r
1254                         return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;\r
1255                 }\r
1256 \r
1257                 if (!sw && !ne) {\r
1258                         this._southWest = new LatLng(sw2.lat, sw2.lng);\r
1259                         this._northEast = new LatLng(ne2.lat, ne2.lng);\r
1260                 } else {\r
1261                         sw.lat = Math.min(sw2.lat, sw.lat);\r
1262                         sw.lng = Math.min(sw2.lng, sw.lng);\r
1263                         ne.lat = Math.max(ne2.lat, ne.lat);\r
1264                         ne.lng = Math.max(ne2.lng, ne.lng);\r
1265                 }\r
1266 \r
1267                 return this;\r
1268         },\r
1269 \r
1270         // @method pad(bufferRatio: Number): LatLngBounds\r
1271         // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.\r
1272         // For example, a ratio of 0.5 extends the bounds by 50% in each direction.\r
1273         // Negative values will retract the bounds.\r
1274         pad: function (bufferRatio) {\r
1275                 var sw = this._southWest,\r
1276                     ne = this._northEast,\r
1277                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,\r
1278                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;\r
1279 \r
1280                 return new LatLngBounds(\r
1281                         new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),\r
1282                         new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));\r
1283         },\r
1284 \r
1285         // @method getCenter(): LatLng\r
1286         // Returns the center point of the bounds.\r
1287         getCenter: function () {\r
1288                 return new LatLng(\r
1289                         (this._southWest.lat + this._northEast.lat) / 2,\r
1290                         (this._southWest.lng + this._northEast.lng) / 2);\r
1291         },\r
1292 \r
1293         // @method getSouthWest(): LatLng\r
1294         // Returns the south-west point of the bounds.\r
1295         getSouthWest: function () {\r
1296                 return this._southWest;\r
1297         },\r
1298 \r
1299         // @method getNorthEast(): LatLng\r
1300         // Returns the north-east point of the bounds.\r
1301         getNorthEast: function () {\r
1302                 return this._northEast;\r
1303         },\r
1304 \r
1305         // @method getNorthWest(): LatLng\r
1306         // Returns the north-west point of the bounds.\r
1307         getNorthWest: function () {\r
1308                 return new LatLng(this.getNorth(), this.getWest());\r
1309         },\r
1310 \r
1311         // @method getSouthEast(): LatLng\r
1312         // Returns the south-east point of the bounds.\r
1313         getSouthEast: function () {\r
1314                 return new LatLng(this.getSouth(), this.getEast());\r
1315         },\r
1316 \r
1317         // @method getWest(): Number\r
1318         // Returns the west longitude of the bounds\r
1319         getWest: function () {\r
1320                 return this._southWest.lng;\r
1321         },\r
1322 \r
1323         // @method getSouth(): Number\r
1324         // Returns the south latitude of the bounds\r
1325         getSouth: function () {\r
1326                 return this._southWest.lat;\r
1327         },\r
1328 \r
1329         // @method getEast(): Number\r
1330         // Returns the east longitude of the bounds\r
1331         getEast: function () {\r
1332                 return this._northEast.lng;\r
1333         },\r
1334 \r
1335         // @method getNorth(): Number\r
1336         // Returns the north latitude of the bounds\r
1337         getNorth: function () {\r
1338                 return this._northEast.lat;\r
1339         },\r
1340 \r
1341         // @method contains(otherBounds: LatLngBounds): Boolean\r
1342         // Returns `true` if the rectangle contains the given one.\r
1343 \r
1344         // @alternative\r
1345         // @method contains (latlng: LatLng): Boolean\r
1346         // Returns `true` if the rectangle contains the given point.\r
1347         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean\r
1348                 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {\r
1349                         obj = toLatLng(obj);\r
1350                 } else {\r
1351                         obj = toLatLngBounds(obj);\r
1352                 }\r
1353 \r
1354                 var sw = this._southWest,\r
1355                     ne = this._northEast,\r
1356                     sw2, ne2;\r
1357 \r
1358                 if (obj instanceof LatLngBounds) {\r
1359                         sw2 = obj.getSouthWest();\r
1360                         ne2 = obj.getNorthEast();\r
1361                 } else {\r
1362                         sw2 = ne2 = obj;\r
1363                 }\r
1364 \r
1365                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&\r
1366                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);\r
1367         },\r
1368 \r
1369         // @method intersects(otherBounds: LatLngBounds): Boolean\r
1370         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.\r
1371         intersects: function (bounds) {\r
1372                 bounds = toLatLngBounds(bounds);\r
1373 \r
1374                 var sw = this._southWest,\r
1375                     ne = this._northEast,\r
1376                     sw2 = bounds.getSouthWest(),\r
1377                     ne2 = bounds.getNorthEast(),\r
1378 \r
1379                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),\r
1380                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);\r
1381 \r
1382                 return latIntersects && lngIntersects;\r
1383         },\r
1384 \r
1385         // @method overlaps(otherBounds: LatLngBounds): Boolean\r
1386         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.\r
1387         overlaps: function (bounds) {\r
1388                 bounds = toLatLngBounds(bounds);\r
1389 \r
1390                 var sw = this._southWest,\r
1391                     ne = this._northEast,\r
1392                     sw2 = bounds.getSouthWest(),\r
1393                     ne2 = bounds.getNorthEast(),\r
1394 \r
1395                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),\r
1396                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);\r
1397 \r
1398                 return latOverlaps && lngOverlaps;\r
1399         },\r
1400 \r
1401         // @method toBBoxString(): String\r
1402         // 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
1403         toBBoxString: function () {\r
1404                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');\r
1405         },\r
1406 \r
1407         // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean\r
1408         // 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
1409         equals: function (bounds, maxMargin) {\r
1410                 if (!bounds) { return false; }\r
1411 \r
1412                 bounds = toLatLngBounds(bounds);\r
1413 \r
1414                 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&\r
1415                        this._northEast.equals(bounds.getNorthEast(), maxMargin);\r
1416         },\r
1417 \r
1418         // @method isValid(): Boolean\r
1419         // Returns `true` if the bounds are properly initialized.\r
1420         isValid: function () {\r
1421                 return !!(this._southWest && this._northEast);\r
1422         }\r
1423 };\r
1424 \r
1425 // TODO International date line?\r
1426 \r
1427 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)\r
1428 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.\r
1429 \r
1430 // @alternative\r
1431 // @factory L.latLngBounds(latlngs: LatLng[])\r
1432 // 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
1433 function toLatLngBounds(a, b) {\r
1434         if (a instanceof LatLngBounds) {\r
1435                 return a;\r
1436         }\r
1437         return new LatLngBounds(a, b);\r
1438 }
1439
1440 /* @class LatLng\r
1441  * @aka L.LatLng\r
1442  *\r
1443  * Represents a geographical point with a certain latitude and longitude.\r
1444  *\r
1445  * @example\r
1446  *\r
1447  * ```\r
1448  * var latlng = L.latLng(50.5, 30.5);\r
1449  * ```\r
1450  *\r
1451  * 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
1452  *\r
1453  * ```\r
1454  * map.panTo([50, 30]);\r
1455  * map.panTo({lon: 30, lat: 50});\r
1456  * map.panTo({lat: 50, lng: 30});\r
1457  * map.panTo(L.latLng(50, 30));\r
1458  * ```\r
1459  *\r
1460  * Note that `LatLng` does not inherit from Leaflet's `Class` object,\r
1461  * which means new classes can't inherit from it, and new methods\r
1462  * can't be added to it with the `include` function.\r
1463  */\r
1464 \r
1465 function LatLng(lat, lng, alt) {\r
1466         if (isNaN(lat) || isNaN(lng)) {\r
1467                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');\r
1468         }\r
1469 \r
1470         // @property lat: Number\r
1471         // Latitude in degrees\r
1472         this.lat = +lat;\r
1473 \r
1474         // @property lng: Number\r
1475         // Longitude in degrees\r
1476         this.lng = +lng;\r
1477 \r
1478         // @property alt: Number\r
1479         // Altitude in meters (optional)\r
1480         if (alt !== undefined) {\r
1481                 this.alt = +alt;\r
1482         }\r
1483 }\r
1484 \r
1485 LatLng.prototype = {\r
1486         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean\r
1487         // 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
1488         equals: function (obj, maxMargin) {\r
1489                 if (!obj) { return false; }\r
1490 \r
1491                 obj = toLatLng(obj);\r
1492 \r
1493                 var margin = Math.max(\r
1494                         Math.abs(this.lat - obj.lat),\r
1495                         Math.abs(this.lng - obj.lng));\r
1496 \r
1497                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);\r
1498         },\r
1499 \r
1500         // @method toString(): String\r
1501         // Returns a string representation of the point (for debugging purposes).\r
1502         toString: function (precision) {\r
1503                 return 'LatLng(' +\r
1504                         formatNum(this.lat, precision) + ', ' +\r
1505                         formatNum(this.lng, precision) + ')';\r
1506         },\r
1507 \r
1508         // @method distanceTo(otherLatLng: LatLng): Number\r
1509         // 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
1510         distanceTo: function (other) {\r
1511                 return Earth.distance(this, toLatLng(other));\r
1512         },\r
1513 \r
1514         // @method wrap(): LatLng\r
1515         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.\r
1516         wrap: function () {\r
1517                 return Earth.wrapLatLng(this);\r
1518         },\r
1519 \r
1520         // @method toBounds(sizeInMeters: Number): LatLngBounds\r
1521         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.\r
1522         toBounds: function (sizeInMeters) {\r
1523                 var latAccuracy = 180 * sizeInMeters / 40075017,\r
1524                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);\r
1525 \r
1526                 return toLatLngBounds(\r
1527                         [this.lat - latAccuracy, this.lng - lngAccuracy],\r
1528                         [this.lat + latAccuracy, this.lng + lngAccuracy]);\r
1529         },\r
1530 \r
1531         clone: function () {\r
1532                 return new LatLng(this.lat, this.lng, this.alt);\r
1533         }\r
1534 };\r
1535 \r
1536 \r
1537 \r
1538 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng\r
1539 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).\r
1540 \r
1541 // @alternative\r
1542 // @factory L.latLng(coords: Array): LatLng\r
1543 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.\r
1544 \r
1545 // @alternative\r
1546 // @factory L.latLng(coords: Object): LatLng\r
1547 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.\r
1548 \r
1549 function toLatLng(a, b, c) {\r
1550         if (a instanceof LatLng) {\r
1551                 return a;\r
1552         }\r
1553         if (isArray(a) && typeof a[0] !== 'object') {\r
1554                 if (a.length === 3) {\r
1555                         return new LatLng(a[0], a[1], a[2]);\r
1556                 }\r
1557                 if (a.length === 2) {\r
1558                         return new LatLng(a[0], a[1]);\r
1559                 }\r
1560                 return null;\r
1561         }\r
1562         if (a === undefined || a === null) {\r
1563                 return a;\r
1564         }\r
1565         if (typeof a === 'object' && 'lat' in a) {\r
1566                 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);\r
1567         }\r
1568         if (b === undefined) {\r
1569                 return null;\r
1570         }\r
1571         return new LatLng(a, b, c);\r
1572 }
1573
1574 /*\r
1575  * @namespace CRS\r
1576  * @crs L.CRS.Base\r
1577  * Object that defines coordinate reference systems for projecting\r
1578  * geographical points into pixel (screen) coordinates and back (and to\r
1579  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See\r
1580  * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).\r
1581  *\r
1582  * Leaflet defines the most usual CRSs by default. If you want to use a\r
1583  * CRS not defined by default, take a look at the\r
1584  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.\r
1585  *\r
1586  * Note that the CRS instances do not inherit from Leaflet's `Class` object,\r
1587  * and can't be instantiated. Also, new classes can't inherit from them,\r
1588  * and methods can't be added to them with the `include` function.\r
1589  */\r
1590 \r
1591 var CRS = {\r
1592         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point\r
1593         // Projects geographical coordinates into pixel coordinates for a given zoom.\r
1594         latLngToPoint: function (latlng, zoom) {\r
1595                 var projectedPoint = this.projection.project(latlng),\r
1596                     scale = this.scale(zoom);\r
1597 \r
1598                 return this.transformation._transform(projectedPoint, scale);\r
1599         },\r
1600 \r
1601         // @method pointToLatLng(point: Point, zoom: Number): LatLng\r
1602         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given\r
1603         // zoom into geographical coordinates.\r
1604         pointToLatLng: function (point, zoom) {\r
1605                 var scale = this.scale(zoom),\r
1606                     untransformedPoint = this.transformation.untransform(point, scale);\r
1607 \r
1608                 return this.projection.unproject(untransformedPoint);\r
1609         },\r
1610 \r
1611         // @method project(latlng: LatLng): Point\r
1612         // Projects geographical coordinates into coordinates in units accepted for\r
1613         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).\r
1614         project: function (latlng) {\r
1615                 return this.projection.project(latlng);\r
1616         },\r
1617 \r
1618         // @method unproject(point: Point): LatLng\r
1619         // Given a projected coordinate returns the corresponding LatLng.\r
1620         // The inverse of `project`.\r
1621         unproject: function (point) {\r
1622                 return this.projection.unproject(point);\r
1623         },\r
1624 \r
1625         // @method scale(zoom: Number): Number\r
1626         // Returns the scale used when transforming projected coordinates into\r
1627         // pixel coordinates for a particular zoom. For example, it returns\r
1628         // `256 * 2^zoom` for Mercator-based CRS.\r
1629         scale: function (zoom) {\r
1630                 return 256 * Math.pow(2, zoom);\r
1631         },\r
1632 \r
1633         // @method zoom(scale: Number): Number\r
1634         // Inverse of `scale()`, returns the zoom level corresponding to a scale\r
1635         // factor of `scale`.\r
1636         zoom: function (scale) {\r
1637                 return Math.log(scale / 256) / Math.LN2;\r
1638         },\r
1639 \r
1640         // @method getProjectedBounds(zoom: Number): Bounds\r
1641         // Returns the projection's bounds scaled and transformed for the provided `zoom`.\r
1642         getProjectedBounds: function (zoom) {\r
1643                 if (this.infinite) { return null; }\r
1644 \r
1645                 var b = this.projection.bounds,\r
1646                     s = this.scale(zoom),\r
1647                     min = this.transformation.transform(b.min, s),\r
1648                     max = this.transformation.transform(b.max, s);\r
1649 \r
1650                 return new Bounds(min, max);\r
1651         },\r
1652 \r
1653         // @method distance(latlng1: LatLng, latlng2: LatLng): Number\r
1654         // Returns the distance between two geographical coordinates.\r
1655 \r
1656         // @property code: String\r
1657         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)\r
1658         //\r
1659         // @property wrapLng: Number[]\r
1660         // An array of two numbers defining whether the longitude (horizontal) coordinate\r
1661         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most\r
1662         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.\r
1663         //\r
1664         // @property wrapLat: Number[]\r
1665         // Like `wrapLng`, but for the latitude (vertical) axis.\r
1666 \r
1667         // wrapLng: [min, max],\r
1668         // wrapLat: [min, max],\r
1669 \r
1670         // @property infinite: Boolean\r
1671         // If true, the coordinate space will be unbounded (infinite in both axes)\r
1672         infinite: false,\r
1673 \r
1674         // @method wrapLatLng(latlng: LatLng): LatLng\r
1675         // Returns a `LatLng` where lat and lng has been wrapped according to the\r
1676         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.\r
1677         wrapLatLng: function (latlng) {\r
1678                 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,\r
1679                     lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,\r
1680                     alt = latlng.alt;\r
1681 \r
1682                 return new LatLng(lat, lng, alt);\r
1683         },\r
1684 \r
1685         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds\r
1686         // Returns a `LatLngBounds` with the same size as the given one, ensuring\r
1687         // that its center is within the CRS's bounds.\r
1688         // Only accepts actual `L.LatLngBounds` instances, not arrays.\r
1689         wrapLatLngBounds: function (bounds) {\r
1690                 var center = bounds.getCenter(),\r
1691                     newCenter = this.wrapLatLng(center),\r
1692                     latShift = center.lat - newCenter.lat,\r
1693                     lngShift = center.lng - newCenter.lng;\r
1694 \r
1695                 if (latShift === 0 && lngShift === 0) {\r
1696                         return bounds;\r
1697                 }\r
1698 \r
1699                 var sw = bounds.getSouthWest(),\r
1700                     ne = bounds.getNorthEast(),\r
1701                     newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),\r
1702                     newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);\r
1703 \r
1704                 return new LatLngBounds(newSw, newNe);\r
1705         }\r
1706 };
1707
1708 /*
1709  * @namespace CRS
1710  * @crs L.CRS.Earth
1711  *
1712  * Serves as the base for CRS that are global such that they cover the earth.
1713  * Can only be used as the base for other CRS and cannot be used directly,
1714  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1715  * meters.
1716  */
1717
1718 var Earth = extend({}, CRS, {
1719         wrapLng: [-180, 180],
1720
1721         // Mean Earth Radius, as recommended for use by
1722         // the International Union of Geodesy and Geophysics,
1723         // see https://rosettacode.org/wiki/Haversine_formula
1724         R: 6371000,
1725
1726         // distance between two geographical points using spherical law of cosines approximation
1727         distance: function (latlng1, latlng2) {
1728                 var rad = Math.PI / 180,
1729                     lat1 = latlng1.lat * rad,
1730                     lat2 = latlng2.lat * rad,
1731                     sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1732                     sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1733                     a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1734                     c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1735                 return this.R * c;
1736         }
1737 });
1738
1739 /*\r
1740  * @namespace Projection\r
1741  * @projection L.Projection.SphericalMercator\r
1742  *\r
1743  * Spherical Mercator projection — the most common projection for online maps,\r
1744  * used by almost all free and commercial tile providers. Assumes that Earth is\r
1745  * a sphere. Used by the `EPSG:3857` CRS.\r
1746  */\r
1747 \r
1748 var earthRadius = 6378137;\r
1749 \r
1750 var SphericalMercator = {\r
1751 \r
1752         R: earthRadius,\r
1753         MAX_LATITUDE: 85.0511287798,\r
1754 \r
1755         project: function (latlng) {\r
1756                 var d = Math.PI / 180,\r
1757                     max = this.MAX_LATITUDE,\r
1758                     lat = Math.max(Math.min(max, latlng.lat), -max),\r
1759                     sin = Math.sin(lat * d);\r
1760 \r
1761                 return new Point(\r
1762                         this.R * latlng.lng * d,\r
1763                         this.R * Math.log((1 + sin) / (1 - sin)) / 2);\r
1764         },\r
1765 \r
1766         unproject: function (point) {\r
1767                 var d = 180 / Math.PI;\r
1768 \r
1769                 return new LatLng(\r
1770                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,\r
1771                         point.x * d / this.R);\r
1772         },\r
1773 \r
1774         bounds: (function () {\r
1775                 var d = earthRadius * Math.PI;\r
1776                 return new Bounds([-d, -d], [d, d]);\r
1777         })()\r
1778 };
1779
1780 /*\r
1781  * @class Transformation\r
1782  * @aka L.Transformation\r
1783  *\r
1784  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`\r
1785  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing\r
1786  * the reverse. Used by Leaflet in its projections code.\r
1787  *\r
1788  * @example\r
1789  *\r
1790  * ```js\r
1791  * var transformation = L.transformation(2, 5, -1, 10),\r
1792  *      p = L.point(1, 2),\r
1793  *      p2 = transformation.transform(p), //  L.point(7, 8)\r
1794  *      p3 = transformation.untransform(p2); //  L.point(1, 2)\r
1795  * ```\r
1796  */\r
1797 \r
1798 \r
1799 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)\r
1800 // Creates a `Transformation` object with the given coefficients.\r
1801 function Transformation(a, b, c, d) {\r
1802         if (isArray(a)) {\r
1803                 // use array properties\r
1804                 this._a = a[0];\r
1805                 this._b = a[1];\r
1806                 this._c = a[2];\r
1807                 this._d = a[3];\r
1808                 return;\r
1809         }\r
1810         this._a = a;\r
1811         this._b = b;\r
1812         this._c = c;\r
1813         this._d = d;\r
1814 }\r
1815 \r
1816 Transformation.prototype = {\r
1817         // @method transform(point: Point, scale?: Number): Point\r
1818         // Returns a transformed point, optionally multiplied by the given scale.\r
1819         // Only accepts actual `L.Point` instances, not arrays.\r
1820         transform: function (point, scale) { // (Point, Number) -> Point\r
1821                 return this._transform(point.clone(), scale);\r
1822         },\r
1823 \r
1824         // destructive transform (faster)\r
1825         _transform: function (point, scale) {\r
1826                 scale = scale || 1;\r
1827                 point.x = scale * (this._a * point.x + this._b);\r
1828                 point.y = scale * (this._c * point.y + this._d);\r
1829                 return point;\r
1830         },\r
1831 \r
1832         // @method untransform(point: Point, scale?: Number): Point\r
1833         // Returns the reverse transformation of the given point, optionally divided\r
1834         // by the given scale. Only accepts actual `L.Point` instances, not arrays.\r
1835         untransform: function (point, scale) {\r
1836                 scale = scale || 1;\r
1837                 return new Point(\r
1838                         (point.x / scale - this._b) / this._a,\r
1839                         (point.y / scale - this._d) / this._c);\r
1840         }\r
1841 };\r
1842 \r
1843 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)\r
1844 \r
1845 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)\r
1846 // Instantiates a Transformation object with the given coefficients.\r
1847 \r
1848 // @alternative\r
1849 // @factory L.transformation(coefficients: Array): Transformation\r
1850 // Expects an coefficients array of the form\r
1851 // `[a: Number, b: Number, c: Number, d: Number]`.\r
1852 \r
1853 function toTransformation(a, b, c, d) {\r
1854         return new Transformation(a, b, c, d);\r
1855 }
1856
1857 /*\r
1858  * @namespace CRS\r
1859  * @crs L.CRS.EPSG3857\r
1860  *\r
1861  * The most common CRS for online maps, used by almost all free and commercial\r
1862  * tile providers. Uses Spherical Mercator projection. Set in by default in\r
1863  * Map's `crs` option.\r
1864  */\r
1865 \r
1866 var EPSG3857 = extend({}, Earth, {\r
1867         code: 'EPSG:3857',\r
1868         projection: SphericalMercator,\r
1869 \r
1870         transformation: (function () {\r
1871                 var scale = 0.5 / (Math.PI * SphericalMercator.R);\r
1872                 return toTransformation(scale, 0.5, -scale, 0.5);\r
1873         }())\r
1874 });\r
1875 \r
1876 var EPSG900913 = extend({}, EPSG3857, {\r
1877         code: 'EPSG:900913'\r
1878 });
1879
1880 // @namespace SVG; @section
1881 // There are several static functions which can be called without instantiating L.SVG:
1882
1883 // @function create(name: String): SVGElement
1884 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1885 // corresponding to the class name passed. For example, using 'line' will return
1886 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1887 function svgCreate(name) {
1888         return document.createElementNS('http://www.w3.org/2000/svg', name);
1889 }
1890
1891 // @function pointsToPath(rings: Point[], closed: Boolean): String
1892 // Generates a SVG path string for multiple rings, with each ring turning
1893 // into "M..L..L.." instructions
1894 function pointsToPath(rings, closed) {
1895         var str = '',
1896         i, j, len, len2, points, p;
1897
1898         for (i = 0, len = rings.length; i < len; i++) {
1899                 points = rings[i];
1900
1901                 for (j = 0, len2 = points.length; j < len2; j++) {
1902                         p = points[j];
1903                         str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1904                 }
1905
1906                 // closes the ring for polygons; "x" is VML syntax
1907                 str += closed ? (Browser.svg ? 'z' : 'x') : '';
1908         }
1909
1910         // SVG complains about empty path strings
1911         return str || 'M0 0';
1912 }
1913
1914 /*\r
1915  * @namespace Browser\r
1916  * @aka L.Browser\r
1917  *\r
1918  * A namespace with static properties for browser/feature detection used by Leaflet internally.\r
1919  *\r
1920  * @example\r
1921  *\r
1922  * ```js\r
1923  * if (L.Browser.ielt9) {\r
1924  *   alert('Upgrade your browser, dude!');\r
1925  * }\r
1926  * ```\r
1927  */\r
1928 \r
1929 var style = document.documentElement.style;\r
1930 \r
1931 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).\r
1932 var ie = 'ActiveXObject' in window;\r
1933 \r
1934 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.\r
1935 var ielt9 = ie && !document.addEventListener;\r
1936 \r
1937 // @property edge: Boolean; `true` for the Edge web browser.\r
1938 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);\r
1939 \r
1940 // @property webkit: Boolean;\r
1941 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).\r
1942 var webkit = userAgentContains('webkit');\r
1943 \r
1944 // @property android: Boolean\r
1945 // **Deprecated.** `true` for any browser running on an Android platform.\r
1946 var android = userAgentContains('android');\r
1947 \r
1948 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.\r
1949 var android23 = userAgentContains('android 2') || userAgentContains('android 3');\r
1950 \r
1951 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */\r
1952 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit\r
1953 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)\r
1954 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);\r
1955 \r
1956 // @property opera: Boolean; `true` for the Opera browser\r
1957 var opera = !!window.opera;\r
1958 \r
1959 // @property chrome: Boolean; `true` for the Chrome browser.\r
1960 var chrome = !edge && userAgentContains('chrome');\r
1961 \r
1962 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.\r
1963 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;\r
1964 \r
1965 // @property safari: Boolean; `true` for the Safari browser.\r
1966 var safari = !chrome && userAgentContains('safari');\r
1967 \r
1968 var phantom = userAgentContains('phantom');\r
1969 \r
1970 // @property opera12: Boolean\r
1971 // `true` for the Opera browser supporting CSS transforms (version 12 or later).\r
1972 var opera12 = 'OTransition' in style;\r
1973 \r
1974 // @property win: Boolean; `true` when the browser is running in a Windows platform\r
1975 var win = navigator.platform.indexOf('Win') === 0;\r
1976 \r
1977 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.\r
1978 var ie3d = ie && ('transition' in style);\r
1979 \r
1980 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.\r
1981 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;\r
1982 \r
1983 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.\r
1984 var gecko3d = 'MozPerspective' in style;\r
1985 \r
1986 // @property any3d: Boolean\r
1987 // `true` for all browsers supporting CSS transforms.\r
1988 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;\r
1989 \r
1990 // @property mobile: Boolean; `true` for all browsers running in a mobile device.\r
1991 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');\r
1992 \r
1993 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.\r
1994 var mobileWebkit = mobile && webkit;\r
1995 \r
1996 // @property mobileWebkit3d: Boolean\r
1997 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.\r
1998 var mobileWebkit3d = mobile && webkit3d;\r
1999 \r
2000 // @property msPointer: Boolean\r
2001 // `true` for browsers implementing the Microsoft touch events model (notably IE10).\r
2002 var msPointer = !window.PointerEvent && window.MSPointerEvent;\r
2003 \r
2004 // @property pointer: Boolean\r
2005 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).\r
2006 var pointer = !!(window.PointerEvent || msPointer);\r
2007 \r
2008 // @property touchNative: Boolean\r
2009 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).\r
2010 // **This does not necessarily mean** that the browser is running in a computer with\r
2011 // a touchscreen, it only means that the browser is capable of understanding\r
2012 // touch events.\r
2013 var touchNative = 'ontouchstart' in window || !!window.TouchEvent;\r
2014 \r
2015 // @property touch: Boolean\r
2016 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.\r
2017 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.\r
2018 var touch = !window.L_NO_TOUCH && (touchNative || pointer);\r
2019 \r
2020 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.\r
2021 var mobileOpera = mobile && opera;\r
2022 \r
2023 // @property mobileGecko: Boolean\r
2024 // `true` for gecko-based browsers running in a mobile device.\r
2025 var mobileGecko = mobile && gecko;\r
2026 \r
2027 // @property retina: Boolean\r
2028 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.\r
2029 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;\r
2030 \r
2031 // @property passiveEvents: Boolean\r
2032 // `true` for browsers that support passive events.\r
2033 var passiveEvents = (function () {\r
2034         var supportsPassiveOption = false;\r
2035         try {\r
2036                 var opts = Object.defineProperty({}, 'passive', {\r
2037                         get: function () { // eslint-disable-line getter-return\r
2038                                 supportsPassiveOption = true;\r
2039                         }\r
2040                 });\r
2041                 window.addEventListener('testPassiveEventSupport', falseFn, opts);\r
2042                 window.removeEventListener('testPassiveEventSupport', falseFn, opts);\r
2043         } catch (e) {\r
2044                 // Errors can safely be ignored since this is only a browser support test.\r
2045         }\r
2046         return supportsPassiveOption;\r
2047 }());\r
2048 \r
2049 // @property canvas: Boolean\r
2050 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).\r
2051 var canvas$1 = (function () {\r
2052         return !!document.createElement('canvas').getContext;\r
2053 }());\r
2054 \r
2055 // @property svg: Boolean\r
2056 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).\r
2057 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);\r
2058 \r
2059 var inlineSvg = !!svg$1 && (function () {\r
2060         var div = document.createElement('div');\r
2061         div.innerHTML = '<svg/>';\r
2062         return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';\r
2063 })();\r
2064 \r
2065 // @property vml: Boolean\r
2066 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).\r
2067 var vml = !svg$1 && (function () {\r
2068         try {\r
2069                 var div = document.createElement('div');\r
2070                 div.innerHTML = '<v:shape adj="1"/>';\r
2071 \r
2072                 var shape = div.firstChild;\r
2073                 shape.style.behavior = 'url(#default#VML)';\r
2074 \r
2075                 return shape && (typeof shape.adj === 'object');\r
2076 \r
2077         } catch (e) {\r
2078                 return false;\r
2079         }\r
2080 }());\r
2081 \r
2082 \r
2083 // @property mac: Boolean; `true` when the browser is running in a Mac platform\r
2084 var mac = navigator.platform.indexOf('Mac') === 0;\r
2085 \r
2086 // @property mac: Boolean; `true` when the browser is running in a Linux platform\r
2087 var linux = navigator.platform.indexOf('Linux') === 0;\r
2088 \r
2089 function userAgentContains(str) {\r
2090         return navigator.userAgent.toLowerCase().indexOf(str) >= 0;\r
2091 }\r
2092 \r
2093 \r
2094 var Browser = {\r
2095         ie: ie,\r
2096         ielt9: ielt9,\r
2097         edge: edge,\r
2098         webkit: webkit,\r
2099         android: android,\r
2100         android23: android23,\r
2101         androidStock: androidStock,\r
2102         opera: opera,\r
2103         chrome: chrome,\r
2104         gecko: gecko,\r
2105         safari: safari,\r
2106         phantom: phantom,\r
2107         opera12: opera12,\r
2108         win: win,\r
2109         ie3d: ie3d,\r
2110         webkit3d: webkit3d,\r
2111         gecko3d: gecko3d,\r
2112         any3d: any3d,\r
2113         mobile: mobile,\r
2114         mobileWebkit: mobileWebkit,\r
2115         mobileWebkit3d: mobileWebkit3d,\r
2116         msPointer: msPointer,\r
2117         pointer: pointer,\r
2118         touch: touch,\r
2119         touchNative: touchNative,\r
2120         mobileOpera: mobileOpera,\r
2121         mobileGecko: mobileGecko,\r
2122         retina: retina,\r
2123         passiveEvents: passiveEvents,\r
2124         canvas: canvas$1,\r
2125         svg: svg$1,\r
2126         vml: vml,\r
2127         inlineSvg: inlineSvg,\r
2128         mac: mac,\r
2129         linux: linux\r
2130 };
2131
2132 /*
2133  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2134  */
2135
2136 var POINTER_DOWN =   Browser.msPointer ? 'MSPointerDown'   : 'pointerdown';
2137 var POINTER_MOVE =   Browser.msPointer ? 'MSPointerMove'   : 'pointermove';
2138 var POINTER_UP =     Browser.msPointer ? 'MSPointerUp'     : 'pointerup';
2139 var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
2140 var pEvent = {
2141         touchstart  : POINTER_DOWN,
2142         touchmove   : POINTER_MOVE,
2143         touchend    : POINTER_UP,
2144         touchcancel : POINTER_CANCEL
2145 };
2146 var handle = {
2147         touchstart  : _onPointerStart,
2148         touchmove   : _handlePointer,
2149         touchend    : _handlePointer,
2150         touchcancel : _handlePointer
2151 };
2152 var _pointers = {};
2153 var _pointerDocListener = false;
2154
2155 // Provides a touch events wrapper for (ms)pointer events.
2156 // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2157
2158 function addPointerListener(obj, type, handler) {
2159         if (type === 'touchstart') {
2160                 _addPointerDocListener();
2161         }
2162         if (!handle[type]) {
2163                 console.warn('wrong event specified:', type);
2164                 return falseFn;
2165         }
2166         handler = handle[type].bind(this, handler);
2167         obj.addEventListener(pEvent[type], handler, false);
2168         return handler;
2169 }
2170
2171 function removePointerListener(obj, type, handler) {
2172         if (!pEvent[type]) {
2173                 console.warn('wrong event specified:', type);
2174                 return;
2175         }
2176         obj.removeEventListener(pEvent[type], handler, false);
2177 }
2178
2179 function _globalPointerDown(e) {
2180         _pointers[e.pointerId] = e;
2181 }
2182
2183 function _globalPointerMove(e) {
2184         if (_pointers[e.pointerId]) {
2185                 _pointers[e.pointerId] = e;
2186         }
2187 }
2188
2189 function _globalPointerUp(e) {
2190         delete _pointers[e.pointerId];
2191 }
2192
2193 function _addPointerDocListener() {
2194         // need to keep track of what pointers and how many are active to provide e.touches emulation
2195         if (!_pointerDocListener) {
2196                 // we listen document as any drags that end by moving the touch off the screen get fired there
2197                 document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2198                 document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2199                 document.addEventListener(POINTER_UP, _globalPointerUp, true);
2200                 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2201
2202                 _pointerDocListener = true;
2203         }
2204 }
2205
2206 function _handlePointer(handler, e) {
2207         if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }
2208
2209         e.touches = [];
2210         for (var i in _pointers) {
2211                 e.touches.push(_pointers[i]);
2212         }
2213         e.changedTouches = [e];
2214
2215         handler(e);
2216 }
2217
2218 function _onPointerStart(handler, e) {
2219         // IE10 specific: MsTouch needs preventDefault. See #2000
2220         if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
2221                 preventDefault(e);
2222         }
2223         _handlePointer(handler, e);
2224 }
2225
2226 /*\r
2227  * Extends the event handling code with double tap support for mobile browsers.\r
2228  *\r
2229  * Note: currently most browsers fire native dblclick, with only a few exceptions\r
2230  * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)\r
2231  */\r
2232 \r
2233 function makeDblclick(event) {\r
2234         // in modern browsers `type` cannot be just overridden:\r
2235         // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only\r
2236         var newEvent = {},\r
2237             prop, i;\r
2238         for (i in event) {\r
2239                 prop = event[i];\r
2240                 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;\r
2241         }\r
2242         event = newEvent;\r
2243         newEvent.type = 'dblclick';\r
2244         newEvent.detail = 2;\r
2245         newEvent.isTrusted = false;\r
2246         newEvent._simulated = true; // for debug purposes\r
2247         return newEvent;\r
2248 }\r
2249 \r
2250 var delay = 200;\r
2251 function addDoubleTapListener(obj, handler) {\r
2252         // Most browsers handle double tap natively\r
2253         obj.addEventListener('dblclick', handler);\r
2254 \r
2255         // On some platforms the browser doesn't fire native dblclicks for touch events.\r
2256         // It seems that in all such cases `detail` property of `click` event is always `1`.\r
2257         // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.\r
2258         var last = 0,\r
2259             detail;\r
2260         function simDblclick(e) {\r
2261                 if (e.detail !== 1) {\r
2262                         detail = e.detail; // keep in sync to avoid false dblclick in some cases\r
2263                         return;\r
2264                 }\r
2265 \r
2266                 if (e.pointerType === 'mouse' ||\r
2267                         (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {\r
2268 \r
2269                         return;\r
2270                 }\r
2271 \r
2272                 // When clicking on an <input>, the browser generates a click on its\r
2273                 // <label> (and vice versa) triggering two clicks in quick succession.\r
2274                 // This ignores clicks on elements which are a label with a 'for'\r
2275                 // attribute (or children of such a label), but not children of\r
2276                 // a <input>.\r
2277                 var path = getPropagationPath(e);\r
2278                 if (path.some(function (el) {\r
2279                         return el instanceof HTMLLabelElement && el.attributes.for;\r
2280                 }) &&\r
2281                         !path.some(function (el) {\r
2282                                 return (\r
2283                                         el instanceof HTMLInputElement ||\r
2284                                         el instanceof HTMLSelectElement\r
2285                                 );\r
2286                         })\r
2287                 ) {\r
2288                         return;\r
2289                 }\r
2290 \r
2291                 var now = Date.now();\r
2292                 if (now - last <= delay) {\r
2293                         detail++;\r
2294                         if (detail === 2) {\r
2295                                 handler(makeDblclick(e));\r
2296                         }\r
2297                 } else {\r
2298                         detail = 1;\r
2299                 }\r
2300                 last = now;\r
2301         }\r
2302 \r
2303         obj.addEventListener('click', simDblclick);\r
2304 \r
2305         return {\r
2306                 dblclick: handler,\r
2307                 simDblclick: simDblclick\r
2308         };\r
2309 }\r
2310 \r
2311 function removeDoubleTapListener(obj, handlers) {\r
2312         obj.removeEventListener('dblclick', handlers.dblclick);\r
2313         obj.removeEventListener('click', handlers.simDblclick);\r
2314 }
2315
2316 /*\r
2317  * @namespace DomUtil\r
2318  *\r
2319  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)\r
2320  * tree, used by Leaflet internally.\r
2321  *\r
2322  * Most functions expecting or returning a `HTMLElement` also work for\r
2323  * SVG elements. The only difference is that classes refer to CSS classes\r
2324  * in HTML and SVG classes in SVG.\r
2325  */\r
2326 \r
2327 \r
2328 // @property TRANSFORM: String\r
2329 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).\r
2330 var TRANSFORM = testProp(\r
2331         ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);\r
2332 \r
2333 // webkitTransition comes first because some browser versions that drop vendor prefix don't do\r
2334 // the same for the transitionend event, in particular the Android 4.1 stock browser\r
2335 \r
2336 // @property TRANSITION: String\r
2337 // Vendor-prefixed transition style name.\r
2338 var TRANSITION = testProp(\r
2339         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);\r
2340 \r
2341 // @property TRANSITION_END: String\r
2342 // Vendor-prefixed transitionend event name.\r
2343 var TRANSITION_END =\r
2344         TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';\r
2345 \r
2346 \r
2347 // @function get(id: String|HTMLElement): HTMLElement\r
2348 // Returns an element given its DOM id, or returns the element itself\r
2349 // if it was passed directly.\r
2350 function get(id) {\r
2351         return typeof id === 'string' ? document.getElementById(id) : id;\r
2352 }\r
2353 \r
2354 // @function getStyle(el: HTMLElement, styleAttrib: String): String\r
2355 // Returns the value for a certain style attribute on an element,\r
2356 // including computed values or values set through CSS.\r
2357 function getStyle(el, style) {\r
2358         var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);\r
2359 \r
2360         if ((!value || value === 'auto') && document.defaultView) {\r
2361                 var css = document.defaultView.getComputedStyle(el, null);\r
2362                 value = css ? css[style] : null;\r
2363         }\r
2364         return value === 'auto' ? null : value;\r
2365 }\r
2366 \r
2367 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement\r
2368 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.\r
2369 function create$1(tagName, className, container) {\r
2370         var el = document.createElement(tagName);\r
2371         el.className = className || '';\r
2372 \r
2373         if (container) {\r
2374                 container.appendChild(el);\r
2375         }\r
2376         return el;\r
2377 }\r
2378 \r
2379 // @function remove(el: HTMLElement)\r
2380 // Removes `el` from its parent element\r
2381 function remove(el) {\r
2382         var parent = el.parentNode;\r
2383         if (parent) {\r
2384                 parent.removeChild(el);\r
2385         }\r
2386 }\r
2387 \r
2388 // @function empty(el: HTMLElement)\r
2389 // Removes all of `el`'s children elements from `el`\r
2390 function empty(el) {\r
2391         while (el.firstChild) {\r
2392                 el.removeChild(el.firstChild);\r
2393         }\r
2394 }\r
2395 \r
2396 // @function toFront(el: HTMLElement)\r
2397 // Makes `el` the last child of its parent, so it renders in front of the other children.\r
2398 function toFront(el) {\r
2399         var parent = el.parentNode;\r
2400         if (parent && parent.lastChild !== el) {\r
2401                 parent.appendChild(el);\r
2402         }\r
2403 }\r
2404 \r
2405 // @function toBack(el: HTMLElement)\r
2406 // Makes `el` the first child of its parent, so it renders behind the other children.\r
2407 function toBack(el) {\r
2408         var parent = el.parentNode;\r
2409         if (parent && parent.firstChild !== el) {\r
2410                 parent.insertBefore(el, parent.firstChild);\r
2411         }\r
2412 }\r
2413 \r
2414 // @function hasClass(el: HTMLElement, name: String): Boolean\r
2415 // Returns `true` if the element's class attribute contains `name`.\r
2416 function hasClass(el, name) {\r
2417         if (el.classList !== undefined) {\r
2418                 return el.classList.contains(name);\r
2419         }\r
2420         var className = getClass(el);\r
2421         return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);\r
2422 }\r
2423 \r
2424 // @function addClass(el: HTMLElement, name: String)\r
2425 // Adds `name` to the element's class attribute.\r
2426 function addClass(el, name) {\r
2427         if (el.classList !== undefined) {\r
2428                 var classes = splitWords(name);\r
2429                 for (var i = 0, len = classes.length; i < len; i++) {\r
2430                         el.classList.add(classes[i]);\r
2431                 }\r
2432         } else if (!hasClass(el, name)) {\r
2433                 var className = getClass(el);\r
2434                 setClass(el, (className ? className + ' ' : '') + name);\r
2435         }\r
2436 }\r
2437 \r
2438 // @function removeClass(el: HTMLElement, name: String)\r
2439 // Removes `name` from the element's class attribute.\r
2440 function removeClass(el, name) {\r
2441         if (el.classList !== undefined) {\r
2442                 el.classList.remove(name);\r
2443         } else {\r
2444                 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));\r
2445         }\r
2446 }\r
2447 \r
2448 // @function setClass(el: HTMLElement, name: String)\r
2449 // Sets the element's class.\r
2450 function setClass(el, name) {\r
2451         if (el.className.baseVal === undefined) {\r
2452                 el.className = name;\r
2453         } else {\r
2454                 // in case of SVG element\r
2455                 el.className.baseVal = name;\r
2456         }\r
2457 }\r
2458 \r
2459 // @function getClass(el: HTMLElement): String\r
2460 // Returns the element's class.\r
2461 function getClass(el) {\r
2462         // Check if the element is an SVGElementInstance and use the correspondingElement instead\r
2463         // (Required for linked SVG elements in IE11.)\r
2464         if (el.correspondingElement) {\r
2465                 el = el.correspondingElement;\r
2466         }\r
2467         return el.className.baseVal === undefined ? el.className : el.className.baseVal;\r
2468 }\r
2469 \r
2470 // @function setOpacity(el: HTMLElement, opacity: Number)\r
2471 // Set the opacity of an element (including old IE support).\r
2472 // `opacity` must be a number from `0` to `1`.\r
2473 function setOpacity(el, value) {\r
2474         if ('opacity' in el.style) {\r
2475                 el.style.opacity = value;\r
2476         } else if ('filter' in el.style) {\r
2477                 _setOpacityIE(el, value);\r
2478         }\r
2479 }\r
2480 \r
2481 function _setOpacityIE(el, value) {\r
2482         var filter = false,\r
2483             filterName = 'DXImageTransform.Microsoft.Alpha';\r
2484 \r
2485         // filters collection throws an error if we try to retrieve a filter that doesn't exist\r
2486         try {\r
2487                 filter = el.filters.item(filterName);\r
2488         } catch (e) {\r
2489                 // don't set opacity to 1 if we haven't already set an opacity,\r
2490                 // it isn't needed and breaks transparent pngs.\r
2491                 if (value === 1) { return; }\r
2492         }\r
2493 \r
2494         value = Math.round(value * 100);\r
2495 \r
2496         if (filter) {\r
2497                 filter.Enabled = (value !== 100);\r
2498                 filter.Opacity = value;\r
2499         } else {\r
2500                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';\r
2501         }\r
2502 }\r
2503 \r
2504 // @function testProp(props: String[]): String|false\r
2505 // Goes through the array of style names and returns the first name\r
2506 // that is a valid style name for an element. If no such name is found,\r
2507 // it returns false. Useful for vendor-prefixed styles like `transform`.\r
2508 function testProp(props) {\r
2509         var style = document.documentElement.style;\r
2510 \r
2511         for (var i = 0; i < props.length; i++) {\r
2512                 if (props[i] in style) {\r
2513                         return props[i];\r
2514                 }\r
2515         }\r
2516         return false;\r
2517 }\r
2518 \r
2519 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)\r
2520 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels\r
2521 // and optionally scaled by `scale`. Does not have an effect if the\r
2522 // browser doesn't support 3D CSS transforms.\r
2523 function setTransform(el, offset, scale) {\r
2524         var pos = offset || new Point(0, 0);\r
2525 \r
2526         el.style[TRANSFORM] =\r
2527                 (Browser.ie3d ?\r
2528                         'translate(' + pos.x + 'px,' + pos.y + 'px)' :\r
2529                         'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +\r
2530                 (scale ? ' scale(' + scale + ')' : '');\r
2531 }\r
2532 \r
2533 // @function setPosition(el: HTMLElement, position: Point)\r
2534 // Sets the position of `el` to coordinates specified by `position`,\r
2535 // using CSS translate or top/left positioning depending on the browser\r
2536 // (used by Leaflet internally to position its layers).\r
2537 function setPosition(el, point) {\r
2538 \r
2539         /*eslint-disable */\r
2540         el._leaflet_pos = point;\r
2541         /* eslint-enable */\r
2542 \r
2543         if (Browser.any3d) {\r
2544                 setTransform(el, point);\r
2545         } else {\r
2546                 el.style.left = point.x + 'px';\r
2547                 el.style.top = point.y + 'px';\r
2548         }\r
2549 }\r
2550 \r
2551 // @function getPosition(el: HTMLElement): Point\r
2552 // Returns the coordinates of an element previously positioned with setPosition.\r
2553 function getPosition(el) {\r
2554         // this method is only used for elements previously positioned using setPosition,\r
2555         // so it's safe to cache the position for performance\r
2556 \r
2557         return el._leaflet_pos || new Point(0, 0);\r
2558 }\r
2559 \r
2560 // @function disableTextSelection()\r
2561 // Prevents the user from generating `selectstart` DOM events, usually generated\r
2562 // when the user drags the mouse through a page with text. Used internally\r
2563 // by Leaflet to override the behaviour of any click-and-drag interaction on\r
2564 // the map. Affects drag interactions on the whole document.\r
2565 \r
2566 // @function enableTextSelection()\r
2567 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).\r
2568 var disableTextSelection;\r
2569 var enableTextSelection;\r
2570 var _userSelect;\r
2571 if ('onselectstart' in document) {\r
2572         disableTextSelection = function () {\r
2573                 on(window, 'selectstart', preventDefault);\r
2574         };\r
2575         enableTextSelection = function () {\r
2576                 off(window, 'selectstart', preventDefault);\r
2577         };\r
2578 } else {\r
2579         var userSelectProperty = testProp(\r
2580                 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);\r
2581 \r
2582         disableTextSelection = function () {\r
2583                 if (userSelectProperty) {\r
2584                         var style = document.documentElement.style;\r
2585                         _userSelect = style[userSelectProperty];\r
2586                         style[userSelectProperty] = 'none';\r
2587                 }\r
2588         };\r
2589         enableTextSelection = function () {\r
2590                 if (userSelectProperty) {\r
2591                         document.documentElement.style[userSelectProperty] = _userSelect;\r
2592                         _userSelect = undefined;\r
2593                 }\r
2594         };\r
2595 }\r
2596 \r
2597 // @function disableImageDrag()\r
2598 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but\r
2599 // for `dragstart` DOM events, usually generated when the user drags an image.\r
2600 function disableImageDrag() {\r
2601         on(window, 'dragstart', preventDefault);\r
2602 }\r
2603 \r
2604 // @function enableImageDrag()\r
2605 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).\r
2606 function enableImageDrag() {\r
2607         off(window, 'dragstart', preventDefault);\r
2608 }\r
2609 \r
2610 var _outlineElement, _outlineStyle;\r
2611 // @function preventOutline(el: HTMLElement)\r
2612 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)\r
2613 // of the element `el` invisible. Used internally by Leaflet to prevent\r
2614 // focusable elements from displaying an outline when the user performs a\r
2615 // drag interaction on them.\r
2616 function preventOutline(element) {\r
2617         while (element.tabIndex === -1) {\r
2618                 element = element.parentNode;\r
2619         }\r
2620         if (!element.style) { return; }\r
2621         restoreOutline();\r
2622         _outlineElement = element;\r
2623         _outlineStyle = element.style.outlineStyle;\r
2624         element.style.outlineStyle = 'none';\r
2625         on(window, 'keydown', restoreOutline);\r
2626 }\r
2627 \r
2628 // @function restoreOutline()\r
2629 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().\r
2630 function restoreOutline() {\r
2631         if (!_outlineElement) { return; }\r
2632         _outlineElement.style.outlineStyle = _outlineStyle;\r
2633         _outlineElement = undefined;\r
2634         _outlineStyle = undefined;\r
2635         off(window, 'keydown', restoreOutline);\r
2636 }\r
2637 \r
2638 // @function getSizedParentNode(el: HTMLElement): HTMLElement\r
2639 // Finds the closest parent node which size (width and height) is not null.\r
2640 function getSizedParentNode(element) {\r
2641         do {\r
2642                 element = element.parentNode;\r
2643         } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);\r
2644         return element;\r
2645 }\r
2646 \r
2647 // @function getScale(el: HTMLElement): Object\r
2648 // Computes the CSS scale currently applied on the element.\r
2649 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,\r
2650 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).\r
2651 function getScale(element) {\r
2652         var rect = element.getBoundingClientRect(); // Read-only in old browsers.\r
2653 \r
2654         return {\r
2655                 x: rect.width / element.offsetWidth || 1,\r
2656                 y: rect.height / element.offsetHeight || 1,\r
2657                 boundingClientRect: rect\r
2658         };\r
2659 }
2660
2661 var DomUtil = {
2662   __proto__: null,
2663   TRANSFORM: TRANSFORM,
2664   TRANSITION: TRANSITION,
2665   TRANSITION_END: TRANSITION_END,
2666   get: get,
2667   getStyle: getStyle,
2668   create: create$1,
2669   remove: remove,
2670   empty: empty,
2671   toFront: toFront,
2672   toBack: toBack,
2673   hasClass: hasClass,
2674   addClass: addClass,
2675   removeClass: removeClass,
2676   setClass: setClass,
2677   getClass: getClass,
2678   setOpacity: setOpacity,
2679   testProp: testProp,
2680   setTransform: setTransform,
2681   setPosition: setPosition,
2682   getPosition: getPosition,
2683   get disableTextSelection () { return disableTextSelection; },
2684   get enableTextSelection () { return enableTextSelection; },
2685   disableImageDrag: disableImageDrag,
2686   enableImageDrag: enableImageDrag,
2687   preventOutline: preventOutline,
2688   restoreOutline: restoreOutline,
2689   getSizedParentNode: getSizedParentNode,
2690   getScale: getScale
2691 };
2692
2693 /*\r
2694  * @namespace DomEvent\r
2695  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.\r
2696  */\r
2697 \r
2698 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.\r
2699 \r
2700 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this\r
2701 // Adds a listener function (`fn`) to a particular DOM event type of the\r
2702 // element `el`. You can optionally specify the context of the listener\r
2703 // (object the `this` keyword will point to). You can also pass several\r
2704 // space-separated types (e.g. `'click dblclick'`).\r
2705 \r
2706 // @alternative\r
2707 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this\r
2708 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`\r
2709 function on(obj, types, fn, context) {\r
2710 \r
2711         if (types && typeof types === 'object') {\r
2712                 for (var type in types) {\r
2713                         addOne(obj, type, types[type], fn);\r
2714                 }\r
2715         } else {\r
2716                 types = splitWords(types);\r
2717 \r
2718                 for (var i = 0, len = types.length; i < len; i++) {\r
2719                         addOne(obj, types[i], fn, context);\r
2720                 }\r
2721         }\r
2722 \r
2723         return this;\r
2724 }\r
2725 \r
2726 var eventsKey = '_leaflet_events';\r
2727 \r
2728 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this\r
2729 // Removes a previously added listener function.\r
2730 // Note that if you passed a custom context to on, you must pass the same\r
2731 // context to `off` in order to remove the listener.\r
2732 \r
2733 // @alternative\r
2734 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this\r
2735 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`\r
2736 \r
2737 // @alternative\r
2738 // @function off(el: HTMLElement, types: String): this\r
2739 // Removes all previously added listeners of given types.\r
2740 \r
2741 // @alternative\r
2742 // @function off(el: HTMLElement): this\r
2743 // Removes all previously added listeners from given HTMLElement\r
2744 function off(obj, types, fn, context) {\r
2745 \r
2746         if (arguments.length === 1) {\r
2747                 batchRemove(obj);\r
2748                 delete obj[eventsKey];\r
2749 \r
2750         } else if (types && typeof types === 'object') {\r
2751                 for (var type in types) {\r
2752                         removeOne(obj, type, types[type], fn);\r
2753                 }\r
2754 \r
2755         } else {\r
2756                 types = splitWords(types);\r
2757 \r
2758                 if (arguments.length === 2) {\r
2759                         batchRemove(obj, function (type) {\r
2760                                 return indexOf(types, type) !== -1;\r
2761                         });\r
2762                 } else {\r
2763                         for (var i = 0, len = types.length; i < len; i++) {\r
2764                                 removeOne(obj, types[i], fn, context);\r
2765                         }\r
2766                 }\r
2767         }\r
2768 \r
2769         return this;\r
2770 }\r
2771 \r
2772 function batchRemove(obj, filterFn) {\r
2773         for (var id in obj[eventsKey]) {\r
2774                 var type = id.split(/\d/)[0];\r
2775                 if (!filterFn || filterFn(type)) {\r
2776                         removeOne(obj, type, null, null, id);\r
2777                 }\r
2778         }\r
2779 }\r
2780 \r
2781 var mouseSubst = {\r
2782         mouseenter: 'mouseover',\r
2783         mouseleave: 'mouseout',\r
2784         wheel: !('onwheel' in window) && 'mousewheel'\r
2785 };\r
2786 \r
2787 function addOne(obj, type, fn, context) {\r
2788         var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');\r
2789 \r
2790         if (obj[eventsKey] && obj[eventsKey][id]) { return this; }\r
2791 \r
2792         var handler = function (e) {\r
2793                 return fn.call(context || obj, e || window.event);\r
2794         };\r
2795 \r
2796         var originalHandler = handler;\r
2797 \r
2798         if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {\r
2799                 // Needs DomEvent.Pointer.js\r
2800                 handler = addPointerListener(obj, type, handler);\r
2801 \r
2802         } else if (Browser.touch && (type === 'dblclick')) {\r
2803                 handler = addDoubleTapListener(obj, handler);\r
2804 \r
2805         } else if ('addEventListener' in obj) {\r
2806 \r
2807                 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' ||  type === 'mousewheel') {\r
2808                         obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);\r
2809 \r
2810                 } else if (type === 'mouseenter' || type === 'mouseleave') {\r
2811                         handler = function (e) {\r
2812                                 e = e || window.event;\r
2813                                 if (isExternalTarget(obj, e)) {\r
2814                                         originalHandler(e);\r
2815                                 }\r
2816                         };\r
2817                         obj.addEventListener(mouseSubst[type], handler, false);\r
2818 \r
2819                 } else {\r
2820                         obj.addEventListener(type, originalHandler, false);\r
2821                 }\r
2822 \r
2823         } else {\r
2824                 obj.attachEvent('on' + type, handler);\r
2825         }\r
2826 \r
2827         obj[eventsKey] = obj[eventsKey] || {};\r
2828         obj[eventsKey][id] = handler;\r
2829 }\r
2830 \r
2831 function removeOne(obj, type, fn, context, id) {\r
2832         id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');\r
2833         var handler = obj[eventsKey] && obj[eventsKey][id];\r
2834 \r
2835         if (!handler) { return this; }\r
2836 \r
2837         if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {\r
2838                 removePointerListener(obj, type, handler);\r
2839 \r
2840         } else if (Browser.touch && (type === 'dblclick')) {\r
2841                 removeDoubleTapListener(obj, handler);\r
2842 \r
2843         } else if ('removeEventListener' in obj) {\r
2844 \r
2845                 obj.removeEventListener(mouseSubst[type] || type, handler, false);\r
2846 \r
2847         } else {\r
2848                 obj.detachEvent('on' + type, handler);\r
2849         }\r
2850 \r
2851         obj[eventsKey][id] = null;\r
2852 }\r
2853 \r
2854 // @function stopPropagation(ev: DOMEvent): this\r
2855 // Stop the given event from propagation to parent elements. Used inside the listener functions:\r
2856 // ```js\r
2857 // L.DomEvent.on(div, 'click', function (ev) {\r
2858 //      L.DomEvent.stopPropagation(ev);\r
2859 // });\r
2860 // ```\r
2861 function stopPropagation(e) {\r
2862 \r
2863         if (e.stopPropagation) {\r
2864                 e.stopPropagation();\r
2865         } else if (e.originalEvent) {  // In case of Leaflet event.\r
2866                 e.originalEvent._stopped = true;\r
2867         } else {\r
2868                 e.cancelBubble = true;\r
2869         }\r
2870 \r
2871         return this;\r
2872 }\r
2873 \r
2874 // @function disableScrollPropagation(el: HTMLElement): this\r
2875 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).\r
2876 function disableScrollPropagation(el) {\r
2877         addOne(el, 'wheel', stopPropagation);\r
2878         return this;\r
2879 }\r
2880 \r
2881 // @function disableClickPropagation(el: HTMLElement): this\r
2882 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,\r
2883 // `'mousedown'` and `'touchstart'` events (plus browser variants).\r
2884 function disableClickPropagation(el) {\r
2885         on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);\r
2886         el['_leaflet_disable_click'] = true;\r
2887         return this;\r
2888 }\r
2889 \r
2890 // @function preventDefault(ev: DOMEvent): this\r
2891 // Prevents the default action of the DOM Event `ev` from happening (such as\r
2892 // following a link in the href of the a element, or doing a POST request\r
2893 // with page reload when a `<form>` is submitted).\r
2894 // Use it inside listener functions.\r
2895 function preventDefault(e) {\r
2896         if (e.preventDefault) {\r
2897                 e.preventDefault();\r
2898         } else {\r
2899                 e.returnValue = false;\r
2900         }\r
2901         return this;\r
2902 }\r
2903 \r
2904 // @function stop(ev: DOMEvent): this\r
2905 // Does `stopPropagation` and `preventDefault` at the same time.\r
2906 function stop(e) {\r
2907         preventDefault(e);\r
2908         stopPropagation(e);\r
2909         return this;\r
2910 }\r
2911 \r
2912 // @function getPropagationPath(ev: DOMEvent): Array\r
2913 // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath).\r
2914 // Returns an array containing the `HTMLElement`s that the given DOM event\r
2915 // should propagate to (if not stopped).\r
2916 function getPropagationPath(ev) {\r
2917         if (ev.composedPath) {\r
2918                 return ev.composedPath();\r
2919         }\r
2920 \r
2921         var path = [];\r
2922         var el = ev.target;\r
2923 \r
2924         while (el) {\r
2925                 path.push(el);\r
2926                 el = el.parentNode;\r
2927         }\r
2928         return path;\r
2929 }\r
2930 \r
2931 \r
2932 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point\r
2933 // Gets normalized mouse position from a DOM event relative to the\r
2934 // `container` (border excluded) or to the whole page if not specified.\r
2935 function getMousePosition(e, container) {\r
2936         if (!container) {\r
2937                 return new Point(e.clientX, e.clientY);\r
2938         }\r
2939 \r
2940         var scale = getScale(container),\r
2941             offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)\r
2942 \r
2943         return new Point(\r
2944                 // offset.left/top values are in page scale (like clientX/Y),\r
2945                 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).\r
2946                 (e.clientX - offset.left) / scale.x - container.clientLeft,\r
2947                 (e.clientY - offset.top) / scale.y - container.clientTop\r
2948         );\r
2949 }\r
2950 \r
2951 \r
2952 //  except , Safari and\r
2953 // We need double the scroll pixels (see #7403 and #4538) for all Browsers\r
2954 // except OSX (Mac) -> 3x, Chrome running on Linux 1x\r
2955 \r
2956 var wheelPxFactor =\r
2957         (Browser.linux && Browser.chrome) ? window.devicePixelRatio :\r
2958         Browser.mac ? window.devicePixelRatio * 3 :\r
2959         window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1;\r
2960 // @function getWheelDelta(ev: DOMEvent): Number\r
2961 // Gets normalized wheel delta from a wheel DOM event, in vertical\r
2962 // pixels scrolled (negative if scrolling down).\r
2963 // Events from pointing devices without precise scrolling are mapped to\r
2964 // a best guess of 60 pixels.\r
2965 function getWheelDelta(e) {\r
2966         return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta\r
2967                (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels\r
2968                (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines\r
2969                (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages\r
2970                (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events\r
2971                e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels\r
2972                (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines\r
2973                e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages\r
2974                0;\r
2975 }\r
2976 \r
2977 // check if element really left/entered the event target (for mouseenter/mouseleave)\r
2978 function isExternalTarget(el, e) {\r
2979 \r
2980         var related = e.relatedTarget;\r
2981 \r
2982         if (!related) { return true; }\r
2983 \r
2984         try {\r
2985                 while (related && (related !== el)) {\r
2986                         related = related.parentNode;\r
2987                 }\r
2988         } catch (err) {\r
2989                 return false;\r
2990         }\r
2991         return (related !== el);\r
2992 }
2993
2994 var DomEvent = {
2995   __proto__: null,
2996   on: on,
2997   off: off,
2998   stopPropagation: stopPropagation,
2999   disableScrollPropagation: disableScrollPropagation,
3000   disableClickPropagation: disableClickPropagation,
3001   preventDefault: preventDefault,
3002   stop: stop,
3003   getPropagationPath: getPropagationPath,
3004   getMousePosition: getMousePosition,
3005   getWheelDelta: getWheelDelta,
3006   isExternalTarget: isExternalTarget,
3007   addListener: on,
3008   removeListener: off
3009 };
3010
3011 /*
3012  * @class PosAnimation
3013  * @aka L.PosAnimation
3014  * @inherits Evented
3015  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
3016  *
3017  * @example
3018  * ```js
3019  * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map);
3020  *
3021  * myPositionMarker.on("click", function() {
3022  *      var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng());
3023  *      pos.y -= 25;
3024  *      var fx = new L.PosAnimation();
3025  *
3026  *      fx.once('end',function() {
3027  *              pos.y += 25;
3028  *              fx.run(myPositionMarker._icon, pos, 0.8);
3029  *      });
3030  *
3031  *      fx.run(myPositionMarker._icon, pos, 0.3);
3032  * });
3033  *
3034  * ```
3035  *
3036  * @constructor L.PosAnimation()
3037  * Creates a `PosAnimation` object.
3038  *
3039  */
3040
3041 var PosAnimation = Evented.extend({
3042
3043         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
3044         // Run an animation of a given element to a new position, optionally setting
3045         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
3046         // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
3047         // `0.5` by default).
3048         run: function (el, newPos, duration, easeLinearity) {
3049                 this.stop();
3050
3051                 this._el = el;
3052                 this._inProgress = true;
3053                 this._duration = duration || 0.25;
3054                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
3055
3056                 this._startPos = getPosition(el);
3057                 this._offset = newPos.subtract(this._startPos);
3058                 this._startTime = +new Date();
3059
3060                 // @event start: Event
3061                 // Fired when the animation starts
3062                 this.fire('start');
3063
3064                 this._animate();
3065         },
3066
3067         // @method stop()
3068         // Stops the animation (if currently running).
3069         stop: function () {
3070                 if (!this._inProgress) { return; }
3071
3072                 this._step(true);
3073                 this._complete();
3074         },
3075
3076         _animate: function () {
3077                 // animation loop
3078                 this._animId = requestAnimFrame(this._animate, this);
3079                 this._step();
3080         },
3081
3082         _step: function (round) {
3083                 var elapsed = (+new Date()) - this._startTime,
3084                     duration = this._duration * 1000;
3085
3086                 if (elapsed < duration) {
3087                         this._runFrame(this._easeOut(elapsed / duration), round);
3088                 } else {
3089                         this._runFrame(1);
3090                         this._complete();
3091                 }
3092         },
3093
3094         _runFrame: function (progress, round) {
3095                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
3096                 if (round) {
3097                         pos._round();
3098                 }
3099                 setPosition(this._el, pos);
3100
3101                 // @event step: Event
3102                 // Fired continuously during the animation.
3103                 this.fire('step');
3104         },
3105
3106         _complete: function () {
3107                 cancelAnimFrame(this._animId);
3108
3109                 this._inProgress = false;
3110                 // @event end: Event
3111                 // Fired when the animation ends.
3112                 this.fire('end');
3113         },
3114
3115         _easeOut: function (t) {
3116                 return 1 - Math.pow(1 - t, this._easeOutPower);
3117         }
3118 });
3119
3120 /*\r
3121  * @class Map\r
3122  * @aka L.Map\r
3123  * @inherits Evented\r
3124  *\r
3125  * The central class of the API — it is used to create a map on a page and manipulate it.\r
3126  *\r
3127  * @example\r
3128  *\r
3129  * ```js\r
3130  * // initialize the map on the "map" div with a given center and zoom\r
3131  * var map = L.map('map', {\r
3132  *      center: [51.505, -0.09],\r
3133  *      zoom: 13\r
3134  * });\r
3135  * ```\r
3136  *\r
3137  */\r
3138 \r
3139 var Map = Evented.extend({\r
3140 \r
3141         options: {\r
3142                 // @section Map State Options\r
3143                 // @option crs: CRS = L.CRS.EPSG3857\r
3144                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not\r
3145                 // sure what it means.\r
3146                 crs: EPSG3857,\r
3147 \r
3148                 // @option center: LatLng = undefined\r
3149                 // Initial geographic center of the map\r
3150                 center: undefined,\r
3151 \r
3152                 // @option zoom: Number = undefined\r
3153                 // Initial map zoom level\r
3154                 zoom: undefined,\r
3155 \r
3156                 // @option minZoom: Number = *\r
3157                 // Minimum zoom level of the map.\r
3158                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,\r
3159                 // the lowest of their `minZoom` options will be used instead.\r
3160                 minZoom: undefined,\r
3161 \r
3162                 // @option maxZoom: Number = *\r
3163                 // Maximum zoom level of the map.\r
3164                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,\r
3165                 // the highest of their `maxZoom` options will be used instead.\r
3166                 maxZoom: undefined,\r
3167 \r
3168                 // @option layers: Layer[] = []\r
3169                 // Array of layers that will be added to the map initially\r
3170                 layers: [],\r
3171 \r
3172                 // @option maxBounds: LatLngBounds = null\r
3173                 // When this option is set, the map restricts the view to the given\r
3174                 // geographical bounds, bouncing the user back if the user tries to pan\r
3175                 // outside the view. To set the restriction dynamically, use\r
3176                 // [`setMaxBounds`](#map-setmaxbounds) method.\r
3177                 maxBounds: undefined,\r
3178 \r
3179                 // @option renderer: Renderer = *\r
3180                 // The default method for drawing vector layers on the map. `L.SVG`\r
3181                 // or `L.Canvas` by default depending on browser support.\r
3182                 renderer: undefined,\r
3183 \r
3184 \r
3185                 // @section Animation Options\r
3186                 // @option zoomAnimation: Boolean = true\r
3187                 // Whether the map zoom animation is enabled. By default it's enabled\r
3188                 // in all browsers that support CSS3 Transitions except Android.\r
3189                 zoomAnimation: true,\r
3190 \r
3191                 // @option zoomAnimationThreshold: Number = 4\r
3192                 // Won't animate zoom if the zoom difference exceeds this value.\r
3193                 zoomAnimationThreshold: 4,\r
3194 \r
3195                 // @option fadeAnimation: Boolean = true\r
3196                 // Whether the tile fade animation is enabled. By default it's enabled\r
3197                 // in all browsers that support CSS3 Transitions except Android.\r
3198                 fadeAnimation: true,\r
3199 \r
3200                 // @option markerZoomAnimation: Boolean = true\r
3201                 // Whether markers animate their zoom with the zoom animation, if disabled\r
3202                 // they will disappear for the length of the animation. By default it's\r
3203                 // enabled in all browsers that support CSS3 Transitions except Android.\r
3204                 markerZoomAnimation: true,\r
3205 \r
3206                 // @option transform3DLimit: Number = 2^23\r
3207                 // Defines the maximum size of a CSS translation transform. The default\r
3208                 // value should not be changed unless a web browser positions layers in\r
3209                 // the wrong place after doing a large `panBy`.\r
3210                 transform3DLimit: 8388608, // Precision limit of a 32-bit float\r
3211 \r
3212                 // @section Interaction Options\r
3213                 // @option zoomSnap: Number = 1\r
3214                 // Forces the map's zoom level to always be a multiple of this, particularly\r
3215                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.\r
3216                 // By default, the zoom level snaps to the nearest integer; lower values\r
3217                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`\r
3218                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.\r
3219                 zoomSnap: 1,\r
3220 \r
3221                 // @option zoomDelta: Number = 1\r
3222                 // Controls how much the map's zoom level will change after a\r
3223                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`\r
3224                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).\r
3225                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.\r
3226                 zoomDelta: 1,\r
3227 \r
3228                 // @option trackResize: Boolean = true\r
3229                 // Whether the map automatically handles browser window resize to update itself.\r
3230                 trackResize: true\r
3231         },\r
3232 \r
3233         initialize: function (id, options) { // (HTMLElement or String, Object)\r
3234                 options = setOptions(this, options);\r
3235 \r
3236                 // Make sure to assign internal flags at the beginning,\r
3237                 // to avoid inconsistent state in some edge cases.\r
3238                 this._handlers = [];\r
3239                 this._layers = {};\r
3240                 this._zoomBoundLayers = {};\r
3241                 this._sizeChanged = true;\r
3242 \r
3243                 this._initContainer(id);\r
3244                 this._initLayout();\r
3245 \r
3246                 // hack for https://github.com/Leaflet/Leaflet/issues/1980\r
3247                 this._onResize = bind(this._onResize, this);\r
3248 \r
3249                 this._initEvents();\r
3250 \r
3251                 if (options.maxBounds) {\r
3252                         this.setMaxBounds(options.maxBounds);\r
3253                 }\r
3254 \r
3255                 if (options.zoom !== undefined) {\r
3256                         this._zoom = this._limitZoom(options.zoom);\r
3257                 }\r
3258 \r
3259                 if (options.center && options.zoom !== undefined) {\r
3260                         this.setView(toLatLng(options.center), options.zoom, {reset: true});\r
3261                 }\r
3262 \r
3263                 this.callInitHooks();\r
3264 \r
3265                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera\r
3266                 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&\r
3267                                 this.options.zoomAnimation;\r
3268 \r
3269                 // zoom transitions run with the same duration for all layers, so if one of transitionend events\r
3270                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally\r
3271                 if (this._zoomAnimated) {\r
3272                         this._createAnimProxy();\r
3273                         on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);\r
3274                 }\r
3275 \r
3276                 this._addLayers(this.options.layers);\r
3277         },\r
3278 \r
3279 \r
3280         // @section Methods for modifying map state\r
3281 \r
3282         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this\r
3283         // Sets the view of the map (geographical center and zoom) with the given\r
3284         // animation options.\r
3285         setView: function (center, zoom, options) {\r
3286 \r
3287                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);\r
3288                 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);\r
3289                 options = options || {};\r
3290 \r
3291                 this._stop();\r
3292 \r
3293                 if (this._loaded && !options.reset && options !== true) {\r
3294 \r
3295                         if (options.animate !== undefined) {\r
3296                                 options.zoom = extend({animate: options.animate}, options.zoom);\r
3297                                 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);\r
3298                         }\r
3299 \r
3300                         // try animating pan or zoom\r
3301                         var moved = (this._zoom !== zoom) ?\r
3302                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :\r
3303                                 this._tryAnimatedPan(center, options.pan);\r
3304 \r
3305                         if (moved) {\r
3306                                 // prevent resize handler call, the view will refresh after animation anyway\r
3307                                 clearTimeout(this._sizeTimer);\r
3308                                 return this;\r
3309                         }\r
3310                 }\r
3311 \r
3312                 // animation didn't start, just reset the map view\r
3313                 this._resetView(center, zoom, options.pan && options.pan.noMoveStart);\r
3314 \r
3315                 return this;\r
3316         },\r
3317 \r
3318         // @method setZoom(zoom: Number, options?: Zoom/pan options): this\r
3319         // Sets the zoom of the map.\r
3320         setZoom: function (zoom, options) {\r
3321                 if (!this._loaded) {\r
3322                         this._zoom = zoom;\r
3323                         return this;\r
3324                 }\r
3325                 return this.setView(this.getCenter(), zoom, {zoom: options});\r
3326         },\r
3327 \r
3328         // @method zoomIn(delta?: Number, options?: Zoom options): this\r
3329         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).\r
3330         zoomIn: function (delta, options) {\r
3331                 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);\r
3332                 return this.setZoom(this._zoom + delta, options);\r
3333         },\r
3334 \r
3335         // @method zoomOut(delta?: Number, options?: Zoom options): this\r
3336         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).\r
3337         zoomOut: function (delta, options) {\r
3338                 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);\r
3339                 return this.setZoom(this._zoom - delta, options);\r
3340         },\r
3341 \r
3342         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this\r
3343         // Zooms the map while keeping a specified geographical point on the map\r
3344         // stationary (e.g. used internally for scroll zoom and double-click zoom).\r
3345         // @alternative\r
3346         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this\r
3347         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.\r
3348         setZoomAround: function (latlng, zoom, options) {\r
3349                 var scale = this.getZoomScale(zoom),\r
3350                     viewHalf = this.getSize().divideBy(2),\r
3351                     containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),\r
3352 \r
3353                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),\r
3354                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));\r
3355 \r
3356                 return this.setView(newCenter, zoom, {zoom: options});\r
3357         },\r
3358 \r
3359         _getBoundsCenterZoom: function (bounds, options) {\r
3360 \r
3361                 options = options || {};\r
3362                 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);\r
3363 \r
3364                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),\r
3365                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),\r
3366 \r
3367                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));\r
3368 \r
3369                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;\r
3370 \r
3371                 if (zoom === Infinity) {\r
3372                         return {\r
3373                                 center: bounds.getCenter(),\r
3374                                 zoom: zoom\r
3375                         };\r
3376                 }\r
3377 \r
3378                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),\r
3379 \r
3380                     swPoint = this.project(bounds.getSouthWest(), zoom),\r
3381                     nePoint = this.project(bounds.getNorthEast(), zoom),\r
3382                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);\r
3383 \r
3384                 return {\r
3385                         center: center,\r
3386                         zoom: zoom\r
3387                 };\r
3388         },\r
3389 \r
3390         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this\r
3391         // Sets a map view that contains the given geographical bounds with the\r
3392         // maximum zoom level possible.\r
3393         fitBounds: function (bounds, options) {\r
3394 \r
3395                 bounds = toLatLngBounds(bounds);\r
3396 \r
3397                 if (!bounds.isValid()) {\r
3398                         throw new Error('Bounds are not valid.');\r
3399                 }\r
3400 \r
3401                 var target = this._getBoundsCenterZoom(bounds, options);\r
3402                 return this.setView(target.center, target.zoom, options);\r
3403         },\r
3404 \r
3405         // @method fitWorld(options?: fitBounds options): this\r
3406         // Sets a map view that mostly contains the whole world with the maximum\r
3407         // zoom level possible.\r
3408         fitWorld: function (options) {\r
3409                 return this.fitBounds([[-90, -180], [90, 180]], options);\r
3410         },\r
3411 \r
3412         // @method panTo(latlng: LatLng, options?: Pan options): this\r
3413         // Pans the map to a given center.\r
3414         panTo: function (center, options) { // (LatLng)\r
3415                 return this.setView(center, this._zoom, {pan: options});\r
3416         },\r
3417 \r
3418         // @method panBy(offset: Point, options?: Pan options): this\r
3419         // Pans the map by a given number of pixels (animated).\r
3420         panBy: function (offset, options) {\r
3421                 offset = toPoint(offset).round();\r
3422                 options = options || {};\r
3423 \r
3424                 if (!offset.x && !offset.y) {\r
3425                         return this.fire('moveend');\r
3426                 }\r
3427                 // If we pan too far, Chrome gets issues with tiles\r
3428                 // and makes them disappear or appear in the wrong place (slightly offset) #2602\r
3429                 if (options.animate !== true && !this.getSize().contains(offset)) {\r
3430                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());\r
3431                         return this;\r
3432                 }\r
3433 \r
3434                 if (!this._panAnim) {\r
3435                         this._panAnim = new PosAnimation();\r
3436 \r
3437                         this._panAnim.on({\r
3438                                 'step': this._onPanTransitionStep,\r
3439                                 'end': this._onPanTransitionEnd\r
3440                         }, this);\r
3441                 }\r
3442 \r
3443                 // don't fire movestart if animating inertia\r
3444                 if (!options.noMoveStart) {\r
3445                         this.fire('movestart');\r
3446                 }\r
3447 \r
3448                 // animate pan unless animate: false specified\r
3449                 if (options.animate !== false) {\r
3450                         addClass(this._mapPane, 'leaflet-pan-anim');\r
3451 \r
3452                         var newPos = this._getMapPanePos().subtract(offset).round();\r
3453                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);\r
3454                 } else {\r
3455                         this._rawPanBy(offset);\r
3456                         this.fire('move').fire('moveend');\r
3457                 }\r
3458 \r
3459                 return this;\r
3460         },\r
3461 \r
3462         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this\r
3463         // Sets the view of the map (geographical center and zoom) performing a smooth\r
3464         // pan-zoom animation.\r
3465         flyTo: function (targetCenter, targetZoom, options) {\r
3466 \r
3467                 options = options || {};\r
3468                 if (options.animate === false || !Browser.any3d) {\r
3469                         return this.setView(targetCenter, targetZoom, options);\r
3470                 }\r
3471 \r
3472                 this._stop();\r
3473 \r
3474                 var from = this.project(this.getCenter()),\r
3475                     to = this.project(targetCenter),\r
3476                     size = this.getSize(),\r
3477                     startZoom = this._zoom;\r
3478 \r
3479                 targetCenter = toLatLng(targetCenter);\r
3480                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;\r
3481 \r
3482                 var w0 = Math.max(size.x, size.y),\r
3483                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),\r
3484                     u1 = (to.distanceTo(from)) || 1,\r
3485                     rho = 1.42,\r
3486                     rho2 = rho * rho;\r
3487 \r
3488                 function r(i) {\r
3489                         var s1 = i ? -1 : 1,\r
3490                             s2 = i ? w1 : w0,\r
3491                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,\r
3492                             b1 = 2 * s2 * rho2 * u1,\r
3493                             b = t1 / b1,\r
3494                             sq = Math.sqrt(b * b + 1) - b;\r
3495 \r
3496                             // workaround for floating point precision bug when sq = 0, log = -Infinite,\r
3497                             // thus triggering an infinite loop in flyTo\r
3498                             var log = sq < 0.000000001 ? -18 : Math.log(sq);\r
3499 \r
3500                         return log;\r
3501                 }\r
3502 \r
3503                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }\r
3504                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }\r
3505                 function tanh(n) { return sinh(n) / cosh(n); }\r
3506 \r
3507                 var r0 = r(0);\r
3508 \r
3509                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }\r
3510                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }\r
3511 \r
3512                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }\r
3513 \r
3514                 var start = Date.now(),\r
3515                     S = (r(1) - r0) / rho,\r
3516                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;\r
3517 \r
3518                 function frame() {\r
3519                         var t = (Date.now() - start) / duration,\r
3520                             s = easeOut(t) * S;\r
3521 \r
3522                         if (t <= 1) {\r
3523                                 this._flyToFrame = requestAnimFrame(frame, this);\r
3524 \r
3525                                 this._move(\r
3526                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),\r
3527                                         this.getScaleZoom(w0 / w(s), startZoom),\r
3528                                         {flyTo: true});\r
3529 \r
3530                         } else {\r
3531                                 this\r
3532                                         ._move(targetCenter, targetZoom)\r
3533                                         ._moveEnd(true);\r
3534                         }\r
3535                 }\r
3536 \r
3537                 this._moveStart(true, options.noMoveStart);\r
3538 \r
3539                 frame.call(this);\r
3540                 return this;\r
3541         },\r
3542 \r
3543         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this\r
3544         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),\r
3545         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).\r
3546         flyToBounds: function (bounds, options) {\r
3547                 var target = this._getBoundsCenterZoom(bounds, options);\r
3548                 return this.flyTo(target.center, target.zoom, options);\r
3549         },\r
3550 \r
3551         // @method setMaxBounds(bounds: LatLngBounds): this\r
3552         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).\r
3553         setMaxBounds: function (bounds) {\r
3554                 bounds = toLatLngBounds(bounds);\r
3555 \r
3556                 if (this.listens('moveend', this._panInsideMaxBounds)) {\r
3557                         this.off('moveend', this._panInsideMaxBounds);\r
3558                 }\r
3559 \r
3560                 if (!bounds.isValid()) {\r
3561                         this.options.maxBounds = null;\r
3562                         return this;\r
3563                 }\r
3564 \r
3565                 this.options.maxBounds = bounds;\r
3566 \r
3567                 if (this._loaded) {\r
3568                         this._panInsideMaxBounds();\r
3569                 }\r
3570 \r
3571                 return this.on('moveend', this._panInsideMaxBounds);\r
3572         },\r
3573 \r
3574         // @method setMinZoom(zoom: Number): this\r
3575         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).\r
3576         setMinZoom: function (zoom) {\r
3577                 var oldZoom = this.options.minZoom;\r
3578                 this.options.minZoom = zoom;\r
3579 \r
3580                 if (this._loaded && oldZoom !== zoom) {\r
3581                         this.fire('zoomlevelschange');\r
3582 \r
3583                         if (this.getZoom() < this.options.minZoom) {\r
3584                                 return this.setZoom(zoom);\r
3585                         }\r
3586                 }\r
3587 \r
3588                 return this;\r
3589         },\r
3590 \r
3591         // @method setMaxZoom(zoom: Number): this\r
3592         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).\r
3593         setMaxZoom: function (zoom) {\r
3594                 var oldZoom = this.options.maxZoom;\r
3595                 this.options.maxZoom = zoom;\r
3596 \r
3597                 if (this._loaded && oldZoom !== zoom) {\r
3598                         this.fire('zoomlevelschange');\r
3599 \r
3600                         if (this.getZoom() > this.options.maxZoom) {\r
3601                                 return this.setZoom(zoom);\r
3602                         }\r
3603                 }\r
3604 \r
3605                 return this;\r
3606         },\r
3607 \r
3608         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this\r
3609         // 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
3610         panInsideBounds: function (bounds, options) {\r
3611                 this._enforcingBounds = true;\r
3612                 var center = this.getCenter(),\r
3613                     newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));\r
3614 \r
3615                 if (!center.equals(newCenter)) {\r
3616                         this.panTo(newCenter, options);\r
3617                 }\r
3618 \r
3619                 this._enforcingBounds = false;\r
3620                 return this;\r
3621         },\r
3622 \r
3623         // @method panInside(latlng: LatLng, options?: padding options): this\r
3624         // Pans the map the minimum amount to make the `latlng` visible. Use\r
3625         // padding options to fit the display to more restricted bounds.\r
3626         // If `latlng` is already within the (optionally padded) display bounds,\r
3627         // the map will not be panned.\r
3628         panInside: function (latlng, options) {\r
3629                 options = options || {};\r
3630 \r
3631                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),\r
3632                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),\r
3633                     pixelCenter = this.project(this.getCenter()),\r
3634                     pixelPoint = this.project(latlng),\r
3635                     pixelBounds = this.getPixelBounds(),\r
3636                     paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),\r
3637                     paddedSize = paddedBounds.getSize();\r
3638 \r
3639                 if (!paddedBounds.contains(pixelPoint)) {\r
3640                         this._enforcingBounds = true;\r
3641                         var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());\r
3642                         var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);\r
3643                         pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;\r
3644                         pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;\r
3645                         this.panTo(this.unproject(pixelCenter), options);\r
3646                         this._enforcingBounds = false;\r
3647                 }\r
3648                 return this;\r
3649         },\r
3650 \r
3651         // @method invalidateSize(options: Zoom/pan options): this\r
3652         // Checks if the map container size changed and updates the map if so —\r
3653         // call it after you've changed the map size dynamically, also animating\r
3654         // pan by default. If `options.pan` is `false`, panning will not occur.\r
3655         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so\r
3656         // that it doesn't happen often even if the method is called many\r
3657         // times in a row.\r
3658 \r
3659         // @alternative\r
3660         // @method invalidateSize(animate: Boolean): this\r
3661         // Checks if the map container size changed and updates the map if so —\r
3662         // call it after you've changed the map size dynamically, also animating\r
3663         // pan by default.\r
3664         invalidateSize: function (options) {\r
3665                 if (!this._loaded) { return this; }\r
3666 \r
3667                 options = extend({\r
3668                         animate: false,\r
3669                         pan: true\r
3670                 }, options === true ? {animate: true} : options);\r
3671 \r
3672                 var oldSize = this.getSize();\r
3673                 this._sizeChanged = true;\r
3674                 this._lastCenter = null;\r
3675 \r
3676                 var newSize = this.getSize(),\r
3677                     oldCenter = oldSize.divideBy(2).round(),\r
3678                     newCenter = newSize.divideBy(2).round(),\r
3679                     offset = oldCenter.subtract(newCenter);\r
3680 \r
3681                 if (!offset.x && !offset.y) { return this; }\r
3682 \r
3683                 if (options.animate && options.pan) {\r
3684                         this.panBy(offset);\r
3685 \r
3686                 } else {\r
3687                         if (options.pan) {\r
3688                                 this._rawPanBy(offset);\r
3689                         }\r
3690 \r
3691                         this.fire('move');\r
3692 \r
3693                         if (options.debounceMoveend) {\r
3694                                 clearTimeout(this._sizeTimer);\r
3695                                 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);\r
3696                         } else {\r
3697                                 this.fire('moveend');\r
3698                         }\r
3699                 }\r
3700 \r
3701                 // @section Map state change events\r
3702                 // @event resize: ResizeEvent\r
3703                 // Fired when the map is resized.\r
3704                 return this.fire('resize', {\r
3705                         oldSize: oldSize,\r
3706                         newSize: newSize\r
3707                 });\r
3708         },\r
3709 \r
3710         // @section Methods for modifying map state\r
3711         // @method stop(): this\r
3712         // Stops the currently running `panTo` or `flyTo` animation, if any.\r
3713         stop: function () {\r
3714                 this.setZoom(this._limitZoom(this._zoom));\r
3715                 if (!this.options.zoomSnap) {\r
3716                         this.fire('viewreset');\r
3717                 }\r
3718                 return this._stop();\r
3719         },\r
3720 \r
3721         // @section Geolocation methods\r
3722         // @method locate(options?: Locate options): this\r
3723         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)\r
3724         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,\r
3725         // and optionally sets the map view to the user's location with respect to\r
3726         // detection accuracy (or to the world view if geolocation failed).\r
3727         // Note that, if your page doesn't use HTTPS, this method will fail in\r
3728         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))\r
3729         // See `Locate options` for more details.\r
3730         locate: function (options) {\r
3731 \r
3732                 options = this._locateOptions = extend({\r
3733                         timeout: 10000,\r
3734                         watch: false\r
3735                         // setView: false\r
3736                         // maxZoom: <Number>\r
3737                         // maximumAge: 0\r
3738                         // enableHighAccuracy: false\r
3739                 }, options);\r
3740 \r
3741                 if (!('geolocation' in navigator)) {\r
3742                         this._handleGeolocationError({\r
3743                                 code: 0,\r
3744                                 message: 'Geolocation not supported.'\r
3745                         });\r
3746                         return this;\r
3747                 }\r
3748 \r
3749                 var onResponse = bind(this._handleGeolocationResponse, this),\r
3750                     onError = bind(this._handleGeolocationError, this);\r
3751 \r
3752                 if (options.watch) {\r
3753                         this._locationWatchId =\r
3754                                 navigator.geolocation.watchPosition(onResponse, onError, options);\r
3755                 } else {\r
3756                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);\r
3757                 }\r
3758                 return this;\r
3759         },\r
3760 \r
3761         // @method stopLocate(): this\r
3762         // Stops watching location previously initiated by `map.locate({watch: true})`\r
3763         // and aborts resetting the map view if map.locate was called with\r
3764         // `{setView: true}`.\r
3765         stopLocate: function () {\r
3766                 if (navigator.geolocation && navigator.geolocation.clearWatch) {\r
3767                         navigator.geolocation.clearWatch(this._locationWatchId);\r
3768                 }\r
3769                 if (this._locateOptions) {\r
3770                         this._locateOptions.setView = false;\r
3771                 }\r
3772                 return this;\r
3773         },\r
3774 \r
3775         _handleGeolocationError: function (error) {\r
3776                 if (!this._container._leaflet_id) { return; }\r
3777 \r
3778                 var c = error.code,\r
3779                     message = error.message ||\r
3780                             (c === 1 ? 'permission denied' :\r
3781                             (c === 2 ? 'position unavailable' : 'timeout'));\r
3782 \r
3783                 if (this._locateOptions.setView && !this._loaded) {\r
3784                         this.fitWorld();\r
3785                 }\r
3786 \r
3787                 // @section Location events\r
3788                 // @event locationerror: ErrorEvent\r
3789                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.\r
3790                 this.fire('locationerror', {\r
3791                         code: c,\r
3792                         message: 'Geolocation error: ' + message + '.'\r
3793                 });\r
3794         },\r
3795 \r
3796         _handleGeolocationResponse: function (pos) {\r
3797                 if (!this._container._leaflet_id) { return; }\r
3798 \r
3799                 var lat = pos.coords.latitude,\r
3800                     lng = pos.coords.longitude,\r
3801                     latlng = new LatLng(lat, lng),\r
3802                     bounds = latlng.toBounds(pos.coords.accuracy * 2),\r
3803                     options = this._locateOptions;\r
3804 \r
3805                 if (options.setView) {\r
3806                         var zoom = this.getBoundsZoom(bounds);\r
3807                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);\r
3808                 }\r
3809 \r
3810                 var data = {\r
3811                         latlng: latlng,\r
3812                         bounds: bounds,\r
3813                         timestamp: pos.timestamp\r
3814                 };\r
3815 \r
3816                 for (var i in pos.coords) {\r
3817                         if (typeof pos.coords[i] === 'number') {\r
3818                                 data[i] = pos.coords[i];\r
3819                         }\r
3820                 }\r
3821 \r
3822                 // @event locationfound: LocationEvent\r
3823                 // Fired when geolocation (using the [`locate`](#map-locate) method)\r
3824                 // went successfully.\r
3825                 this.fire('locationfound', data);\r
3826         },\r
3827 \r
3828         // TODO Appropriate docs section?\r
3829         // @section Other Methods\r
3830         // @method addHandler(name: String, HandlerClass: Function): this\r
3831         // Adds a new `Handler` to the map, given its name and constructor function.\r
3832         addHandler: function (name, HandlerClass) {\r
3833                 if (!HandlerClass) { return this; }\r
3834 \r
3835                 var handler = this[name] = new HandlerClass(this);\r
3836 \r
3837                 this._handlers.push(handler);\r
3838 \r
3839                 if (this.options[name]) {\r
3840                         handler.enable();\r
3841                 }\r
3842 \r
3843                 return this;\r
3844         },\r
3845 \r
3846         // @method remove(): this\r
3847         // Destroys the map and clears all related event listeners.\r
3848         remove: function () {\r
3849 \r
3850                 this._initEvents(true);\r
3851                 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }\r
3852 \r
3853                 if (this._containerId !== this._container._leaflet_id) {\r
3854                         throw new Error('Map container is being reused by another instance');\r
3855                 }\r
3856 \r
3857                 try {\r
3858                         // throws error in IE6-8\r
3859                         delete this._container._leaflet_id;\r
3860                         delete this._containerId;\r
3861                 } catch (e) {\r
3862                         /*eslint-disable */\r
3863                         this._container._leaflet_id = undefined;\r
3864                         /* eslint-enable */\r
3865                         this._containerId = undefined;\r
3866                 }\r
3867 \r
3868                 if (this._locationWatchId !== undefined) {\r
3869                         this.stopLocate();\r
3870                 }\r
3871 \r
3872                 this._stop();\r
3873 \r
3874                 remove(this._mapPane);\r
3875 \r
3876                 if (this._clearControlPos) {\r
3877                         this._clearControlPos();\r
3878                 }\r
3879                 if (this._resizeRequest) {\r
3880                         cancelAnimFrame(this._resizeRequest);\r
3881                         this._resizeRequest = null;\r
3882                 }\r
3883 \r
3884                 this._clearHandlers();\r
3885 \r
3886                 if (this._loaded) {\r
3887                         // @section Map state change events\r
3888                         // @event unload: Event\r
3889                         // Fired when the map is destroyed with [remove](#map-remove) method.\r
3890                         this.fire('unload');\r
3891                 }\r
3892 \r
3893                 var i;\r
3894                 for (i in this._layers) {\r
3895                         this._layers[i].remove();\r
3896                 }\r
3897                 for (i in this._panes) {\r
3898                         remove(this._panes[i]);\r
3899                 }\r
3900 \r
3901                 this._layers = [];\r
3902                 this._panes = [];\r
3903                 delete this._mapPane;\r
3904                 delete this._renderer;\r
3905 \r
3906                 return this;\r
3907         },\r
3908 \r
3909         // @section Other Methods\r
3910         // @method createPane(name: String, container?: HTMLElement): HTMLElement\r
3911         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,\r
3912         // then returns it. The pane is created as a child of `container`, or\r
3913         // as a child of the main map pane if not set.\r
3914         createPane: function (name, container) {\r
3915                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),\r
3916                     pane = create$1('div', className, container || this._mapPane);\r
3917 \r
3918                 if (name) {\r
3919                         this._panes[name] = pane;\r
3920                 }\r
3921                 return pane;\r
3922         },\r
3923 \r
3924         // @section Methods for Getting Map State\r
3925 \r
3926         // @method getCenter(): LatLng\r
3927         // Returns the geographical center of the map view\r
3928         getCenter: function () {\r
3929                 this._checkIfLoaded();\r
3930 \r
3931                 if (this._lastCenter && !this._moved()) {\r
3932                         return this._lastCenter.clone();\r
3933                 }\r
3934                 return this.layerPointToLatLng(this._getCenterLayerPoint());\r
3935         },\r
3936 \r
3937         // @method getZoom(): Number\r
3938         // Returns the current zoom level of the map view\r
3939         getZoom: function () {\r
3940                 return this._zoom;\r
3941         },\r
3942 \r
3943         // @method getBounds(): LatLngBounds\r
3944         // Returns the geographical bounds visible in the current map view\r
3945         getBounds: function () {\r
3946                 var bounds = this.getPixelBounds(),\r
3947                     sw = this.unproject(bounds.getBottomLeft()),\r
3948                     ne = this.unproject(bounds.getTopRight());\r
3949 \r
3950                 return new LatLngBounds(sw, ne);\r
3951         },\r
3952 \r
3953         // @method getMinZoom(): Number\r
3954         // 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
3955         getMinZoom: function () {\r
3956                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;\r
3957         },\r
3958 \r
3959         // @method getMaxZoom(): Number\r
3960         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).\r
3961         getMaxZoom: function () {\r
3962                 return this.options.maxZoom === undefined ?\r
3963                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :\r
3964                         this.options.maxZoom;\r
3965         },\r
3966 \r
3967         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number\r
3968         // Returns the maximum zoom level on which the given bounds fit to the map\r
3969         // view in its entirety. If `inside` (optional) is set to `true`, the method\r
3970         // instead returns the minimum zoom level on which the map view fits into\r
3971         // the given bounds in its entirety.\r
3972         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number\r
3973                 bounds = toLatLngBounds(bounds);\r
3974                 padding = toPoint(padding || [0, 0]);\r
3975 \r
3976                 var zoom = this.getZoom() || 0,\r
3977                     min = this.getMinZoom(),\r
3978                     max = this.getMaxZoom(),\r
3979                     nw = bounds.getNorthWest(),\r
3980                     se = bounds.getSouthEast(),\r
3981                     size = this.getSize().subtract(padding),\r
3982                     boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),\r
3983                     snap = Browser.any3d ? this.options.zoomSnap : 1,\r
3984                     scalex = size.x / boundsSize.x,\r
3985                     scaley = size.y / boundsSize.y,\r
3986                     scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);\r
3987 \r
3988                 zoom = this.getScaleZoom(scale, zoom);\r
3989 \r
3990                 if (snap) {\r
3991                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level\r
3992                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;\r
3993                 }\r
3994 \r
3995                 return Math.max(min, Math.min(max, zoom));\r
3996         },\r
3997 \r
3998         // @method getSize(): Point\r
3999         // Returns the current size of the map container (in pixels).\r
4000         getSize: function () {\r
4001                 if (!this._size || this._sizeChanged) {\r
4002                         this._size = new Point(\r
4003                                 this._container.clientWidth || 0,\r
4004                                 this._container.clientHeight || 0);\r
4005 \r
4006                         this._sizeChanged = false;\r
4007                 }\r
4008                 return this._size.clone();\r
4009         },\r
4010 \r
4011         // @method getPixelBounds(): Bounds\r
4012         // Returns the bounds of the current map view in projected pixel\r
4013         // coordinates (sometimes useful in layer and overlay implementations).\r
4014         getPixelBounds: function (center, zoom) {\r
4015                 var topLeftPoint = this._getTopLeftPoint(center, zoom);\r
4016                 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));\r
4017         },\r
4018 \r
4019         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to\r
4020         // the map pane? "left point of the map layer" can be confusing, specially\r
4021         // since there can be negative offsets.\r
4022         // @method getPixelOrigin(): Point\r
4023         // Returns the projected pixel coordinates of the top left point of\r
4024         // the map layer (useful in custom layer and overlay implementations).\r
4025         getPixelOrigin: function () {\r
4026                 this._checkIfLoaded();\r
4027                 return this._pixelOrigin;\r
4028         },\r
4029 \r
4030         // @method getPixelWorldBounds(zoom?: Number): Bounds\r
4031         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.\r
4032         // If `zoom` is omitted, the map's current zoom level is used.\r
4033         getPixelWorldBounds: function (zoom) {\r
4034                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);\r
4035         },\r
4036 \r
4037         // @section Other Methods\r
4038 \r
4039         // @method getPane(pane: String|HTMLElement): HTMLElement\r
4040         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).\r
4041         getPane: function (pane) {\r
4042                 return typeof pane === 'string' ? this._panes[pane] : pane;\r
4043         },\r
4044 \r
4045         // @method getPanes(): Object\r
4046         // Returns a plain object containing the names of all [panes](#map-pane) as keys and\r
4047         // the panes as values.\r
4048         getPanes: function () {\r
4049                 return this._panes;\r
4050         },\r
4051 \r
4052         // @method getContainer: HTMLElement\r
4053         // Returns the HTML element that contains the map.\r
4054         getContainer: function () {\r
4055                 return this._container;\r
4056         },\r
4057 \r
4058 \r
4059         // @section Conversion Methods\r
4060 \r
4061         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number\r
4062         // Returns the scale factor to be applied to a map transition from zoom level\r
4063         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.\r
4064         getZoomScale: function (toZoom, fromZoom) {\r
4065                 // TODO replace with universal implementation after refactoring projections\r
4066                 var crs = this.options.crs;\r
4067                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;\r
4068                 return crs.scale(toZoom) / crs.scale(fromZoom);\r
4069         },\r
4070 \r
4071         // @method getScaleZoom(scale: Number, fromZoom: Number): Number\r
4072         // Returns the zoom level that the map would end up at, if it is at `fromZoom`\r
4073         // level and everything is scaled by a factor of `scale`. Inverse of\r
4074         // [`getZoomScale`](#map-getZoomScale).\r
4075         getScaleZoom: function (scale, fromZoom) {\r
4076                 var crs = this.options.crs;\r
4077                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;\r
4078                 var zoom = crs.zoom(scale * crs.scale(fromZoom));\r
4079                 return isNaN(zoom) ? Infinity : zoom;\r
4080         },\r
4081 \r
4082         // @method project(latlng: LatLng, zoom: Number): Point\r
4083         // Projects a geographical coordinate `LatLng` according to the projection\r
4084         // of the map's CRS, then scales it according to `zoom` and the CRS's\r
4085         // `Transformation`. The result is pixel coordinate relative to\r
4086         // the CRS origin.\r
4087         project: function (latlng, zoom) {\r
4088                 zoom = zoom === undefined ? this._zoom : zoom;\r
4089                 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);\r
4090         },\r
4091 \r
4092         // @method unproject(point: Point, zoom: Number): LatLng\r
4093         // Inverse of [`project`](#map-project).\r
4094         unproject: function (point, zoom) {\r
4095                 zoom = zoom === undefined ? this._zoom : zoom;\r
4096                 return this.options.crs.pointToLatLng(toPoint(point), zoom);\r
4097         },\r
4098 \r
4099         // @method layerPointToLatLng(point: Point): LatLng\r
4100         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),\r
4101         // returns the corresponding geographical coordinate (for the current zoom level).\r
4102         layerPointToLatLng: function (point) {\r
4103                 var projectedPoint = toPoint(point).add(this.getPixelOrigin());\r
4104                 return this.unproject(projectedPoint);\r
4105         },\r
4106 \r
4107         // @method latLngToLayerPoint(latlng: LatLng): Point\r
4108         // Given a geographical coordinate, returns the corresponding pixel coordinate\r
4109         // relative to the [origin pixel](#map-getpixelorigin).\r
4110         latLngToLayerPoint: function (latlng) {\r
4111                 var projectedPoint = this.project(toLatLng(latlng))._round();\r
4112                 return projectedPoint._subtract(this.getPixelOrigin());\r
4113         },\r
4114 \r
4115         // @method wrapLatLng(latlng: LatLng): LatLng\r
4116         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the\r
4117         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the\r
4118         // CRS's bounds.\r
4119         // By default this means longitude is wrapped around the dateline so its\r
4120         // value is between -180 and +180 degrees.\r
4121         wrapLatLng: function (latlng) {\r
4122                 return this.options.crs.wrapLatLng(toLatLng(latlng));\r
4123         },\r
4124 \r
4125         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds\r
4126         // Returns a `LatLngBounds` with the same size as the given one, ensuring that\r
4127         // its center is within the CRS's bounds.\r
4128         // By default this means the center longitude is wrapped around the dateline so its\r
4129         // value is between -180 and +180 degrees, and the majority of the bounds\r
4130         // overlaps the CRS's bounds.\r
4131         wrapLatLngBounds: function (latlng) {\r
4132                 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));\r
4133         },\r
4134 \r
4135         // @method distance(latlng1: LatLng, latlng2: LatLng): Number\r
4136         // Returns the distance between two geographical coordinates according to\r
4137         // the map's CRS. By default this measures distance in meters.\r
4138         distance: function (latlng1, latlng2) {\r
4139                 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));\r
4140         },\r
4141 \r
4142         // @method containerPointToLayerPoint(point: Point): Point\r
4143         // Given a pixel coordinate relative to the map container, returns the corresponding\r
4144         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).\r
4145         containerPointToLayerPoint: function (point) { // (Point)\r
4146                 return toPoint(point).subtract(this._getMapPanePos());\r
4147         },\r
4148 \r
4149         // @method layerPointToContainerPoint(point: Point): Point\r
4150         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),\r
4151         // returns the corresponding pixel coordinate relative to the map container.\r
4152         layerPointToContainerPoint: function (point) { // (Point)\r
4153                 return toPoint(point).add(this._getMapPanePos());\r
4154         },\r
4155 \r
4156         // @method containerPointToLatLng(point: Point): LatLng\r
4157         // Given a pixel coordinate relative to the map container, returns\r
4158         // the corresponding geographical coordinate (for the current zoom level).\r
4159         containerPointToLatLng: function (point) {\r
4160                 var layerPoint = this.containerPointToLayerPoint(toPoint(point));\r
4161                 return this.layerPointToLatLng(layerPoint);\r
4162         },\r
4163 \r
4164         // @method latLngToContainerPoint(latlng: LatLng): Point\r
4165         // Given a geographical coordinate, returns the corresponding pixel coordinate\r
4166         // relative to the map container.\r
4167         latLngToContainerPoint: function (latlng) {\r
4168                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));\r
4169         },\r
4170 \r
4171         // @method mouseEventToContainerPoint(ev: MouseEvent): Point\r
4172         // Given a MouseEvent object, returns the pixel coordinate relative to the\r
4173         // map container where the event took place.\r
4174         mouseEventToContainerPoint: function (e) {\r
4175                 return getMousePosition(e, this._container);\r
4176         },\r
4177 \r
4178         // @method mouseEventToLayerPoint(ev: MouseEvent): Point\r
4179         // Given a MouseEvent object, returns the pixel coordinate relative to\r
4180         // the [origin pixel](#map-getpixelorigin) where the event took place.\r
4181         mouseEventToLayerPoint: function (e) {\r
4182                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));\r
4183         },\r
4184 \r
4185         // @method mouseEventToLatLng(ev: MouseEvent): LatLng\r
4186         // Given a MouseEvent object, returns geographical coordinate where the\r
4187         // event took place.\r
4188         mouseEventToLatLng: function (e) { // (MouseEvent)\r
4189                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));\r
4190         },\r
4191 \r
4192 \r
4193         // map initialization methods\r
4194 \r
4195         _initContainer: function (id) {\r
4196                 var container = this._container = get(id);\r
4197 \r
4198                 if (!container) {\r
4199                         throw new Error('Map container not found.');\r
4200                 } else if (container._leaflet_id) {\r
4201                         throw new Error('Map container is already initialized.');\r
4202                 }\r
4203 \r
4204                 on(container, 'scroll', this._onScroll, this);\r
4205                 this._containerId = stamp(container);\r
4206         },\r
4207 \r
4208         _initLayout: function () {\r
4209                 var container = this._container;\r
4210 \r
4211                 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;\r
4212 \r
4213                 addClass(container, 'leaflet-container' +\r
4214                         (Browser.touch ? ' leaflet-touch' : '') +\r
4215                         (Browser.retina ? ' leaflet-retina' : '') +\r
4216                         (Browser.ielt9 ? ' leaflet-oldie' : '') +\r
4217                         (Browser.safari ? ' leaflet-safari' : '') +\r
4218                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));\r
4219 \r
4220                 var position = getStyle(container, 'position');\r
4221 \r
4222                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {\r
4223                         container.style.position = 'relative';\r
4224                 }\r
4225 \r
4226                 this._initPanes();\r
4227 \r
4228                 if (this._initControlPos) {\r
4229                         this._initControlPos();\r
4230                 }\r
4231         },\r
4232 \r
4233         _initPanes: function () {\r
4234                 var panes = this._panes = {};\r
4235                 this._paneRenderers = {};\r
4236 \r
4237                 // @section\r
4238                 //\r
4239                 // Panes are DOM elements used to control the ordering of layers on the map. You\r
4240                 // can access panes with [`map.getPane`](#map-getpane) or\r
4241                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the\r
4242                 // [`map.createPane`](#map-createpane) method.\r
4243                 //\r
4244                 // Every map has the following default panes that differ only in zIndex.\r
4245                 //\r
4246                 // @pane mapPane: HTMLElement = 'auto'\r
4247                 // Pane that contains all other map panes\r
4248 \r
4249                 this._mapPane = this.createPane('mapPane', this._container);\r
4250                 setPosition(this._mapPane, new Point(0, 0));\r
4251 \r
4252                 // @pane tilePane: HTMLElement = 200\r
4253                 // Pane for `GridLayer`s and `TileLayer`s\r
4254                 this.createPane('tilePane');\r
4255                 // @pane overlayPane: HTMLElement = 400\r
4256                 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s\r
4257                 this.createPane('overlayPane');\r
4258                 // @pane shadowPane: HTMLElement = 500\r
4259                 // Pane for overlay shadows (e.g. `Marker` shadows)\r
4260                 this.createPane('shadowPane');\r
4261                 // @pane markerPane: HTMLElement = 600\r
4262                 // Pane for `Icon`s of `Marker`s\r
4263                 this.createPane('markerPane');\r
4264                 // @pane tooltipPane: HTMLElement = 650\r
4265                 // Pane for `Tooltip`s.\r
4266                 this.createPane('tooltipPane');\r
4267                 // @pane popupPane: HTMLElement = 700\r
4268                 // Pane for `Popup`s.\r
4269                 this.createPane('popupPane');\r
4270 \r
4271                 if (!this.options.markerZoomAnimation) {\r
4272                         addClass(panes.markerPane, 'leaflet-zoom-hide');\r
4273                         addClass(panes.shadowPane, 'leaflet-zoom-hide');\r
4274                 }\r
4275         },\r
4276 \r
4277 \r
4278         // private methods that modify map state\r
4279 \r
4280         // @section Map state change events\r
4281         _resetView: function (center, zoom, noMoveStart) {\r
4282                 setPosition(this._mapPane, new Point(0, 0));\r
4283 \r
4284                 var loading = !this._loaded;\r
4285                 this._loaded = true;\r
4286                 zoom = this._limitZoom(zoom);\r
4287 \r
4288                 this.fire('viewprereset');\r
4289 \r
4290                 var zoomChanged = this._zoom !== zoom;\r
4291                 this\r
4292                         ._moveStart(zoomChanged, noMoveStart)\r
4293                         ._move(center, zoom)\r
4294                         ._moveEnd(zoomChanged);\r
4295 \r
4296                 // @event viewreset: Event\r
4297                 // Fired when the map needs to redraw its content (this usually happens\r
4298                 // on map zoom or load). Very useful for creating custom overlays.\r
4299                 this.fire('viewreset');\r
4300 \r
4301                 // @event load: Event\r
4302                 // Fired when the map is initialized (when its center and zoom are set\r
4303                 // for the first time).\r
4304                 if (loading) {\r
4305                         this.fire('load');\r
4306                 }\r
4307         },\r
4308 \r
4309         _moveStart: function (zoomChanged, noMoveStart) {\r
4310                 // @event zoomstart: Event\r
4311                 // Fired when the map zoom is about to change (e.g. before zoom animation).\r
4312                 // @event movestart: Event\r
4313                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).\r
4314                 if (zoomChanged) {\r
4315                         this.fire('zoomstart');\r
4316                 }\r
4317                 if (!noMoveStart) {\r
4318                         this.fire('movestart');\r
4319                 }\r
4320                 return this;\r
4321         },\r
4322 \r
4323         _move: function (center, zoom, data, supressEvent) {\r
4324                 if (zoom === undefined) {\r
4325                         zoom = this._zoom;\r
4326                 }\r
4327                 var zoomChanged = this._zoom !== zoom;\r
4328 \r
4329                 this._zoom = zoom;\r
4330                 this._lastCenter = center;\r
4331                 this._pixelOrigin = this._getNewPixelOrigin(center);\r
4332 \r
4333                 if (!supressEvent) {\r
4334                         // @event zoom: Event\r
4335                         // Fired repeatedly during any change in zoom level,\r
4336                         // including zoom and fly animations.\r
4337                         if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530\r
4338                                 this.fire('zoom', data);\r
4339                         }\r
4340 \r
4341                         // @event move: Event\r
4342                         // Fired repeatedly during any movement of the map,\r
4343                         // including pan and fly animations.\r
4344                         this.fire('move', data);\r
4345                 } else if (data && data.pinch) {        // Always fire 'zoom' if pinching because #3530\r
4346                         this.fire('zoom', data);\r
4347                 }\r
4348                 return this;\r
4349         },\r
4350 \r
4351         _moveEnd: function (zoomChanged) {\r
4352                 // @event zoomend: Event\r
4353                 // Fired when the map zoom changed, after any animations.\r
4354                 if (zoomChanged) {\r
4355                         this.fire('zoomend');\r
4356                 }\r
4357 \r
4358                 // @event moveend: Event\r
4359                 // Fired when the center of the map stops changing\r
4360                 // (e.g. user stopped dragging the map or after non-centered zoom).\r
4361                 return this.fire('moveend');\r
4362         },\r
4363 \r
4364         _stop: function () {\r
4365                 cancelAnimFrame(this._flyToFrame);\r
4366                 if (this._panAnim) {\r
4367                         this._panAnim.stop();\r
4368                 }\r
4369                 return this;\r
4370         },\r
4371 \r
4372         _rawPanBy: function (offset) {\r
4373                 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));\r
4374         },\r
4375 \r
4376         _getZoomSpan: function () {\r
4377                 return this.getMaxZoom() - this.getMinZoom();\r
4378         },\r
4379 \r
4380         _panInsideMaxBounds: function () {\r
4381                 if (!this._enforcingBounds) {\r
4382                         this.panInsideBounds(this.options.maxBounds);\r
4383                 }\r
4384         },\r
4385 \r
4386         _checkIfLoaded: function () {\r
4387                 if (!this._loaded) {\r
4388                         throw new Error('Set map center and zoom first.');\r
4389                 }\r
4390         },\r
4391 \r
4392         // DOM event handling\r
4393 \r
4394         // @section Interaction events\r
4395         _initEvents: function (remove) {\r
4396                 this._targets = {};\r
4397                 this._targets[stamp(this._container)] = this;\r
4398 \r
4399                 var onOff = remove ? off : on;\r
4400 \r
4401                 // @event click: MouseEvent\r
4402                 // Fired when the user clicks (or taps) the map.\r
4403                 // @event dblclick: MouseEvent\r
4404                 // Fired when the user double-clicks (or double-taps) the map.\r
4405                 // @event mousedown: MouseEvent\r
4406                 // Fired when the user pushes the mouse button on the map.\r
4407                 // @event mouseup: MouseEvent\r
4408                 // Fired when the user releases the mouse button on the map.\r
4409                 // @event mouseover: MouseEvent\r
4410                 // Fired when the mouse enters the map.\r
4411                 // @event mouseout: MouseEvent\r
4412                 // Fired when the mouse leaves the map.\r
4413                 // @event mousemove: MouseEvent\r
4414                 // Fired while the mouse moves over the map.\r
4415                 // @event contextmenu: MouseEvent\r
4416                 // Fired when the user pushes the right mouse button on the map, prevents\r
4417                 // default browser context menu from showing if there are listeners on\r
4418                 // this event. Also fired on mobile when the user holds a single touch\r
4419                 // for a second (also called long press).\r
4420                 // @event keypress: KeyboardEvent\r
4421                 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.\r
4422                 // @event keydown: KeyboardEvent\r
4423                 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,\r
4424                 // the `keydown` event is fired for keys that produce a character value and for keys\r
4425                 // that do not produce a character value.\r
4426                 // @event keyup: KeyboardEvent\r
4427                 // Fired when the user releases a key from the keyboard while the map is focused.\r
4428                 onOff(this._container, 'click dblclick mousedown mouseup ' +\r
4429                         'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);\r
4430 \r
4431                 if (this.options.trackResize) {\r
4432                         onOff(window, 'resize', this._onResize, this);\r
4433                 }\r
4434 \r
4435                 if (Browser.any3d && this.options.transform3DLimit) {\r
4436                         (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);\r
4437                 }\r
4438         },\r
4439 \r
4440         _onResize: function () {\r
4441                 cancelAnimFrame(this._resizeRequest);\r
4442                 this._resizeRequest = requestAnimFrame(\r
4443                         function () { this.invalidateSize({debounceMoveend: true}); }, this);\r
4444         },\r
4445 \r
4446         _onScroll: function () {\r
4447                 this._container.scrollTop  = 0;\r
4448                 this._container.scrollLeft = 0;\r
4449         },\r
4450 \r
4451         _onMoveEnd: function () {\r
4452                 var pos = this._getMapPanePos();\r
4453                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {\r
4454                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have\r
4455                         // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/\r
4456                         this._resetView(this.getCenter(), this.getZoom());\r
4457                 }\r
4458         },\r
4459 \r
4460         _findEventTargets: function (e, type) {\r
4461                 var targets = [],\r
4462                     target,\r
4463                     isHover = type === 'mouseout' || type === 'mouseover',\r
4464                     src = e.target || e.srcElement,\r
4465                     dragging = false;\r
4466 \r
4467                 while (src) {\r
4468                         target = this._targets[stamp(src)];\r
4469                         if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {\r
4470                                 // Prevent firing click after you just dragged an object.\r
4471                                 dragging = true;\r
4472                                 break;\r
4473                         }\r
4474                         if (target && target.listens(type, true)) {\r
4475                                 if (isHover && !isExternalTarget(src, e)) { break; }\r
4476                                 targets.push(target);\r
4477                                 if (isHover) { break; }\r
4478                         }\r
4479                         if (src === this._container) { break; }\r
4480                         src = src.parentNode;\r
4481                 }\r
4482                 if (!targets.length && !dragging && !isHover && this.listens(type, true)) {\r
4483                         targets = [this];\r
4484                 }\r
4485                 return targets;\r
4486         },\r
4487 \r
4488         _isClickDisabled: function (el) {\r
4489                 while (el && el !== this._container) {\r
4490                         if (el['_leaflet_disable_click']) { return true; }\r
4491                         el = el.parentNode;\r
4492                 }\r
4493         },\r
4494 \r
4495         _handleDOMEvent: function (e) {\r
4496                 var el = (e.target || e.srcElement);\r
4497                 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {\r
4498                         return;\r
4499                 }\r
4500 \r
4501                 var type = e.type;\r
4502 \r
4503                 if (type === 'mousedown') {\r
4504                         // prevents outline when clicking on keyboard-focusable element\r
4505                         preventOutline(el);\r
4506                 }\r
4507 \r
4508                 this._fireDOMEvent(e, type);\r
4509         },\r
4510 \r
4511         _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],\r
4512 \r
4513         _fireDOMEvent: function (e, type, canvasTargets) {\r
4514 \r
4515                 if (e.type === 'click') {\r
4516                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).\r
4517                         // @event preclick: MouseEvent\r
4518                         // Fired before mouse click on the map (sometimes useful when you\r
4519                         // want something to happen on click before any existing click\r
4520                         // handlers start running).\r
4521                         var synth = extend({}, e);\r
4522                         synth.type = 'preclick';\r
4523                         this._fireDOMEvent(synth, synth.type, canvasTargets);\r
4524                 }\r
4525 \r
4526                 // Find the layer the event is propagating from and its parents.\r
4527                 var targets = this._findEventTargets(e, type);\r
4528 \r
4529                 if (canvasTargets) {\r
4530                         var filtered = []; // pick only targets with listeners\r
4531                         for (var i = 0; i < canvasTargets.length; i++) {\r
4532                                 if (canvasTargets[i].listens(type, true)) {\r
4533                                         filtered.push(canvasTargets[i]);\r
4534                                 }\r
4535                         }\r
4536                         targets = filtered.concat(targets);\r
4537                 }\r
4538 \r
4539                 if (!targets.length) { return; }\r
4540 \r
4541                 if (type === 'contextmenu') {\r
4542                         preventDefault(e);\r
4543                 }\r
4544 \r
4545                 var target = targets[0];\r
4546                 var data = {\r
4547                         originalEvent: e\r
4548                 };\r
4549 \r
4550                 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {\r
4551                         var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);\r
4552                         data.containerPoint = isMarker ?\r
4553                                 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);\r
4554                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);\r
4555                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);\r
4556                 }\r
4557 \r
4558                 for (i = 0; i < targets.length; i++) {\r
4559                         targets[i].fire(type, data, true);\r
4560                         if (data.originalEvent._stopped ||\r
4561                                 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }\r
4562                 }\r
4563         },\r
4564 \r
4565         _draggableMoved: function (obj) {\r
4566                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;\r
4567                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());\r
4568         },\r
4569 \r
4570         _clearHandlers: function () {\r
4571                 for (var i = 0, len = this._handlers.length; i < len; i++) {\r
4572                         this._handlers[i].disable();\r
4573                 }\r
4574         },\r
4575 \r
4576         // @section Other Methods\r
4577 \r
4578         // @method whenReady(fn: Function, context?: Object): this\r
4579         // Runs the given function `fn` when the map gets initialized with\r
4580         // a view (center and zoom) and at least one layer, or immediately\r
4581         // if it's already initialized, optionally passing a function context.\r
4582         whenReady: function (callback, context) {\r
4583                 if (this._loaded) {\r
4584                         callback.call(context || this, {target: this});\r
4585                 } else {\r
4586                         this.on('load', callback, context);\r
4587                 }\r
4588                 return this;\r
4589         },\r
4590 \r
4591 \r
4592         // private methods for getting map state\r
4593 \r
4594         _getMapPanePos: function () {\r
4595                 return getPosition(this._mapPane) || new Point(0, 0);\r
4596         },\r
4597 \r
4598         _moved: function () {\r
4599                 var pos = this._getMapPanePos();\r
4600                 return pos && !pos.equals([0, 0]);\r
4601         },\r
4602 \r
4603         _getTopLeftPoint: function (center, zoom) {\r
4604                 var pixelOrigin = center && zoom !== undefined ?\r
4605                         this._getNewPixelOrigin(center, zoom) :\r
4606                         this.getPixelOrigin();\r
4607                 return pixelOrigin.subtract(this._getMapPanePos());\r
4608         },\r
4609 \r
4610         _getNewPixelOrigin: function (center, zoom) {\r
4611                 var viewHalf = this.getSize()._divideBy(2);\r
4612                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();\r
4613         },\r
4614 \r
4615         _latLngToNewLayerPoint: function (latlng, zoom, center) {\r
4616                 var topLeft = this._getNewPixelOrigin(center, zoom);\r
4617                 return this.project(latlng, zoom)._subtract(topLeft);\r
4618         },\r
4619 \r
4620         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {\r
4621                 var topLeft = this._getNewPixelOrigin(center, zoom);\r
4622                 return toBounds([\r
4623                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),\r
4624                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),\r
4625                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),\r
4626                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)\r
4627                 ]);\r
4628         },\r
4629 \r
4630         // layer point of the current center\r
4631         _getCenterLayerPoint: function () {\r
4632                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));\r
4633         },\r
4634 \r
4635         // offset of the specified place to the current center in pixels\r
4636         _getCenterOffset: function (latlng) {\r
4637                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());\r
4638         },\r
4639 \r
4640         // adjust center for view to get inside bounds\r
4641         _limitCenter: function (center, zoom, bounds) {\r
4642 \r
4643                 if (!bounds) { return center; }\r
4644 \r
4645                 var centerPoint = this.project(center, zoom),\r
4646                     viewHalf = this.getSize().divideBy(2),\r
4647                     viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),\r
4648                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);\r
4649 \r
4650                 // If offset is less than a pixel, ignore.\r
4651                 // This prevents unstable projections from getting into\r
4652                 // an infinite loop of tiny offsets.\r
4653                 if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {\r
4654                         return center;\r
4655                 }\r
4656 \r
4657                 return this.unproject(centerPoint.add(offset), zoom);\r
4658         },\r
4659 \r
4660         // adjust offset for view to get inside bounds\r
4661         _limitOffset: function (offset, bounds) {\r
4662                 if (!bounds) { return offset; }\r
4663 \r
4664                 var viewBounds = this.getPixelBounds(),\r
4665                     newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));\r
4666 \r
4667                 return offset.add(this._getBoundsOffset(newBounds, bounds));\r
4668         },\r
4669 \r
4670         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom\r
4671         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {\r
4672                 var projectedMaxBounds = toBounds(\r
4673                         this.project(maxBounds.getNorthEast(), zoom),\r
4674                         this.project(maxBounds.getSouthWest(), zoom)\r
4675                     ),\r
4676                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),\r
4677                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),\r
4678 \r
4679                     dx = this._rebound(minOffset.x, -maxOffset.x),\r
4680                     dy = this._rebound(minOffset.y, -maxOffset.y);\r
4681 \r
4682                 return new Point(dx, dy);\r
4683         },\r
4684 \r
4685         _rebound: function (left, right) {\r
4686                 return left + right > 0 ?\r
4687                         Math.round(left - right) / 2 :\r
4688                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));\r
4689         },\r
4690 \r
4691         _limitZoom: function (zoom) {\r
4692                 var min = this.getMinZoom(),\r
4693                     max = this.getMaxZoom(),\r
4694                     snap = Browser.any3d ? this.options.zoomSnap : 1;\r
4695                 if (snap) {\r
4696                         zoom = Math.round(zoom / snap) * snap;\r
4697                 }\r
4698                 return Math.max(min, Math.min(max, zoom));\r
4699         },\r
4700 \r
4701         _onPanTransitionStep: function () {\r
4702                 this.fire('move');\r
4703         },\r
4704 \r
4705         _onPanTransitionEnd: function () {\r
4706                 removeClass(this._mapPane, 'leaflet-pan-anim');\r
4707                 this.fire('moveend');\r
4708         },\r
4709 \r
4710         _tryAnimatedPan: function (center, options) {\r
4711                 // difference between the new and current centers in pixels\r
4712                 var offset = this._getCenterOffset(center)._trunc();\r
4713 \r
4714                 // don't animate too far unless animate: true specified in options\r
4715                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }\r
4716 \r
4717                 this.panBy(offset, options);\r
4718 \r
4719                 return true;\r
4720         },\r
4721 \r
4722         _createAnimProxy: function () {\r
4723 \r
4724                 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');\r
4725                 this._panes.mapPane.appendChild(proxy);\r
4726 \r
4727                 this.on('zoomanim', function (e) {\r
4728                         var prop = TRANSFORM,\r
4729                             transform = this._proxy.style[prop];\r
4730 \r
4731                         setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));\r
4732 \r
4733                         // workaround for case when transform is the same and so transitionend event is not fired\r
4734                         if (transform === this._proxy.style[prop] && this._animatingZoom) {\r
4735                                 this._onZoomTransitionEnd();\r
4736                         }\r
4737                 }, this);\r
4738 \r
4739                 this.on('load moveend', this._animMoveEnd, this);\r
4740 \r
4741                 this._on('unload', this._destroyAnimProxy, this);\r
4742         },\r
4743 \r
4744         _destroyAnimProxy: function () {\r
4745                 remove(this._proxy);\r
4746                 this.off('load moveend', this._animMoveEnd, this);\r
4747                 delete this._proxy;\r
4748         },\r
4749 \r
4750         _animMoveEnd: function () {\r
4751                 var c = this.getCenter(),\r
4752                     z = this.getZoom();\r
4753                 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));\r
4754         },\r
4755 \r
4756         _catchTransitionEnd: function (e) {\r
4757                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {\r
4758                         this._onZoomTransitionEnd();\r
4759                 }\r
4760         },\r
4761 \r
4762         _nothingToAnimate: function () {\r
4763                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;\r
4764         },\r
4765 \r
4766         _tryAnimatedZoom: function (center, zoom, options) {\r
4767 \r
4768                 if (this._animatingZoom) { return true; }\r
4769 \r
4770                 options = options || {};\r
4771 \r
4772                 // don't animate if disabled, not supported or zoom difference is too large\r
4773                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||\r
4774                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }\r
4775 \r
4776                 // offset is the pixel coords of the zoom origin relative to the current center\r
4777                 var scale = this.getZoomScale(zoom),\r
4778                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);\r
4779 \r
4780                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced\r
4781                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }\r
4782 \r
4783                 requestAnimFrame(function () {\r
4784                         this\r
4785                             ._moveStart(true, options.noMoveStart || false)\r
4786                             ._animateZoom(center, zoom, true);\r
4787                 }, this);\r
4788 \r
4789                 return true;\r
4790         },\r
4791 \r
4792         _animateZoom: function (center, zoom, startAnim, noUpdate) {\r
4793                 if (!this._mapPane) { return; }\r
4794 \r
4795                 if (startAnim) {\r
4796                         this._animatingZoom = true;\r
4797 \r
4798                         // remember what center/zoom to set after animation\r
4799                         this._animateToCenter = center;\r
4800                         this._animateToZoom = zoom;\r
4801 \r
4802                         addClass(this._mapPane, 'leaflet-zoom-anim');\r
4803                 }\r
4804 \r
4805                 // @section Other Events\r
4806                 // @event zoomanim: ZoomAnimEvent\r
4807                 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.\r
4808                 this.fire('zoomanim', {\r
4809                         center: center,\r
4810                         zoom: zoom,\r
4811                         noUpdate: noUpdate\r
4812                 });\r
4813 \r
4814                 if (!this._tempFireZoomEvent) {\r
4815                         this._tempFireZoomEvent = this._zoom !== this._animateToZoom;\r
4816                 }\r
4817 \r
4818                 this._move(this._animateToCenter, this._animateToZoom, undefined, true);\r
4819 \r
4820                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693\r
4821                 setTimeout(bind(this._onZoomTransitionEnd, this), 250);\r
4822         },\r
4823 \r
4824         _onZoomTransitionEnd: function () {\r
4825                 if (!this._animatingZoom) { return; }\r
4826 \r
4827                 if (this._mapPane) {\r
4828                         removeClass(this._mapPane, 'leaflet-zoom-anim');\r
4829                 }\r
4830 \r
4831                 this._animatingZoom = false;\r
4832 \r
4833                 this._move(this._animateToCenter, this._animateToZoom, undefined, true);\r
4834 \r
4835                 if (this._tempFireZoomEvent) {\r
4836                         this.fire('zoom');\r
4837                 }\r
4838                 delete this._tempFireZoomEvent;\r
4839 \r
4840                 this.fire('move');\r
4841 \r
4842                 this._moveEnd(true);\r
4843         }\r
4844 });\r
4845 \r
4846 // @section\r
4847 \r
4848 // @factory L.map(id: String, options?: Map options)\r
4849 // Instantiates a map object given the DOM ID of a `<div>` element\r
4850 // and optionally an object literal with `Map options`.\r
4851 //\r
4852 // @alternative\r
4853 // @factory L.map(el: HTMLElement, options?: Map options)\r
4854 // Instantiates a map object given an instance of a `<div>` HTML element\r
4855 // and optionally an object literal with `Map options`.\r
4856 function createMap(id, options) {\r
4857         return new Map(id, options);\r
4858 }
4859
4860 /*\r
4861  * @class Control\r
4862  * @aka L.Control\r
4863  * @inherits Class\r
4864  *\r
4865  * L.Control is a base class for implementing map controls. Handles positioning.\r
4866  * All other controls extend from this class.\r
4867  */\r
4868 \r
4869 var Control = Class.extend({\r
4870         // @section\r
4871         // @aka Control Options\r
4872         options: {\r
4873                 // @option position: String = 'topright'\r
4874                 // The position of the control (one of the map corners). Possible values are `'topleft'`,\r
4875                 // `'topright'`, `'bottomleft'` or `'bottomright'`\r
4876                 position: 'topright'\r
4877         },\r
4878 \r
4879         initialize: function (options) {\r
4880                 setOptions(this, options);\r
4881         },\r
4882 \r
4883         /* @section\r
4884          * Classes extending L.Control will inherit the following methods:\r
4885          *\r
4886          * @method getPosition: string\r
4887          * Returns the position of the control.\r
4888          */\r
4889         getPosition: function () {\r
4890                 return this.options.position;\r
4891         },\r
4892 \r
4893         // @method setPosition(position: string): this\r
4894         // Sets the position of the control.\r
4895         setPosition: function (position) {\r
4896                 var map = this._map;\r
4897 \r
4898                 if (map) {\r
4899                         map.removeControl(this);\r
4900                 }\r
4901 \r
4902                 this.options.position = position;\r
4903 \r
4904                 if (map) {\r
4905                         map.addControl(this);\r
4906                 }\r
4907 \r
4908                 return this;\r
4909         },\r
4910 \r
4911         // @method getContainer: HTMLElement\r
4912         // Returns the HTMLElement that contains the control.\r
4913         getContainer: function () {\r
4914                 return this._container;\r
4915         },\r
4916 \r
4917         // @method addTo(map: Map): this\r
4918         // Adds the control to the given map.\r
4919         addTo: function (map) {\r
4920                 this.remove();\r
4921                 this._map = map;\r
4922 \r
4923                 var container = this._container = this.onAdd(map),\r
4924                     pos = this.getPosition(),\r
4925                     corner = map._controlCorners[pos];\r
4926 \r
4927                 addClass(container, 'leaflet-control');\r
4928 \r
4929                 if (pos.indexOf('bottom') !== -1) {\r
4930                         corner.insertBefore(container, corner.firstChild);\r
4931                 } else {\r
4932                         corner.appendChild(container);\r
4933                 }\r
4934 \r
4935                 this._map.on('unload', this.remove, this);\r
4936 \r
4937                 return this;\r
4938         },\r
4939 \r
4940         // @method remove: this\r
4941         // Removes the control from the map it is currently active on.\r
4942         remove: function () {\r
4943                 if (!this._map) {\r
4944                         return this;\r
4945                 }\r
4946 \r
4947                 remove(this._container);\r
4948 \r
4949                 if (this.onRemove) {\r
4950                         this.onRemove(this._map);\r
4951                 }\r
4952 \r
4953                 this._map.off('unload', this.remove, this);\r
4954                 this._map = null;\r
4955 \r
4956                 return this;\r
4957         },\r
4958 \r
4959         _refocusOnMap: function (e) {\r
4960                 // if map exists and event is not a keyboard event\r
4961                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {\r
4962                         this._map.getContainer().focus();\r
4963                 }\r
4964         }\r
4965 });\r
4966 \r
4967 var control = function (options) {\r
4968         return new Control(options);\r
4969 };\r
4970 \r
4971 /* @section Extension methods\r
4972  * @uninheritable\r
4973  *\r
4974  * Every control should extend from `L.Control` and (re-)implement the following methods.\r
4975  *\r
4976  * @method onAdd(map: Map): HTMLElement\r
4977  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).\r
4978  *\r
4979  * @method onRemove(map: Map)\r
4980  * 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
4981  */\r
4982 \r
4983 /* @namespace Map\r
4984  * @section Methods for Layers and Controls\r
4985  */\r
4986 Map.include({\r
4987         // @method addControl(control: Control): this\r
4988         // Adds the given control to the map\r
4989         addControl: function (control) {\r
4990                 control.addTo(this);\r
4991                 return this;\r
4992         },\r
4993 \r
4994         // @method removeControl(control: Control): this\r
4995         // Removes the given control from the map\r
4996         removeControl: function (control) {\r
4997                 control.remove();\r
4998                 return this;\r
4999         },\r
5000 \r
5001         _initControlPos: function () {\r
5002                 var corners = this._controlCorners = {},\r
5003                     l = 'leaflet-',\r
5004                     container = this._controlContainer =\r
5005                             create$1('div', l + 'control-container', this._container);\r
5006 \r
5007                 function createCorner(vSide, hSide) {\r
5008                         var className = l + vSide + ' ' + l + hSide;\r
5009 \r
5010                         corners[vSide + hSide] = create$1('div', className, container);\r
5011                 }\r
5012 \r
5013                 createCorner('top', 'left');\r
5014                 createCorner('top', 'right');\r
5015                 createCorner('bottom', 'left');\r
5016                 createCorner('bottom', 'right');\r
5017         },\r
5018 \r
5019         _clearControlPos: function () {\r
5020                 for (var i in this._controlCorners) {\r
5021                         remove(this._controlCorners[i]);\r
5022                 }\r
5023                 remove(this._controlContainer);\r
5024                 delete this._controlCorners;\r
5025                 delete this._controlContainer;\r
5026         }\r
5027 });
5028
5029 /*\r
5030  * @class Control.Layers\r
5031  * @aka L.Control.Layers\r
5032  * @inherits Control\r
5033  *\r
5034  * 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
5035  *\r
5036  * @example\r
5037  *\r
5038  * ```js\r
5039  * var baseLayers = {\r
5040  *      "Mapbox": mapbox,\r
5041  *      "OpenStreetMap": osm\r
5042  * };\r
5043  *\r
5044  * var overlays = {\r
5045  *      "Marker": marker,\r
5046  *      "Roads": roadsLayer\r
5047  * };\r
5048  *\r
5049  * L.control.layers(baseLayers, overlays).addTo(map);\r
5050  * ```\r
5051  *\r
5052  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:\r
5053  *\r
5054  * ```js\r
5055  * {\r
5056  *     "<someName1>": layer1,\r
5057  *     "<someName2>": layer2\r
5058  * }\r
5059  * ```\r
5060  *\r
5061  * The layer names can contain HTML, which allows you to add additional styling to the items:\r
5062  *\r
5063  * ```js\r
5064  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}\r
5065  * ```\r
5066  */\r
5067 \r
5068 var Layers = Control.extend({\r
5069         // @section\r
5070         // @aka Control.Layers options\r
5071         options: {\r
5072                 // @option collapsed: Boolean = true\r
5073                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.\r
5074                 collapsed: true,\r
5075                 position: 'topright',\r
5076 \r
5077                 // @option autoZIndex: Boolean = true\r
5078                 // 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
5079                 autoZIndex: true,\r
5080 \r
5081                 // @option hideSingleBase: Boolean = false\r
5082                 // If `true`, the base layers in the control will be hidden when there is only one.\r
5083                 hideSingleBase: false,\r
5084 \r
5085                 // @option sortLayers: Boolean = false\r
5086                 // Whether to sort the layers. When `false`, layers will keep the order\r
5087                 // in which they were added to the control.\r
5088                 sortLayers: false,\r
5089 \r
5090                 // @option sortFunction: Function = *\r
5091                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)\r
5092                 // that will be used for sorting the layers, when `sortLayers` is `true`.\r
5093                 // The function receives both the `L.Layer` instances and their names, as in\r
5094                 // `sortFunction(layerA, layerB, nameA, nameB)`.\r
5095                 // By default, it sorts layers alphabetically by their name.\r
5096                 sortFunction: function (layerA, layerB, nameA, nameB) {\r
5097                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);\r
5098                 }\r
5099         },\r
5100 \r
5101         initialize: function (baseLayers, overlays, options) {\r
5102                 setOptions(this, options);\r
5103 \r
5104                 this._layerControlInputs = [];\r
5105                 this._layers = [];\r
5106                 this._lastZIndex = 0;\r
5107                 this._handlingClick = false;\r
5108                 this._preventClick = false;\r
5109 \r
5110                 for (var i in baseLayers) {\r
5111                         this._addLayer(baseLayers[i], i);\r
5112                 }\r
5113 \r
5114                 for (i in overlays) {\r
5115                         this._addLayer(overlays[i], i, true);\r
5116                 }\r
5117         },\r
5118 \r
5119         onAdd: function (map) {\r
5120                 this._initLayout();\r
5121                 this._update();\r
5122 \r
5123                 this._map = map;\r
5124                 map.on('zoomend', this._checkDisabledLayers, this);\r
5125 \r
5126                 for (var i = 0; i < this._layers.length; i++) {\r
5127                         this._layers[i].layer.on('add remove', this._onLayerChange, this);\r
5128                 }\r
5129 \r
5130                 return this._container;\r
5131         },\r
5132 \r
5133         addTo: function (map) {\r
5134                 Control.prototype.addTo.call(this, map);\r
5135                 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.\r
5136                 return this._expandIfNotCollapsed();\r
5137         },\r
5138 \r
5139         onRemove: function () {\r
5140                 this._map.off('zoomend', this._checkDisabledLayers, this);\r
5141 \r
5142                 for (var i = 0; i < this._layers.length; i++) {\r
5143                         this._layers[i].layer.off('add remove', this._onLayerChange, this);\r
5144                 }\r
5145         },\r
5146 \r
5147         // @method addBaseLayer(layer: Layer, name: String): this\r
5148         // Adds a base layer (radio button entry) with the given name to the control.\r
5149         addBaseLayer: function (layer, name) {\r
5150                 this._addLayer(layer, name);\r
5151                 return (this._map) ? this._update() : this;\r
5152         },\r
5153 \r
5154         // @method addOverlay(layer: Layer, name: String): this\r
5155         // Adds an overlay (checkbox entry) with the given name to the control.\r
5156         addOverlay: function (layer, name) {\r
5157                 this._addLayer(layer, name, true);\r
5158                 return (this._map) ? this._update() : this;\r
5159         },\r
5160 \r
5161         // @method removeLayer(layer: Layer): this\r
5162         // Remove the given layer from the control.\r
5163         removeLayer: function (layer) {\r
5164                 layer.off('add remove', this._onLayerChange, this);\r
5165 \r
5166                 var obj = this._getLayer(stamp(layer));\r
5167                 if (obj) {\r
5168                         this._layers.splice(this._layers.indexOf(obj), 1);\r
5169                 }\r
5170                 return (this._map) ? this._update() : this;\r
5171         },\r
5172 \r
5173         // @method expand(): this\r
5174         // Expand the control container if collapsed.\r
5175         expand: function () {\r
5176                 addClass(this._container, 'leaflet-control-layers-expanded');\r
5177                 this._section.style.height = null;\r
5178                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);\r
5179                 if (acceptableHeight < this._section.clientHeight) {\r
5180                         addClass(this._section, 'leaflet-control-layers-scrollbar');\r
5181                         this._section.style.height = acceptableHeight + 'px';\r
5182                 } else {\r
5183                         removeClass(this._section, 'leaflet-control-layers-scrollbar');\r
5184                 }\r
5185                 this._checkDisabledLayers();\r
5186                 return this;\r
5187         },\r
5188 \r
5189         // @method collapse(): this\r
5190         // Collapse the control container if expanded.\r
5191         collapse: function () {\r
5192                 removeClass(this._container, 'leaflet-control-layers-expanded');\r
5193                 return this;\r
5194         },\r
5195 \r
5196         _initLayout: function () {\r
5197                 var className = 'leaflet-control-layers',\r
5198                     container = this._container = create$1('div', className),\r
5199                     collapsed = this.options.collapsed;\r
5200 \r
5201                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released\r
5202                 container.setAttribute('aria-haspopup', true);\r
5203 \r
5204                 disableClickPropagation(container);\r
5205                 disableScrollPropagation(container);\r
5206 \r
5207                 var section = this._section = create$1('section', className + '-list');\r
5208 \r
5209                 if (collapsed) {\r
5210                         this._map.on('click', this.collapse, this);\r
5211 \r
5212                         on(container, {\r
5213                                 mouseenter: this._expandSafely,\r
5214                                 mouseleave: this.collapse\r
5215                         }, this);\r
5216                 }\r
5217 \r
5218                 var link = this._layersLink = create$1('a', className + '-toggle', container);\r
5219                 link.href = '#';\r
5220                 link.title = 'Layers';\r
5221                 link.setAttribute('role', 'button');\r
5222 \r
5223                 on(link, {\r
5224                         keydown: function (e) {\r
5225                                 if (e.keyCode === 13) {\r
5226                                         this._expandSafely();\r
5227                                 }\r
5228                         },\r
5229                         // Certain screen readers intercept the key event and instead send a click event\r
5230                         click: function (e) {\r
5231                                 preventDefault(e);\r
5232                                 this._expandSafely();\r
5233                         }\r
5234                 }, this);\r
5235 \r
5236                 if (!collapsed) {\r
5237                         this.expand();\r
5238                 }\r
5239 \r
5240                 this._baseLayersList = create$1('div', className + '-base', section);\r
5241                 this._separator = create$1('div', className + '-separator', section);\r
5242                 this._overlaysList = create$1('div', className + '-overlays', section);\r
5243 \r
5244                 container.appendChild(section);\r
5245         },\r
5246 \r
5247         _getLayer: function (id) {\r
5248                 for (var i = 0; i < this._layers.length; i++) {\r
5249 \r
5250                         if (this._layers[i] && stamp(this._layers[i].layer) === id) {\r
5251                                 return this._layers[i];\r
5252                         }\r
5253                 }\r
5254         },\r
5255 \r
5256         _addLayer: function (layer, name, overlay) {\r
5257                 if (this._map) {\r
5258                         layer.on('add remove', this._onLayerChange, this);\r
5259                 }\r
5260 \r
5261                 this._layers.push({\r
5262                         layer: layer,\r
5263                         name: name,\r
5264                         overlay: overlay\r
5265                 });\r
5266 \r
5267                 if (this.options.sortLayers) {\r
5268                         this._layers.sort(bind(function (a, b) {\r
5269                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);\r
5270                         }, this));\r
5271                 }\r
5272 \r
5273                 if (this.options.autoZIndex && layer.setZIndex) {\r
5274                         this._lastZIndex++;\r
5275                         layer.setZIndex(this._lastZIndex);\r
5276                 }\r
5277 \r
5278                 this._expandIfNotCollapsed();\r
5279         },\r
5280 \r
5281         _update: function () {\r
5282                 if (!this._container) { return this; }\r
5283 \r
5284                 empty(this._baseLayersList);\r
5285                 empty(this._overlaysList);\r
5286 \r
5287                 this._layerControlInputs = [];\r
5288                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;\r
5289 \r
5290                 for (i = 0; i < this._layers.length; i++) {\r
5291                         obj = this._layers[i];\r
5292                         this._addItem(obj);\r
5293                         overlaysPresent = overlaysPresent || obj.overlay;\r
5294                         baseLayersPresent = baseLayersPresent || !obj.overlay;\r
5295                         baseLayersCount += !obj.overlay ? 1 : 0;\r
5296                 }\r
5297 \r
5298                 // Hide base layers section if there's only one layer.\r
5299                 if (this.options.hideSingleBase) {\r
5300                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;\r
5301                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';\r
5302                 }\r
5303 \r
5304                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';\r
5305 \r
5306                 return this;\r
5307         },\r
5308 \r
5309         _onLayerChange: function (e) {\r
5310                 if (!this._handlingClick) {\r
5311                         this._update();\r
5312                 }\r
5313 \r
5314                 var obj = this._getLayer(stamp(e.target));\r
5315 \r
5316                 // @namespace Map\r
5317                 // @section Layer events\r
5318                 // @event baselayerchange: LayersControlEvent\r
5319                 // Fired when the base layer is changed through the [layers control](#control-layers).\r
5320                 // @event overlayadd: LayersControlEvent\r
5321                 // Fired when an overlay is selected through the [layers control](#control-layers).\r
5322                 // @event overlayremove: LayersControlEvent\r
5323                 // Fired when an overlay is deselected through the [layers control](#control-layers).\r
5324                 // @namespace Control.Layers\r
5325                 var type = obj.overlay ?\r
5326                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :\r
5327                         (e.type === 'add' ? 'baselayerchange' : null);\r
5328 \r
5329                 if (type) {\r
5330                         this._map.fire(type, obj);\r
5331                 }\r
5332         },\r
5333 \r
5334         // 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
5335         _createRadioElement: function (name, checked) {\r
5336 \r
5337                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +\r
5338                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';\r
5339 \r
5340                 var radioFragment = document.createElement('div');\r
5341                 radioFragment.innerHTML = radioHtml;\r
5342 \r
5343                 return radioFragment.firstChild;\r
5344         },\r
5345 \r
5346         _addItem: function (obj) {\r
5347                 var label = document.createElement('label'),\r
5348                     checked = this._map.hasLayer(obj.layer),\r
5349                     input;\r
5350 \r
5351                 if (obj.overlay) {\r
5352                         input = document.createElement('input');\r
5353                         input.type = 'checkbox';\r
5354                         input.className = 'leaflet-control-layers-selector';\r
5355                         input.defaultChecked = checked;\r
5356                 } else {\r
5357                         input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);\r
5358                 }\r
5359 \r
5360                 this._layerControlInputs.push(input);\r
5361                 input.layerId = stamp(obj.layer);\r
5362 \r
5363                 on(input, 'click', this._onInputClick, this);\r
5364 \r
5365                 var name = document.createElement('span');\r
5366                 name.innerHTML = ' ' + obj.name;\r
5367 \r
5368                 // Helps from preventing layer control flicker when checkboxes are disabled\r
5369                 // https://github.com/Leaflet/Leaflet/issues/2771\r
5370                 var holder = document.createElement('span');\r
5371 \r
5372                 label.appendChild(holder);\r
5373                 holder.appendChild(input);\r
5374                 holder.appendChild(name);\r
5375 \r
5376                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;\r
5377                 container.appendChild(label);\r
5378 \r
5379                 this._checkDisabledLayers();\r
5380                 return label;\r
5381         },\r
5382 \r
5383         _onInputClick: function () {\r
5384                 // expanding the control on mobile with a click can cause adding a layer - we don't want this\r
5385                 if (this._preventClick) {\r
5386                         return;\r
5387                 }\r
5388 \r
5389                 var inputs = this._layerControlInputs,\r
5390                     input, layer;\r
5391                 var addedLayers = [],\r
5392                     removedLayers = [];\r
5393 \r
5394                 this._handlingClick = true;\r
5395 \r
5396                 for (var i = inputs.length - 1; i >= 0; i--) {\r
5397                         input = inputs[i];\r
5398                         layer = this._getLayer(input.layerId).layer;\r
5399 \r
5400                         if (input.checked) {\r
5401                                 addedLayers.push(layer);\r
5402                         } else if (!input.checked) {\r
5403                                 removedLayers.push(layer);\r
5404                         }\r
5405                 }\r
5406 \r
5407                 // Bugfix issue 2318: Should remove all old layers before readding new ones\r
5408                 for (i = 0; i < removedLayers.length; i++) {\r
5409                         if (this._map.hasLayer(removedLayers[i])) {\r
5410                                 this._map.removeLayer(removedLayers[i]);\r
5411                         }\r
5412                 }\r
5413                 for (i = 0; i < addedLayers.length; i++) {\r
5414                         if (!this._map.hasLayer(addedLayers[i])) {\r
5415                                 this._map.addLayer(addedLayers[i]);\r
5416                         }\r
5417                 }\r
5418 \r
5419                 this._handlingClick = false;\r
5420 \r
5421                 this._refocusOnMap();\r
5422         },\r
5423 \r
5424         _checkDisabledLayers: function () {\r
5425                 var inputs = this._layerControlInputs,\r
5426                     input,\r
5427                     layer,\r
5428                     zoom = this._map.getZoom();\r
5429 \r
5430                 for (var i = inputs.length - 1; i >= 0; i--) {\r
5431                         input = inputs[i];\r
5432                         layer = this._getLayer(input.layerId).layer;\r
5433                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||\r
5434                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);\r
5435 \r
5436                 }\r
5437         },\r
5438 \r
5439         _expandIfNotCollapsed: function () {\r
5440                 if (this._map && !this.options.collapsed) {\r
5441                         this.expand();\r
5442                 }\r
5443                 return this;\r
5444         },\r
5445 \r
5446         _expandSafely: function () {\r
5447                 var section = this._section;\r
5448                 this._preventClick = true;\r
5449                 on(section, 'click', preventDefault);\r
5450                 this.expand();\r
5451                 var that = this;\r
5452                 setTimeout(function () {\r
5453                         off(section, 'click', preventDefault);\r
5454                         that._preventClick = false;\r
5455                 });\r
5456         }\r
5457 \r
5458 });\r
5459 \r
5460 \r
5461 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)\r
5462 // 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
5463 var layers = function (baseLayers, overlays, options) {\r
5464         return new Layers(baseLayers, overlays, options);\r
5465 };
5466
5467 /*\r
5468  * @class Control.Zoom\r
5469  * @aka L.Control.Zoom\r
5470  * @inherits Control\r
5471  *\r
5472  * 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
5473  */\r
5474 \r
5475 var Zoom = Control.extend({\r
5476         // @section\r
5477         // @aka Control.Zoom options\r
5478         options: {\r
5479                 position: 'topleft',\r
5480 \r
5481                 // @option zoomInText: String = '<span aria-hidden="true">+</span>'\r
5482                 // The text set on the 'zoom in' button.\r
5483                 zoomInText: '<span aria-hidden="true">+</span>',\r
5484 \r
5485                 // @option zoomInTitle: String = 'Zoom in'\r
5486                 // The title set on the 'zoom in' button.\r
5487                 zoomInTitle: 'Zoom in',\r
5488 \r
5489                 // @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'\r
5490                 // The text set on the 'zoom out' button.\r
5491                 zoomOutText: '<span aria-hidden="true">&#x2212;</span>',\r
5492 \r
5493                 // @option zoomOutTitle: String = 'Zoom out'\r
5494                 // The title set on the 'zoom out' button.\r
5495                 zoomOutTitle: 'Zoom out'\r
5496         },\r
5497 \r
5498         onAdd: function (map) {\r
5499                 var zoomName = 'leaflet-control-zoom',\r
5500                     container = create$1('div', zoomName + ' leaflet-bar'),\r
5501                     options = this.options;\r
5502 \r
5503                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,\r
5504                         zoomName + '-in',  container, this._zoomIn);\r
5505                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,\r
5506                         zoomName + '-out', container, this._zoomOut);\r
5507 \r
5508                 this._updateDisabled();\r
5509                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);\r
5510 \r
5511                 return container;\r
5512         },\r
5513 \r
5514         onRemove: function (map) {\r
5515                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);\r
5516         },\r
5517 \r
5518         disable: function () {\r
5519                 this._disabled = true;\r
5520                 this._updateDisabled();\r
5521                 return this;\r
5522         },\r
5523 \r
5524         enable: function () {\r
5525                 this._disabled = false;\r
5526                 this._updateDisabled();\r
5527                 return this;\r
5528         },\r
5529 \r
5530         _zoomIn: function (e) {\r
5531                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {\r
5532                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));\r
5533                 }\r
5534         },\r
5535 \r
5536         _zoomOut: function (e) {\r
5537                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {\r
5538                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));\r
5539                 }\r
5540         },\r
5541 \r
5542         _createButton: function (html, title, className, container, fn) {\r
5543                 var link = create$1('a', className, container);\r
5544                 link.innerHTML = html;\r
5545                 link.href = '#';\r
5546                 link.title = title;\r
5547 \r
5548                 /*\r
5549                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"\r
5550                  */\r
5551                 link.setAttribute('role', 'button');\r
5552                 link.setAttribute('aria-label', title);\r
5553 \r
5554                 disableClickPropagation(link);\r
5555                 on(link, 'click', stop);\r
5556                 on(link, 'click', fn, this);\r
5557                 on(link, 'click', this._refocusOnMap, this);\r
5558 \r
5559                 return link;\r
5560         },\r
5561 \r
5562         _updateDisabled: function () {\r
5563                 var map = this._map,\r
5564                     className = 'leaflet-disabled';\r
5565 \r
5566                 removeClass(this._zoomInButton, className);\r
5567                 removeClass(this._zoomOutButton, className);\r
5568                 this._zoomInButton.setAttribute('aria-disabled', 'false');\r
5569                 this._zoomOutButton.setAttribute('aria-disabled', 'false');\r
5570 \r
5571                 if (this._disabled || map._zoom === map.getMinZoom()) {\r
5572                         addClass(this._zoomOutButton, className);\r
5573                         this._zoomOutButton.setAttribute('aria-disabled', 'true');\r
5574                 }\r
5575                 if (this._disabled || map._zoom === map.getMaxZoom()) {\r
5576                         addClass(this._zoomInButton, className);\r
5577                         this._zoomInButton.setAttribute('aria-disabled', 'true');\r
5578                 }\r
5579         }\r
5580 });\r
5581 \r
5582 // @namespace Map\r
5583 // @section Control options\r
5584 // @option zoomControl: Boolean = true\r
5585 // Whether a [zoom control](#control-zoom) is added to the map by default.\r
5586 Map.mergeOptions({\r
5587         zoomControl: true\r
5588 });\r
5589 \r
5590 Map.addInitHook(function () {\r
5591         if (this.options.zoomControl) {\r
5592                 // @section Controls\r
5593                 // @property zoomControl: Control.Zoom\r
5594                 // The default zoom control (only available if the\r
5595                 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).\r
5596                 this.zoomControl = new Zoom();\r
5597                 this.addControl(this.zoomControl);\r
5598         }\r
5599 });\r
5600 \r
5601 // @namespace Control.Zoom\r
5602 // @factory L.control.zoom(options: Control.Zoom options)\r
5603 // Creates a zoom control\r
5604 var zoom = function (options) {\r
5605         return new Zoom(options);\r
5606 };
5607
5608 /*
5609  * @class Control.Scale
5610  * @aka L.Control.Scale
5611  * @inherits Control
5612  *
5613  * 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`.
5614  *
5615  * @example
5616  *
5617  * ```js
5618  * L.control.scale().addTo(map);
5619  * ```
5620  */
5621
5622 var Scale = Control.extend({
5623         // @section
5624         // @aka Control.Scale options
5625         options: {
5626                 position: 'bottomleft',
5627
5628                 // @option maxWidth: Number = 100
5629                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5630                 maxWidth: 100,
5631
5632                 // @option metric: Boolean = True
5633                 // Whether to show the metric scale line (m/km).
5634                 metric: true,
5635
5636                 // @option imperial: Boolean = True
5637                 // Whether to show the imperial scale line (mi/ft).
5638                 imperial: true
5639
5640                 // @option updateWhenIdle: Boolean = false
5641                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5642         },
5643
5644         onAdd: function (map) {
5645                 var className = 'leaflet-control-scale',
5646                     container = create$1('div', className),
5647                     options = this.options;
5648
5649                 this._addScales(options, className + '-line', container);
5650
5651                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5652                 map.whenReady(this._update, this);
5653
5654                 return container;
5655         },
5656
5657         onRemove: function (map) {
5658                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5659         },
5660
5661         _addScales: function (options, className, container) {
5662                 if (options.metric) {
5663                         this._mScale = create$1('div', className, container);
5664                 }
5665                 if (options.imperial) {
5666                         this._iScale = create$1('div', className, container);
5667                 }
5668         },
5669
5670         _update: function () {
5671                 var map = this._map,
5672                     y = map.getSize().y / 2;
5673
5674                 var maxMeters = map.distance(
5675                         map.containerPointToLatLng([0, y]),
5676                         map.containerPointToLatLng([this.options.maxWidth, y]));
5677
5678                 this._updateScales(maxMeters);
5679         },
5680
5681         _updateScales: function (maxMeters) {
5682                 if (this.options.metric && maxMeters) {
5683                         this._updateMetric(maxMeters);
5684                 }
5685                 if (this.options.imperial && maxMeters) {
5686                         this._updateImperial(maxMeters);
5687                 }
5688         },
5689
5690         _updateMetric: function (maxMeters) {
5691                 var meters = this._getRoundNum(maxMeters),
5692                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5693
5694                 this._updateScale(this._mScale, label, meters / maxMeters);
5695         },
5696
5697         _updateImperial: function (maxMeters) {
5698                 var maxFeet = maxMeters * 3.2808399,
5699                     maxMiles, miles, feet;
5700
5701                 if (maxFeet > 5280) {
5702                         maxMiles = maxFeet / 5280;
5703                         miles = this._getRoundNum(maxMiles);
5704                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5705
5706                 } else {
5707                         feet = this._getRoundNum(maxFeet);
5708                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5709                 }
5710         },
5711
5712         _updateScale: function (scale, text, ratio) {
5713                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5714                 scale.innerHTML = text;
5715         },
5716
5717         _getRoundNum: function (num) {
5718                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5719                     d = num / pow10;
5720
5721                 d = d >= 10 ? 10 :
5722                     d >= 5 ? 5 :
5723                     d >= 3 ? 3 :
5724                     d >= 2 ? 2 : 1;
5725
5726                 return pow10 * d;
5727         }
5728 });
5729
5730
5731 // @factory L.control.scale(options?: Control.Scale options)
5732 // Creates an scale control with the given options.
5733 var scale = function (options) {
5734         return new Scale(options);
5735 };
5736
5737 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
5738 \r
5739 \r
5740 /*\r
5741  * @class Control.Attribution\r
5742  * @aka L.Control.Attribution\r
5743  * @inherits Control\r
5744  *\r
5745  * 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
5746  */\r
5747 \r
5748 var Attribution = Control.extend({\r
5749         // @section\r
5750         // @aka Control.Attribution options\r
5751         options: {\r
5752                 position: 'bottomright',\r
5753 \r
5754                 // @option prefix: String|false = 'Leaflet'\r
5755                 // The HTML text shown before the attributions. Pass `false` to disable.\r
5756                 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'\r
5757         },\r
5758 \r
5759         initialize: function (options) {\r
5760                 setOptions(this, options);\r
5761 \r
5762                 this._attributions = {};\r
5763         },\r
5764 \r
5765         onAdd: function (map) {\r
5766                 map.attributionControl = this;\r
5767                 this._container = create$1('div', 'leaflet-control-attribution');\r
5768                 disableClickPropagation(this._container);\r
5769 \r
5770                 // TODO ugly, refactor\r
5771                 for (var i in map._layers) {\r
5772                         if (map._layers[i].getAttribution) {\r
5773                                 this.addAttribution(map._layers[i].getAttribution());\r
5774                         }\r
5775                 }\r
5776 \r
5777                 this._update();\r
5778 \r
5779                 map.on('layeradd', this._addAttribution, this);\r
5780 \r
5781                 return this._container;\r
5782         },\r
5783 \r
5784         onRemove: function (map) {\r
5785                 map.off('layeradd', this._addAttribution, this);\r
5786         },\r
5787 \r
5788         _addAttribution: function (ev) {\r
5789                 if (ev.layer.getAttribution) {\r
5790                         this.addAttribution(ev.layer.getAttribution());\r
5791                         ev.layer.once('remove', function () {\r
5792                                 this.removeAttribution(ev.layer.getAttribution());\r
5793                         }, this);\r
5794                 }\r
5795         },\r
5796 \r
5797         // @method setPrefix(prefix: String|false): this\r
5798         // The HTML text shown before the attributions. Pass `false` to disable.\r
5799         setPrefix: function (prefix) {\r
5800                 this.options.prefix = prefix;\r
5801                 this._update();\r
5802                 return this;\r
5803         },\r
5804 \r
5805         // @method addAttribution(text: String): this\r
5806         // Adds an attribution text (e.g. `'&copy; OpenStreetMap contributors'`).\r
5807         addAttribution: function (text) {\r
5808                 if (!text) { return this; }\r
5809 \r
5810                 if (!this._attributions[text]) {\r
5811                         this._attributions[text] = 0;\r
5812                 }\r
5813                 this._attributions[text]++;\r
5814 \r
5815                 this._update();\r
5816 \r
5817                 return this;\r
5818         },\r
5819 \r
5820         // @method removeAttribution(text: String): this\r
5821         // Removes an attribution text.\r
5822         removeAttribution: function (text) {\r
5823                 if (!text) { return this; }\r
5824 \r
5825                 if (this._attributions[text]) {\r
5826                         this._attributions[text]--;\r
5827                         this._update();\r
5828                 }\r
5829 \r
5830                 return this;\r
5831         },\r
5832 \r
5833         _update: function () {\r
5834                 if (!this._map) { return; }\r
5835 \r
5836                 var attribs = [];\r
5837 \r
5838                 for (var i in this._attributions) {\r
5839                         if (this._attributions[i]) {\r
5840                                 attribs.push(i);\r
5841                         }\r
5842                 }\r
5843 \r
5844                 var prefixAndAttribs = [];\r
5845 \r
5846                 if (this.options.prefix) {\r
5847                         prefixAndAttribs.push(this.options.prefix);\r
5848                 }\r
5849                 if (attribs.length) {\r
5850                         prefixAndAttribs.push(attribs.join(', '));\r
5851                 }\r
5852 \r
5853                 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');\r
5854         }\r
5855 });\r
5856 \r
5857 // @namespace Map\r
5858 // @section Control options\r
5859 // @option attributionControl: Boolean = true\r
5860 // Whether a [attribution control](#control-attribution) is added to the map by default.\r
5861 Map.mergeOptions({\r
5862         attributionControl: true\r
5863 });\r
5864 \r
5865 Map.addInitHook(function () {\r
5866         if (this.options.attributionControl) {\r
5867                 new Attribution().addTo(this);\r
5868         }\r
5869 });\r
5870 \r
5871 // @namespace Control.Attribution\r
5872 // @factory L.control.attribution(options: Control.Attribution options)\r
5873 // Creates an attribution control.\r
5874 var attribution = function (options) {\r
5875         return new Attribution(options);\r
5876 };
5877
5878 Control.Layers = Layers;
5879 Control.Zoom = Zoom;
5880 Control.Scale = Scale;
5881 Control.Attribution = Attribution;
5882
5883 control.layers = layers;
5884 control.zoom = zoom;
5885 control.scale = scale;
5886 control.attribution = attribution;
5887
5888 /*
5889         L.Handler is a base class for handler classes that are used internally to inject
5890         interaction features like dragging to classes like Map and Marker.
5891 */
5892
5893 // @class Handler
5894 // @aka L.Handler
5895 // Abstract class for map interaction handlers
5896
5897 var Handler = Class.extend({
5898         initialize: function (map) {
5899                 this._map = map;
5900         },
5901
5902         // @method enable(): this
5903         // Enables the handler
5904         enable: function () {
5905                 if (this._enabled) { return this; }
5906
5907                 this._enabled = true;
5908                 this.addHooks();
5909                 return this;
5910         },
5911
5912         // @method disable(): this
5913         // Disables the handler
5914         disable: function () {
5915                 if (!this._enabled) { return this; }
5916
5917                 this._enabled = false;
5918                 this.removeHooks();
5919                 return this;
5920         },
5921
5922         // @method enabled(): Boolean
5923         // Returns `true` if the handler is enabled
5924         enabled: function () {
5925                 return !!this._enabled;
5926         }
5927
5928         // @section Extension methods
5929         // Classes inheriting from `Handler` must implement the two following methods:
5930         // @method addHooks()
5931         // Called when the handler is enabled, should add event hooks.
5932         // @method removeHooks()
5933         // Called when the handler is disabled, should remove the event hooks added previously.
5934 });
5935
5936 // @section There is static function which can be called without instantiating L.Handler:
5937 // @function addTo(map: Map, name: String): this
5938 // Adds a new Handler to the given map with the given name.
5939 Handler.addTo = function (map, name) {
5940         map.addHandler(name, this);
5941         return this;
5942 };
5943
5944 var Mixin = {Events: Events};
5945
5946 /*\r
5947  * @class Draggable\r
5948  * @aka L.Draggable\r
5949  * @inherits Evented\r
5950  *\r
5951  * A class for making DOM elements draggable (including touch support).\r
5952  * Used internally for map and marker dragging. Only works for elements\r
5953  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).\r
5954  *\r
5955  * @example\r
5956  * ```js\r
5957  * var draggable = new L.Draggable(elementToDrag);\r
5958  * draggable.enable();\r
5959  * ```\r
5960  */\r
5961 \r
5962 var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';\r
5963 \r
5964 var Draggable = Evented.extend({\r
5965 \r
5966         options: {\r
5967                 // @section\r
5968                 // @aka Draggable options\r
5969                 // @option clickTolerance: Number = 3\r
5970                 // The max number of pixels a user can shift the mouse pointer during a click\r
5971                 // for it to be considered a valid click (as opposed to a mouse drag).\r
5972                 clickTolerance: 3\r
5973         },\r
5974 \r
5975         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)\r
5976         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).\r
5977         initialize: function (element, dragStartTarget, preventOutline, options) {\r
5978                 setOptions(this, options);\r
5979 \r
5980                 this._element = element;\r
5981                 this._dragStartTarget = dragStartTarget || element;\r
5982                 this._preventOutline = preventOutline;\r
5983         },\r
5984 \r
5985         // @method enable()\r
5986         // Enables the dragging ability\r
5987         enable: function () {\r
5988                 if (this._enabled) { return; }\r
5989 \r
5990                 on(this._dragStartTarget, START, this._onDown, this);\r
5991 \r
5992                 this._enabled = true;\r
5993         },\r
5994 \r
5995         // @method disable()\r
5996         // Disables the dragging ability\r
5997         disable: function () {\r
5998                 if (!this._enabled) { return; }\r
5999 \r
6000                 // If we're currently dragging this draggable,\r
6001                 // disabling it counts as first ending the drag.\r
6002                 if (Draggable._dragging === this) {\r
6003                         this.finishDrag(true);\r
6004                 }\r
6005 \r
6006                 off(this._dragStartTarget, START, this._onDown, this);\r
6007 \r
6008                 this._enabled = false;\r
6009                 this._moved = false;\r
6010         },\r
6011 \r
6012         _onDown: function (e) {\r
6013                 // Ignore the event if disabled; this happens in IE11\r
6014                 // under some circumstances, see #3666.\r
6015                 if (!this._enabled) { return; }\r
6016 \r
6017                 this._moved = false;\r
6018 \r
6019                 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }\r
6020 \r
6021                 if (e.touches && e.touches.length !== 1) {\r
6022                         // Finish dragging to avoid conflict with touchZoom\r
6023                         if (Draggable._dragging === this) {\r
6024                                 this.finishDrag();\r
6025                         }\r
6026                         return;\r
6027                 }\r
6028 \r
6029                 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }\r
6030                 Draggable._dragging = this;  // Prevent dragging multiple objects at once.\r
6031 \r
6032                 if (this._preventOutline) {\r
6033                         preventOutline(this._element);\r
6034                 }\r
6035 \r
6036                 disableImageDrag();\r
6037                 disableTextSelection();\r
6038 \r
6039                 if (this._moving) { return; }\r
6040 \r
6041                 // @event down: Event\r
6042                 // Fired when a drag is about to start.\r
6043                 this.fire('down');\r
6044 \r
6045                 var first = e.touches ? e.touches[0] : e,\r
6046                     sizedParent = getSizedParentNode(this._element);\r
6047 \r
6048                 this._startPoint = new Point(first.clientX, first.clientY);\r
6049                 this._startPos = getPosition(this._element);\r
6050 \r
6051                 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).\r
6052                 this._parentScale = getScale(sizedParent);\r
6053 \r
6054                 var mouseevent = e.type === 'mousedown';\r
6055                 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);\r
6056                 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);\r
6057         },\r
6058 \r
6059         _onMove: function (e) {\r
6060                 // Ignore the event if disabled; this happens in IE11\r
6061                 // under some circumstances, see #3666.\r
6062                 if (!this._enabled) { return; }\r
6063 \r
6064                 if (e.touches && e.touches.length > 1) {\r
6065                         this._moved = true;\r
6066                         return;\r
6067                 }\r
6068 \r
6069                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),\r
6070                     offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);\r
6071 \r
6072                 if (!offset.x && !offset.y) { return; }\r
6073                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }\r
6074 \r
6075                 // We assume that the parent container's position, border and scale do not change for the duration of the drag.\r
6076                 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)\r
6077                 // and we can use the cached value for the scale.\r
6078                 offset.x /= this._parentScale.x;\r
6079                 offset.y /= this._parentScale.y;\r
6080 \r
6081                 preventDefault(e);\r
6082 \r
6083                 if (!this._moved) {\r
6084                         // @event dragstart: Event\r
6085                         // Fired when a drag starts\r
6086                         this.fire('dragstart');\r
6087 \r
6088                         this._moved = true;\r
6089 \r
6090                         addClass(document.body, 'leaflet-dragging');\r
6091 \r
6092                         this._lastTarget = e.target || e.srcElement;\r
6093                         // IE and Edge do not give the <use> element, so fetch it\r
6094                         // if necessary\r
6095                         if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {\r
6096                                 this._lastTarget = this._lastTarget.correspondingUseElement;\r
6097                         }\r
6098                         addClass(this._lastTarget, 'leaflet-drag-target');\r
6099                 }\r
6100 \r
6101                 this._newPos = this._startPos.add(offset);\r
6102                 this._moving = true;\r
6103 \r
6104                 this._lastEvent = e;\r
6105                 this._updatePosition();\r
6106         },\r
6107 \r
6108         _updatePosition: function () {\r
6109                 var e = {originalEvent: this._lastEvent};\r
6110 \r
6111                 // @event predrag: Event\r
6112                 // Fired continuously during dragging *before* each corresponding\r
6113                 // update of the element's position.\r
6114                 this.fire('predrag', e);\r
6115                 setPosition(this._element, this._newPos);\r
6116 \r
6117                 // @event drag: Event\r
6118                 // Fired continuously during dragging.\r
6119                 this.fire('drag', e);\r
6120         },\r
6121 \r
6122         _onUp: function () {\r
6123                 // Ignore the event if disabled; this happens in IE11\r
6124                 // under some circumstances, see #3666.\r
6125                 if (!this._enabled) { return; }\r
6126                 this.finishDrag();\r
6127         },\r
6128 \r
6129         finishDrag: function (noInertia) {\r
6130                 removeClass(document.body, 'leaflet-dragging');\r
6131 \r
6132                 if (this._lastTarget) {\r
6133                         removeClass(this._lastTarget, 'leaflet-drag-target');\r
6134                         this._lastTarget = null;\r
6135                 }\r
6136 \r
6137                 off(document, 'mousemove touchmove', this._onMove, this);\r
6138                 off(document, 'mouseup touchend touchcancel', this._onUp, this);\r
6139 \r
6140                 enableImageDrag();\r
6141                 enableTextSelection();\r
6142 \r
6143                 var fireDragend = this._moved && this._moving;\r
6144 \r
6145                 this._moving = false;\r
6146                 Draggable._dragging = false;\r
6147 \r
6148                 if (fireDragend) {\r
6149                         // @event dragend: DragEndEvent\r
6150                         // Fired when the drag ends.\r
6151                         this.fire('dragend', {\r
6152                                 noInertia: noInertia,\r
6153                                 distance: this._newPos.distanceTo(this._startPos)\r
6154                         });\r
6155                 }\r
6156         }\r
6157 \r
6158 });
6159
6160 /*\r
6161  * @namespace PolyUtil\r
6162  * Various utility functions for polygon geometries.\r
6163  */\r
6164 \r
6165 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]\r
6166  * 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
6167  * Used by Leaflet to only show polygon points that are on the screen or near, increasing\r
6168  * performance. Note that polygon points needs different algorithm for clipping\r
6169  * than polyline, so there's a separate method for it.\r
6170  */\r
6171 function clipPolygon(points, bounds, round) {\r
6172         var clippedPoints,\r
6173             edges = [1, 4, 2, 8],\r
6174             i, j, k,\r
6175             a, b,\r
6176             len, edge, p;\r
6177 \r
6178         for (i = 0, len = points.length; i < len; i++) {\r
6179                 points[i]._code = _getBitCode(points[i], bounds);\r
6180         }\r
6181 \r
6182         // for each edge (left, bottom, right, top)\r
6183         for (k = 0; k < 4; k++) {\r
6184                 edge = edges[k];\r
6185                 clippedPoints = [];\r
6186 \r
6187                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {\r
6188                         a = points[i];\r
6189                         b = points[j];\r
6190 \r
6191                         // if a is inside the clip window\r
6192                         if (!(a._code & edge)) {\r
6193                                 // if b is outside the clip window (a->b goes out of screen)\r
6194                                 if (b._code & edge) {\r
6195                                         p = _getEdgeIntersection(b, a, edge, bounds, round);\r
6196                                         p._code = _getBitCode(p, bounds);\r
6197                                         clippedPoints.push(p);\r
6198                                 }\r
6199                                 clippedPoints.push(a);\r
6200 \r
6201                         // else if b is inside the clip window (a->b enters the screen)\r
6202                         } else if (!(b._code & edge)) {\r
6203                                 p = _getEdgeIntersection(b, a, edge, bounds, round);\r
6204                                 p._code = _getBitCode(p, bounds);\r
6205                                 clippedPoints.push(p);\r
6206                         }\r
6207                 }\r
6208                 points = clippedPoints;\r
6209         }\r
6210 \r
6211         return points;\r
6212 }\r
6213 \r
6214 /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng\r
6215  * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.\r
6216  */\r
6217 function polygonCenter(latlngs, crs) {\r
6218         var i, j, p1, p2, f, area, x, y, center;\r
6219 \r
6220         if (!latlngs || latlngs.length === 0) {\r
6221                 throw new Error('latlngs not passed');\r
6222         }\r
6223 \r
6224         if (!isFlat(latlngs)) {\r
6225                 console.warn('latlngs are not flat! Only the first ring will be used');\r
6226                 latlngs = latlngs[0];\r
6227         }\r
6228 \r
6229         var centroidLatLng = toLatLng([0, 0]);\r
6230 \r
6231         var bounds = toLatLngBounds(latlngs);\r
6232         var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());\r
6233         // tests showed that below 1700 rounding errors are happening\r
6234         if (areaBounds < 1700) {\r
6235                 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors\r
6236                 centroidLatLng = centroid(latlngs);\r
6237         }\r
6238 \r
6239         var len = latlngs.length;\r
6240         var points = [];\r
6241         for (i = 0; i < len; i++) {\r
6242                 var latlng = toLatLng(latlngs[i]);\r
6243                 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));\r
6244         }\r
6245 \r
6246         area = x = y = 0;\r
6247 \r
6248         // polygon centroid algorithm;\r
6249         for (i = 0, j = len - 1; i < len; j = i++) {\r
6250                 p1 = points[i];\r
6251                 p2 = points[j];\r
6252 \r
6253                 f = p1.y * p2.x - p2.y * p1.x;\r
6254                 x += (p1.x + p2.x) * f;\r
6255                 y += (p1.y + p2.y) * f;\r
6256                 area += f * 3;\r
6257         }\r
6258 \r
6259         if (area === 0) {\r
6260                 // Polygon is so small that all points are on same pixel.\r
6261                 center = points[0];\r
6262         } else {\r
6263                 center = [x / area, y / area];\r
6264         }\r
6265 \r
6266         var latlngCenter = crs.unproject(toPoint(center));\r
6267         return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);\r
6268 }\r
6269 \r
6270 /* @function centroid(latlngs: LatLng[]): LatLng\r
6271  * Returns the 'center of mass' of the passed LatLngs.\r
6272  */\r
6273 function centroid(coords) {\r
6274         var latSum = 0;\r
6275         var lngSum = 0;\r
6276         var len = 0;\r
6277         for (var i = 0; i < coords.length; i++) {\r
6278                 var latlng = toLatLng(coords[i]);\r
6279                 latSum += latlng.lat;\r
6280                 lngSum += latlng.lng;\r
6281                 len++;\r
6282         }\r
6283         return toLatLng([latSum / len, lngSum / len]);\r
6284 }
6285
6286 var PolyUtil = {
6287   __proto__: null,
6288   clipPolygon: clipPolygon,
6289   polygonCenter: polygonCenter,
6290   centroid: centroid
6291 };
6292
6293 /*\r
6294  * @namespace LineUtil\r
6295  *\r
6296  * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.\r
6297  */\r
6298 \r
6299 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.\r
6300 // Improves rendering performance dramatically by lessening the number of points to draw.\r
6301 \r
6302 // @function simplify(points: Point[], tolerance: Number): Point[]\r
6303 // Dramatically reduces the number of points in a polyline while retaining\r
6304 // its shape and returns a new array of simplified points, using the\r
6305 // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).\r
6306 // Used for a huge performance boost when processing/displaying Leaflet polylines for\r
6307 // each zoom level and also reducing visual noise. tolerance affects the amount of\r
6308 // simplification (lesser value means higher quality but slower and with more points).\r
6309 // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).\r
6310 function simplify(points, tolerance) {\r
6311         if (!tolerance || !points.length) {\r
6312                 return points.slice();\r
6313         }\r
6314 \r
6315         var sqTolerance = tolerance * tolerance;\r
6316 \r
6317             // stage 1: vertex reduction\r
6318             points = _reducePoints(points, sqTolerance);\r
6319 \r
6320             // stage 2: Douglas-Peucker simplification\r
6321             points = _simplifyDP(points, sqTolerance);\r
6322 \r
6323         return points;\r
6324 }\r
6325 \r
6326 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number\r
6327 // Returns the distance between point `p` and segment `p1` to `p2`.\r
6328 function pointToSegmentDistance(p, p1, p2) {\r
6329         return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));\r
6330 }\r
6331 \r
6332 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number\r
6333 // Returns the closest point from a point `p` on a segment `p1` to `p2`.\r
6334 function closestPointOnSegment(p, p1, p2) {\r
6335         return _sqClosestPointOnSegment(p, p1, p2);\r
6336 }\r
6337 \r
6338 // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm\r
6339 function _simplifyDP(points, sqTolerance) {\r
6340 \r
6341         var len = points.length,\r
6342             ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,\r
6343             markers = new ArrayConstructor(len);\r
6344 \r
6345             markers[0] = markers[len - 1] = 1;\r
6346 \r
6347         _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);\r
6348 \r
6349         var i,\r
6350             newPoints = [];\r
6351 \r
6352         for (i = 0; i < len; i++) {\r
6353                 if (markers[i]) {\r
6354                         newPoints.push(points[i]);\r
6355                 }\r
6356         }\r
6357 \r
6358         return newPoints;\r
6359 }\r
6360 \r
6361 function _simplifyDPStep(points, markers, sqTolerance, first, last) {\r
6362 \r
6363         var maxSqDist = 0,\r
6364         index, i, sqDist;\r
6365 \r
6366         for (i = first + 1; i <= last - 1; i++) {\r
6367                 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);\r
6368 \r
6369                 if (sqDist > maxSqDist) {\r
6370                         index = i;\r
6371                         maxSqDist = sqDist;\r
6372                 }\r
6373         }\r
6374 \r
6375         if (maxSqDist > sqTolerance) {\r
6376                 markers[index] = 1;\r
6377 \r
6378                 _simplifyDPStep(points, markers, sqTolerance, first, index);\r
6379                 _simplifyDPStep(points, markers, sqTolerance, index, last);\r
6380         }\r
6381 }\r
6382 \r
6383 // reduce points that are too close to each other to a single point\r
6384 function _reducePoints(points, sqTolerance) {\r
6385         var reducedPoints = [points[0]];\r
6386 \r
6387         for (var i = 1, prev = 0, len = points.length; i < len; i++) {\r
6388                 if (_sqDist(points[i], points[prev]) > sqTolerance) {\r
6389                         reducedPoints.push(points[i]);\r
6390                         prev = i;\r
6391                 }\r
6392         }\r
6393         if (prev < len - 1) {\r
6394                 reducedPoints.push(points[len - 1]);\r
6395         }\r
6396         return reducedPoints;\r
6397 }\r
6398 \r
6399 var _lastCode;\r
6400 \r
6401 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean\r
6402 // Clips the segment a to b by rectangular bounds with the\r
6403 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)\r
6404 // (modifying the segment points directly!). Used by Leaflet to only show polyline\r
6405 // points that are on the screen or near, increasing performance.\r
6406 function clipSegment(a, b, bounds, useLastCode, round) {\r
6407         var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),\r
6408             codeB = _getBitCode(b, bounds),\r
6409 \r
6410             codeOut, p, newCode;\r
6411 \r
6412             // save 2nd code to avoid calculating it on the next segment\r
6413             _lastCode = codeB;\r
6414 \r
6415         while (true) {\r
6416                 // if a,b is inside the clip window (trivial accept)\r
6417                 if (!(codeA | codeB)) {\r
6418                         return [a, b];\r
6419                 }\r
6420 \r
6421                 // if a,b is outside the clip window (trivial reject)\r
6422                 if (codeA & codeB) {\r
6423                         return false;\r
6424                 }\r
6425 \r
6426                 // other cases\r
6427                 codeOut = codeA || codeB;\r
6428                 p = _getEdgeIntersection(a, b, codeOut, bounds, round);\r
6429                 newCode = _getBitCode(p, bounds);\r
6430 \r
6431                 if (codeOut === codeA) {\r
6432                         a = p;\r
6433                         codeA = newCode;\r
6434                 } else {\r
6435                         b = p;\r
6436                         codeB = newCode;\r
6437                 }\r
6438         }\r
6439 }\r
6440 \r
6441 function _getEdgeIntersection(a, b, code, bounds, round) {\r
6442         var dx = b.x - a.x,\r
6443             dy = b.y - a.y,\r
6444             min = bounds.min,\r
6445             max = bounds.max,\r
6446             x, y;\r
6447 \r
6448         if (code & 8) { // top\r
6449                 x = a.x + dx * (max.y - a.y) / dy;\r
6450                 y = max.y;\r
6451 \r
6452         } else if (code & 4) { // bottom\r
6453                 x = a.x + dx * (min.y - a.y) / dy;\r
6454                 y = min.y;\r
6455 \r
6456         } else if (code & 2) { // right\r
6457                 x = max.x;\r
6458                 y = a.y + dy * (max.x - a.x) / dx;\r
6459 \r
6460         } else if (code & 1) { // left\r
6461                 x = min.x;\r
6462                 y = a.y + dy * (min.x - a.x) / dx;\r
6463         }\r
6464 \r
6465         return new Point(x, y, round);\r
6466 }\r
6467 \r
6468 function _getBitCode(p, bounds) {\r
6469         var code = 0;\r
6470 \r
6471         if (p.x < bounds.min.x) { // left\r
6472                 code |= 1;\r
6473         } else if (p.x > bounds.max.x) { // right\r
6474                 code |= 2;\r
6475         }\r
6476 \r
6477         if (p.y < bounds.min.y) { // bottom\r
6478                 code |= 4;\r
6479         } else if (p.y > bounds.max.y) { // top\r
6480                 code |= 8;\r
6481         }\r
6482 \r
6483         return code;\r
6484 }\r
6485 \r
6486 // square distance (to avoid unnecessary Math.sqrt calls)\r
6487 function _sqDist(p1, p2) {\r
6488         var dx = p2.x - p1.x,\r
6489             dy = p2.y - p1.y;\r
6490         return dx * dx + dy * dy;\r
6491 }\r
6492 \r
6493 // return closest point on segment or distance to that point\r
6494 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {\r
6495         var x = p1.x,\r
6496             y = p1.y,\r
6497             dx = p2.x - x,\r
6498             dy = p2.y - y,\r
6499             dot = dx * dx + dy * dy,\r
6500             t;\r
6501 \r
6502         if (dot > 0) {\r
6503                 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;\r
6504 \r
6505                 if (t > 1) {\r
6506                         x = p2.x;\r
6507                         y = p2.y;\r
6508                 } else if (t > 0) {\r
6509                         x += dx * t;\r
6510                         y += dy * t;\r
6511                 }\r
6512         }\r
6513 \r
6514         dx = p.x - x;\r
6515         dy = p.y - y;\r
6516 \r
6517         return sqDist ? dx * dx + dy * dy : new Point(x, y);\r
6518 }\r
6519 \r
6520 \r
6521 // @function isFlat(latlngs: LatLng[]): Boolean\r
6522 // Returns true if `latlngs` is a flat array, false is nested.\r
6523 function isFlat(latlngs) {\r
6524         return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');\r
6525 }\r
6526 \r
6527 function _flat(latlngs) {\r
6528         console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');\r
6529         return isFlat(latlngs);\r
6530 }\r
6531 \r
6532 /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng\r
6533  * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.\r
6534  */\r
6535 function polylineCenter(latlngs, crs) {\r
6536         var i, halfDist, segDist, dist, p1, p2, ratio, center;\r
6537 \r
6538         if (!latlngs || latlngs.length === 0) {\r
6539                 throw new Error('latlngs not passed');\r
6540         }\r
6541 \r
6542         if (!isFlat(latlngs)) {\r
6543                 console.warn('latlngs are not flat! Only the first ring will be used');\r
6544                 latlngs = latlngs[0];\r
6545         }\r
6546 \r
6547         var centroidLatLng = toLatLng([0, 0]);\r
6548 \r
6549         var bounds = toLatLngBounds(latlngs);\r
6550         var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());\r
6551         // tests showed that below 1700 rounding errors are happening\r
6552         if (areaBounds < 1700) {\r
6553                 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors\r
6554                 centroidLatLng = centroid(latlngs);\r
6555         }\r
6556 \r
6557         var len = latlngs.length;\r
6558         var points = [];\r
6559         for (i = 0; i < len; i++) {\r
6560                 var latlng = toLatLng(latlngs[i]);\r
6561                 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));\r
6562         }\r
6563 \r
6564         for (i = 0, halfDist = 0; i < len - 1; i++) {\r
6565                 halfDist += points[i].distanceTo(points[i + 1]) / 2;\r
6566         }\r
6567 \r
6568         // The line is so small in the current view that all points are on the same pixel.\r
6569         if (halfDist === 0) {\r
6570                 center = points[0];\r
6571         } else {\r
6572                 for (i = 0, dist = 0; i < len - 1; i++) {\r
6573                         p1 = points[i];\r
6574                         p2 = points[i + 1];\r
6575                         segDist = p1.distanceTo(p2);\r
6576                         dist += segDist;\r
6577 \r
6578                         if (dist > halfDist) {\r
6579                                 ratio = (dist - halfDist) / segDist;\r
6580                                 center = [\r
6581                                         p2.x - ratio * (p2.x - p1.x),\r
6582                                         p2.y - ratio * (p2.y - p1.y)\r
6583                                 ];\r
6584                                 break;\r
6585                         }\r
6586                 }\r
6587         }\r
6588 \r
6589         var latlngCenter = crs.unproject(toPoint(center));\r
6590         return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);\r
6591 }
6592
6593 var LineUtil = {
6594   __proto__: null,
6595   simplify: simplify,
6596   pointToSegmentDistance: pointToSegmentDistance,
6597   closestPointOnSegment: closestPointOnSegment,
6598   clipSegment: clipSegment,
6599   _getEdgeIntersection: _getEdgeIntersection,
6600   _getBitCode: _getBitCode,
6601   _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6602   isFlat: isFlat,
6603   _flat: _flat,
6604   polylineCenter: polylineCenter
6605 };
6606
6607 /*\r
6608  * @namespace Projection\r
6609  * @section\r
6610  * Leaflet comes with a set of already defined Projections out of the box:\r
6611  *\r
6612  * @projection L.Projection.LonLat\r
6613  *\r
6614  * Equirectangular, or Plate Carree projection — the most simple projection,\r
6615  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as\r
6616  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the\r
6617  * `EPSG:4326` and `Simple` CRS.\r
6618  */\r
6619 \r
6620 var LonLat = {\r
6621         project: function (latlng) {\r
6622                 return new Point(latlng.lng, latlng.lat);\r
6623         },\r
6624 \r
6625         unproject: function (point) {\r
6626                 return new LatLng(point.y, point.x);\r
6627         },\r
6628 \r
6629         bounds: new Bounds([-180, -90], [180, 90])\r
6630 };
6631
6632 /*\r
6633  * @namespace Projection\r
6634  * @projection L.Projection.Mercator\r
6635  *\r
6636  * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.\r
6637  */\r
6638 \r
6639 var Mercator = {\r
6640         R: 6378137,\r
6641         R_MINOR: 6356752.314245179,\r
6642 \r
6643         bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),\r
6644 \r
6645         project: function (latlng) {\r
6646                 var d = Math.PI / 180,\r
6647                     r = this.R,\r
6648                     y = latlng.lat * d,\r
6649                     tmp = this.R_MINOR / r,\r
6650                     e = Math.sqrt(1 - tmp * tmp),\r
6651                     con = e * Math.sin(y);\r
6652 \r
6653                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);\r
6654                 y = -r * Math.log(Math.max(ts, 1E-10));\r
6655 \r
6656                 return new Point(latlng.lng * d * r, y);\r
6657         },\r
6658 \r
6659         unproject: function (point) {\r
6660                 var d = 180 / Math.PI,\r
6661                     r = this.R,\r
6662                     tmp = this.R_MINOR / r,\r
6663                     e = Math.sqrt(1 - tmp * tmp),\r
6664                     ts = Math.exp(-point.y / r),\r
6665                     phi = Math.PI / 2 - 2 * Math.atan(ts);\r
6666 \r
6667                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {\r
6668                         con = e * Math.sin(phi);\r
6669                         con = Math.pow((1 - con) / (1 + con), e / 2);\r
6670                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;\r
6671                         phi += dphi;\r
6672                 }\r
6673 \r
6674                 return new LatLng(phi * d, point.x * d / r);\r
6675         }\r
6676 };
6677
6678 /*
6679  * @class Projection
6680
6681  * An object with methods for projecting geographical coordinates of the world onto
6682  * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
6683
6684  * @property bounds: Bounds
6685  * The bounds (specified in CRS units) where the projection is valid
6686
6687  * @method project(latlng: LatLng): Point
6688  * Projects geographical coordinates into a 2D point.
6689  * Only accepts actual `L.LatLng` instances, not arrays.
6690
6691  * @method unproject(point: Point): LatLng
6692  * The inverse of `project`. Projects a 2D point into a geographical location.
6693  * Only accepts actual `L.Point` instances, not arrays.
6694
6695  * Note that the projection instances do not inherit from Leaflet's `Class` object,
6696  * and can't be instantiated. Also, new classes can't inherit from them,
6697  * and methods can't be added to them with the `include` function.
6698
6699  */
6700
6701 var index = {
6702   __proto__: null,
6703   LonLat: LonLat,
6704   Mercator: Mercator,
6705   SphericalMercator: SphericalMercator
6706 };
6707
6708 /*\r
6709  * @namespace CRS\r
6710  * @crs L.CRS.EPSG3395\r
6711  *\r
6712  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.\r
6713  */\r
6714 var EPSG3395 = extend({}, Earth, {\r
6715         code: 'EPSG:3395',\r
6716         projection: Mercator,\r
6717 \r
6718         transformation: (function () {\r
6719                 var scale = 0.5 / (Math.PI * Mercator.R);\r
6720                 return toTransformation(scale, 0.5, -scale, 0.5);\r
6721         }())\r
6722 });
6723
6724 /*\r
6725  * @namespace CRS\r
6726  * @crs L.CRS.EPSG4326\r
6727  *\r
6728  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.\r
6729  *\r
6730  * 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
6731  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`\r
6732  * with this CRS, ensure that there are two 256x256 pixel tiles covering the\r
6733  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),\r
6734  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.\r
6735  */\r
6736 \r
6737 var EPSG4326 = extend({}, Earth, {\r
6738         code: 'EPSG:4326',\r
6739         projection: LonLat,\r
6740         transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)\r
6741 });
6742
6743 /*
6744  * @namespace CRS
6745  * @crs L.CRS.Simple
6746  *
6747  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6748  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6749  * axis should still be inverted (going from bottom to top). `distance()` returns
6750  * simple euclidean distance.
6751  */
6752
6753 var Simple = extend({}, CRS, {
6754         projection: LonLat,
6755         transformation: toTransformation(1, 0, -1, 0),
6756
6757         scale: function (zoom) {
6758                 return Math.pow(2, zoom);
6759         },
6760
6761         zoom: function (scale) {
6762                 return Math.log(scale) / Math.LN2;
6763         },
6764
6765         distance: function (latlng1, latlng2) {
6766                 var dx = latlng2.lng - latlng1.lng,
6767                     dy = latlng2.lat - latlng1.lat;
6768
6769                 return Math.sqrt(dx * dx + dy * dy);
6770         },
6771
6772         infinite: true
6773 });
6774
6775 CRS.Earth = Earth;
6776 CRS.EPSG3395 = EPSG3395;
6777 CRS.EPSG3857 = EPSG3857;
6778 CRS.EPSG900913 = EPSG900913;
6779 CRS.EPSG4326 = EPSG4326;
6780 CRS.Simple = Simple;
6781
6782 /*
6783  * @class Layer
6784  * @inherits Evented
6785  * @aka L.Layer
6786  * @aka ILayer
6787  *
6788  * A set of methods from the Layer base class that all Leaflet layers use.
6789  * Inherits all methods, options and events from `L.Evented`.
6790  *
6791  * @example
6792  *
6793  * ```js
6794  * var layer = L.marker(latlng).addTo(map);
6795  * layer.addTo(map);
6796  * layer.remove();
6797  * ```
6798  *
6799  * @event add: Event
6800  * Fired after the layer is added to a map
6801  *
6802  * @event remove: Event
6803  * Fired after the layer is removed from a map
6804  */
6805
6806
6807 var Layer = Evented.extend({
6808
6809         // Classes extending `L.Layer` will inherit the following options:
6810         options: {
6811                 // @option pane: String = 'overlayPane'
6812                 // 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.
6813                 pane: 'overlayPane',
6814
6815                 // @option attribution: String = null
6816                 // 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.
6817                 attribution: null,
6818
6819                 bubblingMouseEvents: true
6820         },
6821
6822         /* @section
6823          * Classes extending `L.Layer` will inherit the following methods:
6824          *
6825          * @method addTo(map: Map|LayerGroup): this
6826          * Adds the layer to the given map or layer group.
6827          */
6828         addTo: function (map) {
6829                 map.addLayer(this);
6830                 return this;
6831         },
6832
6833         // @method remove: this
6834         // Removes the layer from the map it is currently active on.
6835         remove: function () {
6836                 return this.removeFrom(this._map || this._mapToAdd);
6837         },
6838
6839         // @method removeFrom(map: Map): this
6840         // Removes the layer from the given map
6841         //
6842         // @alternative
6843         // @method removeFrom(group: LayerGroup): this
6844         // Removes the layer from the given `LayerGroup`
6845         removeFrom: function (obj) {
6846                 if (obj) {
6847                         obj.removeLayer(this);
6848                 }
6849                 return this;
6850         },
6851
6852         // @method getPane(name? : String): HTMLElement
6853         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6854         getPane: function (name) {
6855                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6856         },
6857
6858         addInteractiveTarget: function (targetEl) {
6859                 this._map._targets[stamp(targetEl)] = this;
6860                 return this;
6861         },
6862
6863         removeInteractiveTarget: function (targetEl) {
6864                 delete this._map._targets[stamp(targetEl)];
6865                 return this;
6866         },
6867
6868         // @method getAttribution: String
6869         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6870         getAttribution: function () {
6871                 return this.options.attribution;
6872         },
6873
6874         _layerAdd: function (e) {
6875                 var map = e.target;
6876
6877                 // check in case layer gets added and then removed before the map is ready
6878                 if (!map.hasLayer(this)) { return; }
6879
6880                 this._map = map;
6881                 this._zoomAnimated = map._zoomAnimated;
6882
6883                 if (this.getEvents) {
6884                         var events = this.getEvents();
6885                         map.on(events, this);
6886                         this.once('remove', function () {
6887                                 map.off(events, this);
6888                         }, this);
6889                 }
6890
6891                 this.onAdd(map);
6892
6893                 this.fire('add');
6894                 map.fire('layeradd', {layer: this});
6895         }
6896 });
6897
6898 /* @section Extension methods
6899  * @uninheritable
6900  *
6901  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6902  *
6903  * @method onAdd(map: Map): this
6904  * 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).
6905  *
6906  * @method onRemove(map: Map): this
6907  * 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).
6908  *
6909  * @method getEvents(): Object
6910  * 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.
6911  *
6912  * @method getAttribution(): String
6913  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6914  *
6915  * @method beforeAdd(map: Map): this
6916  * 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.
6917  */
6918
6919
6920 /* @namespace Map
6921  * @section Layer events
6922  *
6923  * @event layeradd: LayerEvent
6924  * Fired when a new layer is added to the map.
6925  *
6926  * @event layerremove: LayerEvent
6927  * Fired when some layer is removed from the map
6928  *
6929  * @section Methods for Layers and Controls
6930  */
6931 Map.include({
6932         // @method addLayer(layer: Layer): this
6933         // Adds the given layer to the map
6934         addLayer: function (layer) {
6935                 if (!layer._layerAdd) {
6936                         throw new Error('The provided object is not a Layer.');
6937                 }
6938
6939                 var id = stamp(layer);
6940                 if (this._layers[id]) { return this; }
6941                 this._layers[id] = layer;
6942
6943                 layer._mapToAdd = this;
6944
6945                 if (layer.beforeAdd) {
6946                         layer.beforeAdd(this);
6947                 }
6948
6949                 this.whenReady(layer._layerAdd, layer);
6950
6951                 return this;
6952         },
6953
6954         // @method removeLayer(layer: Layer): this
6955         // Removes the given layer from the map.
6956         removeLayer: function (layer) {
6957                 var id = stamp(layer);
6958
6959                 if (!this._layers[id]) { return this; }
6960
6961                 if (this._loaded) {
6962                         layer.onRemove(this);
6963                 }
6964
6965                 delete this._layers[id];
6966
6967                 if (this._loaded) {
6968                         this.fire('layerremove', {layer: layer});
6969                         layer.fire('remove');
6970                 }
6971
6972                 layer._map = layer._mapToAdd = null;
6973
6974                 return this;
6975         },
6976
6977         // @method hasLayer(layer: Layer): Boolean
6978         // Returns `true` if the given layer is currently added to the map
6979         hasLayer: function (layer) {
6980                 return stamp(layer) in this._layers;
6981         },
6982
6983         /* @method eachLayer(fn: Function, context?: Object): this
6984          * Iterates over the layers of the map, optionally specifying context of the iterator function.
6985          * ```
6986          * map.eachLayer(function(layer){
6987          *     layer.bindPopup('Hello');
6988          * });
6989          * ```
6990          */
6991         eachLayer: function (method, context) {
6992                 for (var i in this._layers) {
6993                         method.call(context, this._layers[i]);
6994                 }
6995                 return this;
6996         },
6997
6998         _addLayers: function (layers) {
6999                 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
7000
7001                 for (var i = 0, len = layers.length; i < len; i++) {
7002                         this.addLayer(layers[i]);
7003                 }
7004         },
7005
7006         _addZoomLimit: function (layer) {
7007                 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
7008                         this._zoomBoundLayers[stamp(layer)] = layer;
7009                         this._updateZoomLevels();
7010                 }
7011         },
7012
7013         _removeZoomLimit: function (layer) {
7014                 var id = stamp(layer);
7015
7016                 if (this._zoomBoundLayers[id]) {
7017                         delete this._zoomBoundLayers[id];
7018                         this._updateZoomLevels();
7019                 }
7020         },
7021
7022         _updateZoomLevels: function () {
7023                 var minZoom = Infinity,
7024                     maxZoom = -Infinity,
7025                     oldZoomSpan = this._getZoomSpan();
7026
7027                 for (var i in this._zoomBoundLayers) {
7028                         var options = this._zoomBoundLayers[i].options;
7029
7030                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
7031                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
7032                 }
7033
7034                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
7035                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
7036
7037                 // @section Map state change events
7038                 // @event zoomlevelschange: Event
7039                 // Fired when the number of zoomlevels on the map is changed due
7040                 // to adding or removing a layer.
7041                 if (oldZoomSpan !== this._getZoomSpan()) {
7042                         this.fire('zoomlevelschange');
7043                 }
7044
7045                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
7046                         this.setZoom(this._layersMaxZoom);
7047                 }
7048                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
7049                         this.setZoom(this._layersMinZoom);
7050                 }
7051         }
7052 });
7053
7054 /*\r
7055  * @class LayerGroup\r
7056  * @aka L.LayerGroup\r
7057  * @inherits Interactive layer\r
7058  *\r
7059  * Used to group several layers and handle them as one. If you add it to the map,\r
7060  * any layers added or removed from the group will be added/removed on the map as\r
7061  * well. Extends `Layer`.\r
7062  *\r
7063  * @example\r
7064  *\r
7065  * ```js\r
7066  * L.layerGroup([marker1, marker2])\r
7067  *      .addLayer(polyline)\r
7068  *      .addTo(map);\r
7069  * ```\r
7070  */\r
7071 \r
7072 var LayerGroup = Layer.extend({\r
7073 \r
7074         initialize: function (layers, options) {\r
7075                 setOptions(this, options);\r
7076 \r
7077                 this._layers = {};\r
7078 \r
7079                 var i, len;\r
7080 \r
7081                 if (layers) {\r
7082                         for (i = 0, len = layers.length; i < len; i++) {\r
7083                                 this.addLayer(layers[i]);\r
7084                         }\r
7085                 }\r
7086         },\r
7087 \r
7088         // @method addLayer(layer: Layer): this\r
7089         // Adds the given layer to the group.\r
7090         addLayer: function (layer) {\r
7091                 var id = this.getLayerId(layer);\r
7092 \r
7093                 this._layers[id] = layer;\r
7094 \r
7095                 if (this._map) {\r
7096                         this._map.addLayer(layer);\r
7097                 }\r
7098 \r
7099                 return this;\r
7100         },\r
7101 \r
7102         // @method removeLayer(layer: Layer): this\r
7103         // Removes the given layer from the group.\r
7104         // @alternative\r
7105         // @method removeLayer(id: Number): this\r
7106         // Removes the layer with the given internal ID from the group.\r
7107         removeLayer: function (layer) {\r
7108                 var id = layer in this._layers ? layer : this.getLayerId(layer);\r
7109 \r
7110                 if (this._map && this._layers[id]) {\r
7111                         this._map.removeLayer(this._layers[id]);\r
7112                 }\r
7113 \r
7114                 delete this._layers[id];\r
7115 \r
7116                 return this;\r
7117         },\r
7118 \r
7119         // @method hasLayer(layer: Layer): Boolean\r
7120         // Returns `true` if the given layer is currently added to the group.\r
7121         // @alternative\r
7122         // @method hasLayer(id: Number): Boolean\r
7123         // Returns `true` if the given internal ID is currently added to the group.\r
7124         hasLayer: function (layer) {\r
7125                 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);\r
7126                 return layerId in this._layers;\r
7127         },\r
7128 \r
7129         // @method clearLayers(): this\r
7130         // Removes all the layers from the group.\r
7131         clearLayers: function () {\r
7132                 return this.eachLayer(this.removeLayer, this);\r
7133         },\r
7134 \r
7135         // @method invoke(methodName: String, …): this\r
7136         // Calls `methodName` on every layer contained in this group, passing any\r
7137         // additional parameters. Has no effect if the layers contained do not\r
7138         // implement `methodName`.\r
7139         invoke: function (methodName) {\r
7140                 var args = Array.prototype.slice.call(arguments, 1),\r
7141                     i, layer;\r
7142 \r
7143                 for (i in this._layers) {\r
7144                         layer = this._layers[i];\r
7145 \r
7146                         if (layer[methodName]) {\r
7147                                 layer[methodName].apply(layer, args);\r
7148                         }\r
7149                 }\r
7150 \r
7151                 return this;\r
7152         },\r
7153 \r
7154         onAdd: function (map) {\r
7155                 this.eachLayer(map.addLayer, map);\r
7156         },\r
7157 \r
7158         onRemove: function (map) {\r
7159                 this.eachLayer(map.removeLayer, map);\r
7160         },\r
7161 \r
7162         // @method eachLayer(fn: Function, context?: Object): this\r
7163         // Iterates over the layers of the group, optionally specifying context of the iterator function.\r
7164         // ```js\r
7165         // group.eachLayer(function (layer) {\r
7166         //      layer.bindPopup('Hello');\r
7167         // });\r
7168         // ```\r
7169         eachLayer: function (method, context) {\r
7170                 for (var i in this._layers) {\r
7171                         method.call(context, this._layers[i]);\r
7172                 }\r
7173                 return this;\r
7174         },\r
7175 \r
7176         // @method getLayer(id: Number): Layer\r
7177         // Returns the layer with the given internal ID.\r
7178         getLayer: function (id) {\r
7179                 return this._layers[id];\r
7180         },\r
7181 \r
7182         // @method getLayers(): Layer[]\r
7183         // Returns an array of all the layers added to the group.\r
7184         getLayers: function () {\r
7185                 var layers = [];\r
7186                 this.eachLayer(layers.push, layers);\r
7187                 return layers;\r
7188         },\r
7189 \r
7190         // @method setZIndex(zIndex: Number): this\r
7191         // Calls `setZIndex` on every layer contained in this group, passing the z-index.\r
7192         setZIndex: function (zIndex) {\r
7193                 return this.invoke('setZIndex', zIndex);\r
7194         },\r
7195 \r
7196         // @method getLayerId(layer: Layer): Number\r
7197         // Returns the internal ID for a layer\r
7198         getLayerId: function (layer) {\r
7199                 return stamp(layer);\r
7200         }\r
7201 });\r
7202 \r
7203 \r
7204 // @factory L.layerGroup(layers?: Layer[], options?: Object)\r
7205 // Create a layer group, optionally given an initial set of layers and an `options` object.\r
7206 var layerGroup = function (layers, options) {\r
7207         return new LayerGroup(layers, options);\r
7208 };
7209
7210 /*\r
7211  * @class FeatureGroup\r
7212  * @aka L.FeatureGroup\r
7213  * @inherits LayerGroup\r
7214  *\r
7215  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:\r
7216  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))\r
7217  *  * Events are propagated to the `FeatureGroup`, so if the group has an event\r
7218  * handler, it will handle events from any of the layers. This includes mouse events\r
7219  * and custom events.\r
7220  *  * Has `layeradd` and `layerremove` events\r
7221  *\r
7222  * @example\r
7223  *\r
7224  * ```js\r
7225  * L.featureGroup([marker1, marker2, polyline])\r
7226  *      .bindPopup('Hello world!')\r
7227  *      .on('click', function() { alert('Clicked on a member of the group!'); })\r
7228  *      .addTo(map);\r
7229  * ```\r
7230  */\r
7231 \r
7232 var FeatureGroup = LayerGroup.extend({\r
7233 \r
7234         addLayer: function (layer) {\r
7235                 if (this.hasLayer(layer)) {\r
7236                         return this;\r
7237                 }\r
7238 \r
7239                 layer.addEventParent(this);\r
7240 \r
7241                 LayerGroup.prototype.addLayer.call(this, layer);\r
7242 \r
7243                 // @event layeradd: LayerEvent\r
7244                 // Fired when a layer is added to this `FeatureGroup`\r
7245                 return this.fire('layeradd', {layer: layer});\r
7246         },\r
7247 \r
7248         removeLayer: function (layer) {\r
7249                 if (!this.hasLayer(layer)) {\r
7250                         return this;\r
7251                 }\r
7252                 if (layer in this._layers) {\r
7253                         layer = this._layers[layer];\r
7254                 }\r
7255 \r
7256                 layer.removeEventParent(this);\r
7257 \r
7258                 LayerGroup.prototype.removeLayer.call(this, layer);\r
7259 \r
7260                 // @event layerremove: LayerEvent\r
7261                 // Fired when a layer is removed from this `FeatureGroup`\r
7262                 return this.fire('layerremove', {layer: layer});\r
7263         },\r
7264 \r
7265         // @method setStyle(style: Path options): this\r
7266         // Sets the given path options to each layer of the group that has a `setStyle` method.\r
7267         setStyle: function (style) {\r
7268                 return this.invoke('setStyle', style);\r
7269         },\r
7270 \r
7271         // @method bringToFront(): this\r
7272         // Brings the layer group to the top of all other layers\r
7273         bringToFront: function () {\r
7274                 return this.invoke('bringToFront');\r
7275         },\r
7276 \r
7277         // @method bringToBack(): this\r
7278         // Brings the layer group to the back of all other layers\r
7279         bringToBack: function () {\r
7280                 return this.invoke('bringToBack');\r
7281         },\r
7282 \r
7283         // @method getBounds(): LatLngBounds\r
7284         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).\r
7285         getBounds: function () {\r
7286                 var bounds = new LatLngBounds();\r
7287 \r
7288                 for (var id in this._layers) {\r
7289                         var layer = this._layers[id];\r
7290                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());\r
7291                 }\r
7292                 return bounds;\r
7293         }\r
7294 });\r
7295 \r
7296 // @factory L.featureGroup(layers?: Layer[], options?: Object)\r
7297 // Create a feature group, optionally given an initial set of layers and an `options` object.\r
7298 var featureGroup = function (layers, options) {\r
7299         return new FeatureGroup(layers, options);\r
7300 };
7301
7302 /*\r
7303  * @class Icon\r
7304  * @aka L.Icon\r
7305  *\r
7306  * Represents an icon to provide when creating a marker.\r
7307  *\r
7308  * @example\r
7309  *\r
7310  * ```js\r
7311  * var myIcon = L.icon({\r
7312  *     iconUrl: 'my-icon.png',\r
7313  *     iconRetinaUrl: 'my-icon@2x.png',\r
7314  *     iconSize: [38, 95],\r
7315  *     iconAnchor: [22, 94],\r
7316  *     popupAnchor: [-3, -76],\r
7317  *     shadowUrl: 'my-icon-shadow.png',\r
7318  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',\r
7319  *     shadowSize: [68, 95],\r
7320  *     shadowAnchor: [22, 94]\r
7321  * });\r
7322  *\r
7323  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);\r
7324  * ```\r
7325  *\r
7326  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.\r
7327  *\r
7328  */\r
7329 \r
7330 var Icon = Class.extend({\r
7331 \r
7332         /* @section\r
7333          * @aka Icon options\r
7334          *\r
7335          * @option iconUrl: String = null\r
7336          * **(required)** The URL to the icon image (absolute or relative to your script path).\r
7337          *\r
7338          * @option iconRetinaUrl: String = null\r
7339          * The URL to a retina sized version of the icon image (absolute or relative to your\r
7340          * script path). Used for Retina screen devices.\r
7341          *\r
7342          * @option iconSize: Point = null\r
7343          * Size of the icon image in pixels.\r
7344          *\r
7345          * @option iconAnchor: Point = null\r
7346          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon\r
7347          * will be aligned so that this point is at the marker's geographical location. Centered\r
7348          * by default if size is specified, also can be set in CSS with negative margins.\r
7349          *\r
7350          * @option popupAnchor: Point = [0, 0]\r
7351          * The coordinates of the point from which popups will "open", relative to the icon anchor.\r
7352          *\r
7353          * @option tooltipAnchor: Point = [0, 0]\r
7354          * The coordinates of the point from which tooltips will "open", relative to the icon anchor.\r
7355          *\r
7356          * @option shadowUrl: String = null\r
7357          * The URL to the icon shadow image. If not specified, no shadow image will be created.\r
7358          *\r
7359          * @option shadowRetinaUrl: String = null\r
7360          *\r
7361          * @option shadowSize: Point = null\r
7362          * Size of the shadow image in pixels.\r
7363          *\r
7364          * @option shadowAnchor: Point = null\r
7365          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same\r
7366          * as iconAnchor if not specified).\r
7367          *\r
7368          * @option className: String = ''\r
7369          * A custom class name to assign to both icon and shadow images. Empty by default.\r
7370          */\r
7371 \r
7372         options: {\r
7373                 popupAnchor: [0, 0],\r
7374                 tooltipAnchor: [0, 0],\r
7375 \r
7376                 // @option crossOrigin: Boolean|String = false\r
7377                 // Whether the crossOrigin attribute will be added to the tiles.\r
7378                 // 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
7379                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.\r
7380                 crossOrigin: false\r
7381         },\r
7382 \r
7383         initialize: function (options) {\r
7384                 setOptions(this, options);\r
7385         },\r
7386 \r
7387         // @method createIcon(oldIcon?: HTMLElement): HTMLElement\r
7388         // Called internally when the icon has to be shown, returns a `<img>` HTML element\r
7389         // styled according to the options.\r
7390         createIcon: function (oldIcon) {\r
7391                 return this._createIcon('icon', oldIcon);\r
7392         },\r
7393 \r
7394         // @method createShadow(oldIcon?: HTMLElement): HTMLElement\r
7395         // As `createIcon`, but for the shadow beneath it.\r
7396         createShadow: function (oldIcon) {\r
7397                 return this._createIcon('shadow', oldIcon);\r
7398         },\r
7399 \r
7400         _createIcon: function (name, oldIcon) {\r
7401                 var src = this._getIconUrl(name);\r
7402 \r
7403                 if (!src) {\r
7404                         if (name === 'icon') {\r
7405                                 throw new Error('iconUrl not set in Icon options (see the docs).');\r
7406                         }\r
7407                         return null;\r
7408                 }\r
7409 \r
7410                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);\r
7411                 this._setIconStyles(img, name);\r
7412 \r
7413                 if (this.options.crossOrigin || this.options.crossOrigin === '') {\r
7414                         img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;\r
7415                 }\r
7416 \r
7417                 return img;\r
7418         },\r
7419 \r
7420         _setIconStyles: function (img, name) {\r
7421                 var options = this.options;\r
7422                 var sizeOption = options[name + 'Size'];\r
7423 \r
7424                 if (typeof sizeOption === 'number') {\r
7425                         sizeOption = [sizeOption, sizeOption];\r
7426                 }\r
7427 \r
7428                 var size = toPoint(sizeOption),\r
7429                     anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||\r
7430                             size && size.divideBy(2, true));\r
7431 \r
7432                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');\r
7433 \r
7434                 if (anchor) {\r
7435                         img.style.marginLeft = (-anchor.x) + 'px';\r
7436                         img.style.marginTop  = (-anchor.y) + 'px';\r
7437                 }\r
7438 \r
7439                 if (size) {\r
7440                         img.style.width  = size.x + 'px';\r
7441                         img.style.height = size.y + 'px';\r
7442                 }\r
7443         },\r
7444 \r
7445         _createImg: function (src, el) {\r
7446                 el = el || document.createElement('img');\r
7447                 el.src = src;\r
7448                 return el;\r
7449         },\r
7450 \r
7451         _getIconUrl: function (name) {\r
7452                 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];\r
7453         }\r
7454 });\r
7455 \r
7456 \r
7457 // @factory L.icon(options: Icon options)\r
7458 // Creates an icon instance with the given options.\r
7459 function icon(options) {\r
7460         return new Icon(options);\r
7461 }
7462
7463 /*
7464  * @miniclass Icon.Default (Icon)
7465  * @aka L.Icon.Default
7466  * @section
7467  *
7468  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7469  * no icon is specified. Points to the blue marker image distributed with Leaflet
7470  * releases.
7471  *
7472  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7473  * (which is a set of `Icon options`).
7474  *
7475  * If you want to _completely_ replace the default icon, override the
7476  * `L.Marker.prototype.options.icon` with your own icon instead.
7477  */
7478
7479 var IconDefault = Icon.extend({
7480
7481         options: {
7482                 iconUrl:       'marker-icon.png',
7483                 iconRetinaUrl: 'marker-icon-2x.png',
7484                 shadowUrl:     'marker-shadow.png',
7485                 iconSize:    [25, 41],
7486                 iconAnchor:  [12, 41],
7487                 popupAnchor: [1, -34],
7488                 tooltipAnchor: [16, -28],
7489                 shadowSize:  [41, 41]
7490         },
7491
7492         _getIconUrl: function (name) {
7493                 if (typeof IconDefault.imagePath !== 'string') {        // Deprecated, backwards-compatibility only
7494                         IconDefault.imagePath = this._detectIconPath();
7495                 }
7496
7497                 // @option imagePath: String
7498                 // `Icon.Default` will try to auto-detect the location of the
7499                 // blue icon images. If you are placing these images in a non-standard
7500                 // way, set this option to point to the right path.
7501                 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7502         },
7503
7504         _stripUrl: function (path) {    // separate function to use in tests
7505                 var strip = function (str, re, idx) {
7506                         var match = re.exec(str);
7507                         return match && match[idx];
7508                 };
7509                 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
7510                 return path && strip(path, /^(.*)marker-icon\.png$/, 1);
7511         },
7512
7513         _detectIconPath: function () {
7514                 var el = create$1('div',  'leaflet-default-icon-path', document.body);
7515                 var path = getStyle(el, 'background-image') ||
7516                            getStyle(el, 'backgroundImage');     // IE8
7517
7518                 document.body.removeChild(el);
7519                 path = this._stripUrl(path);
7520                 if (path) { return path; }
7521                 var link = document.querySelector('link[href$="leaflet.css"]');
7522                 if (!link) { return ''; }
7523                 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
7524         }
7525 });
7526
7527 /*
7528  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7529  */
7530
7531
7532 /* @namespace Marker
7533  * @section Interaction handlers
7534  *
7535  * 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:
7536  *
7537  * ```js
7538  * marker.dragging.disable();
7539  * ```
7540  *
7541  * @property dragging: Handler
7542  * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7543  */
7544
7545 var MarkerDrag = Handler.extend({
7546         initialize: function (marker) {
7547                 this._marker = marker;
7548         },
7549
7550         addHooks: function () {
7551                 var icon = this._marker._icon;
7552
7553                 if (!this._draggable) {
7554                         this._draggable = new Draggable(icon, icon, true);
7555                 }
7556
7557                 this._draggable.on({
7558                         dragstart: this._onDragStart,
7559                         predrag: this._onPreDrag,
7560                         drag: this._onDrag,
7561                         dragend: this._onDragEnd
7562                 }, this).enable();
7563
7564                 addClass(icon, 'leaflet-marker-draggable');
7565         },
7566
7567         removeHooks: function () {
7568                 this._draggable.off({
7569                         dragstart: this._onDragStart,
7570                         predrag: this._onPreDrag,
7571                         drag: this._onDrag,
7572                         dragend: this._onDragEnd
7573                 }, this).disable();
7574
7575                 if (this._marker._icon) {
7576                         removeClass(this._marker._icon, 'leaflet-marker-draggable');
7577                 }
7578         },
7579
7580         moved: function () {
7581                 return this._draggable && this._draggable._moved;
7582         },
7583
7584         _adjustPan: function (e) {
7585                 var marker = this._marker,
7586                     map = marker._map,
7587                     speed = this._marker.options.autoPanSpeed,
7588                     padding = this._marker.options.autoPanPadding,
7589                     iconPos = getPosition(marker._icon),
7590                     bounds = map.getPixelBounds(),
7591                     origin = map.getPixelOrigin();
7592
7593                 var panBounds = toBounds(
7594                         bounds.min._subtract(origin).add(padding),
7595                         bounds.max._subtract(origin).subtract(padding)
7596                 );
7597
7598                 if (!panBounds.contains(iconPos)) {
7599                         // Compute incremental movement
7600                         var movement = toPoint(
7601                                 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7602                                 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7603
7604                                 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7605                                 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7606                         ).multiplyBy(speed);
7607
7608                         map.panBy(movement, {animate: false});
7609
7610                         this._draggable._newPos._add(movement);
7611                         this._draggable._startPos._add(movement);
7612
7613                         setPosition(marker._icon, this._draggable._newPos);
7614                         this._onDrag(e);
7615
7616                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7617                 }
7618         },
7619
7620         _onDragStart: function () {
7621                 // @section Dragging events
7622                 // @event dragstart: Event
7623                 // Fired when the user starts dragging the marker.
7624
7625                 // @event movestart: Event
7626                 // Fired when the marker starts moving (because of dragging).
7627
7628                 this._oldLatLng = this._marker.getLatLng();
7629
7630                 // When using ES6 imports it could not be set when `Popup` was not imported as well
7631                 this._marker.closePopup && this._marker.closePopup();
7632
7633                 this._marker
7634                         .fire('movestart')
7635                         .fire('dragstart');
7636         },
7637
7638         _onPreDrag: function (e) {
7639                 if (this._marker.options.autoPan) {
7640                         cancelAnimFrame(this._panRequest);
7641                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7642                 }
7643         },
7644
7645         _onDrag: function (e) {
7646                 var marker = this._marker,
7647                     shadow = marker._shadow,
7648                     iconPos = getPosition(marker._icon),
7649                     latlng = marker._map.layerPointToLatLng(iconPos);
7650
7651                 // update shadow position
7652                 if (shadow) {
7653                         setPosition(shadow, iconPos);
7654                 }
7655
7656                 marker._latlng = latlng;
7657                 e.latlng = latlng;
7658                 e.oldLatLng = this._oldLatLng;
7659
7660                 // @event drag: Event
7661                 // Fired repeatedly while the user drags the marker.
7662                 marker
7663                     .fire('move', e)
7664                     .fire('drag', e);
7665         },
7666
7667         _onDragEnd: function (e) {
7668                 // @event dragend: DragEndEvent
7669                 // Fired when the user stops dragging the marker.
7670
7671                  cancelAnimFrame(this._panRequest);
7672
7673                 // @event moveend: Event
7674                 // Fired when the marker stops moving (because of dragging).
7675                 delete this._oldLatLng;
7676                 this._marker
7677                     .fire('moveend')
7678                     .fire('dragend', e);
7679         }
7680 });
7681
7682 /*\r
7683  * @class Marker\r
7684  * @inherits Interactive layer\r
7685  * @aka L.Marker\r
7686  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.\r
7687  *\r
7688  * @example\r
7689  *\r
7690  * ```js\r
7691  * L.marker([50.5, 30.5]).addTo(map);\r
7692  * ```\r
7693  */\r
7694 \r
7695 var Marker = Layer.extend({\r
7696 \r
7697         // @section\r
7698         // @aka Marker options\r
7699         options: {\r
7700                 // @option icon: Icon = *\r
7701                 // Icon instance to use for rendering the marker.\r
7702                 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.\r
7703                 // If not specified, a common instance of `L.Icon.Default` is used.\r
7704                 icon: new IconDefault(),\r
7705 \r
7706                 // Option inherited from "Interactive layer" abstract class\r
7707                 interactive: true,\r
7708 \r
7709                 // @option keyboard: Boolean = true\r
7710                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.\r
7711                 keyboard: true,\r
7712 \r
7713                 // @option title: String = ''\r
7714                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).\r
7715                 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).\r
7716                 title: '',\r
7717 \r
7718                 // @option alt: String = 'Marker'\r
7719                 // Text for the `alt` attribute of the icon image.\r
7720                 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).\r
7721                 alt: 'Marker',\r
7722 \r
7723                 // @option zIndexOffset: Number = 0\r
7724                 // 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
7725                 zIndexOffset: 0,\r
7726 \r
7727                 // @option opacity: Number = 1.0\r
7728                 // The opacity of the marker.\r
7729                 opacity: 1,\r
7730 \r
7731                 // @option riseOnHover: Boolean = false\r
7732                 // If `true`, the marker will get on top of others when you hover the mouse over it.\r
7733                 riseOnHover: false,\r
7734 \r
7735                 // @option riseOffset: Number = 250\r
7736                 // The z-index offset used for the `riseOnHover` feature.\r
7737                 riseOffset: 250,\r
7738 \r
7739                 // @option pane: String = 'markerPane'\r
7740                 // `Map pane` where the markers icon will be added.\r
7741                 pane: 'markerPane',\r
7742 \r
7743                 // @option shadowPane: String = 'shadowPane'\r
7744                 // `Map pane` where the markers shadow will be added.\r
7745                 shadowPane: 'shadowPane',\r
7746 \r
7747                 // @option bubblingMouseEvents: Boolean = false\r
7748                 // When `true`, a mouse event on this marker will trigger the same event on the map\r
7749                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).\r
7750                 bubblingMouseEvents: false,\r
7751 \r
7752                 // @option autoPanOnFocus: Boolean = true\r
7753                 // When `true`, the map will pan whenever the marker is focused (via\r
7754                 // e.g. pressing `tab` on the keyboard) to ensure the marker is\r
7755                 // visible within the map's bounds\r
7756                 autoPanOnFocus: true,\r
7757 \r
7758                 // @section Draggable marker options\r
7759                 // @option draggable: Boolean = false\r
7760                 // Whether the marker is draggable with mouse/touch or not.\r
7761                 draggable: false,\r
7762 \r
7763                 // @option autoPan: Boolean = false\r
7764                 // Whether to pan the map when dragging this marker near its edge or not.\r
7765                 autoPan: false,\r
7766 \r
7767                 // @option autoPanPadding: Point = Point(50, 50)\r
7768                 // Distance (in pixels to the left/right and to the top/bottom) of the\r
7769                 // map edge to start panning the map.\r
7770                 autoPanPadding: [50, 50],\r
7771 \r
7772                 // @option autoPanSpeed: Number = 10\r
7773                 // Number of pixels the map should pan by.\r
7774                 autoPanSpeed: 10\r
7775         },\r
7776 \r
7777         /* @section\r
7778          *\r
7779          * 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
7780          */\r
7781 \r
7782         initialize: function (latlng, options) {\r
7783                 setOptions(this, options);\r
7784                 this._latlng = toLatLng(latlng);\r
7785         },\r
7786 \r
7787         onAdd: function (map) {\r
7788                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;\r
7789 \r
7790                 if (this._zoomAnimated) {\r
7791                         map.on('zoomanim', this._animateZoom, this);\r
7792                 }\r
7793 \r
7794                 this._initIcon();\r
7795                 this.update();\r
7796         },\r
7797 \r
7798         onRemove: function (map) {\r
7799                 if (this.dragging && this.dragging.enabled()) {\r
7800                         this.options.draggable = true;\r
7801                         this.dragging.removeHooks();\r
7802                 }\r
7803                 delete this.dragging;\r
7804 \r
7805                 if (this._zoomAnimated) {\r
7806                         map.off('zoomanim', this._animateZoom, this);\r
7807                 }\r
7808 \r
7809                 this._removeIcon();\r
7810                 this._removeShadow();\r
7811         },\r
7812 \r
7813         getEvents: function () {\r
7814                 return {\r
7815                         zoom: this.update,\r
7816                         viewreset: this.update\r
7817                 };\r
7818         },\r
7819 \r
7820         // @method getLatLng: LatLng\r
7821         // Returns the current geographical position of the marker.\r
7822         getLatLng: function () {\r
7823                 return this._latlng;\r
7824         },\r
7825 \r
7826         // @method setLatLng(latlng: LatLng): this\r
7827         // Changes the marker position to the given point.\r
7828         setLatLng: function (latlng) {\r
7829                 var oldLatLng = this._latlng;\r
7830                 this._latlng = toLatLng(latlng);\r
7831                 this.update();\r
7832 \r
7833                 // @event move: Event\r
7834                 // 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
7835                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});\r
7836         },\r
7837 \r
7838         // @method setZIndexOffset(offset: Number): this\r
7839         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.\r
7840         setZIndexOffset: function (offset) {\r
7841                 this.options.zIndexOffset = offset;\r
7842                 return this.update();\r
7843         },\r
7844 \r
7845         // @method getIcon: Icon\r
7846         // Returns the current icon used by the marker\r
7847         getIcon: function () {\r
7848                 return this.options.icon;\r
7849         },\r
7850 \r
7851         // @method setIcon(icon: Icon): this\r
7852         // Changes the marker icon.\r
7853         setIcon: function (icon) {\r
7854 \r
7855                 this.options.icon = icon;\r
7856 \r
7857                 if (this._map) {\r
7858                         this._initIcon();\r
7859                         this.update();\r
7860                 }\r
7861 \r
7862                 if (this._popup) {\r
7863                         this.bindPopup(this._popup, this._popup.options);\r
7864                 }\r
7865 \r
7866                 return this;\r
7867         },\r
7868 \r
7869         getElement: function () {\r
7870                 return this._icon;\r
7871         },\r
7872 \r
7873         update: function () {\r
7874 \r
7875                 if (this._icon && this._map) {\r
7876                         var pos = this._map.latLngToLayerPoint(this._latlng).round();\r
7877                         this._setPos(pos);\r
7878                 }\r
7879 \r
7880                 return this;\r
7881         },\r
7882 \r
7883         _initIcon: function () {\r
7884                 var options = this.options,\r
7885                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');\r
7886 \r
7887                 var icon = options.icon.createIcon(this._icon),\r
7888                     addIcon = false;\r
7889 \r
7890                 // if we're not reusing the icon, remove the old one and init new one\r
7891                 if (icon !== this._icon) {\r
7892                         if (this._icon) {\r
7893                                 this._removeIcon();\r
7894                         }\r
7895                         addIcon = true;\r
7896 \r
7897                         if (options.title) {\r
7898                                 icon.title = options.title;\r
7899                         }\r
7900 \r
7901                         if (icon.tagName === 'IMG') {\r
7902                                 icon.alt = options.alt || '';\r
7903                         }\r
7904                 }\r
7905 \r
7906                 addClass(icon, classToAdd);\r
7907 \r
7908                 if (options.keyboard) {\r
7909                         icon.tabIndex = '0';\r
7910                         icon.setAttribute('role', 'button');\r
7911                 }\r
7912 \r
7913                 this._icon = icon;\r
7914 \r
7915                 if (options.riseOnHover) {\r
7916                         this.on({\r
7917                                 mouseover: this._bringToFront,\r
7918                                 mouseout: this._resetZIndex\r
7919                         });\r
7920                 }\r
7921 \r
7922                 if (this.options.autoPanOnFocus) {\r
7923                         on(icon, 'focus', this._panOnFocus, this);\r
7924                 }\r
7925 \r
7926                 var newShadow = options.icon.createShadow(this._shadow),\r
7927                     addShadow = false;\r
7928 \r
7929                 if (newShadow !== this._shadow) {\r
7930                         this._removeShadow();\r
7931                         addShadow = true;\r
7932                 }\r
7933 \r
7934                 if (newShadow) {\r
7935                         addClass(newShadow, classToAdd);\r
7936                         newShadow.alt = '';\r
7937                 }\r
7938                 this._shadow = newShadow;\r
7939 \r
7940 \r
7941                 if (options.opacity < 1) {\r
7942                         this._updateOpacity();\r
7943                 }\r
7944 \r
7945 \r
7946                 if (addIcon) {\r
7947                         this.getPane().appendChild(this._icon);\r
7948                 }\r
7949                 this._initInteraction();\r
7950                 if (newShadow && addShadow) {\r
7951                         this.getPane(options.shadowPane).appendChild(this._shadow);\r
7952                 }\r
7953         },\r
7954 \r
7955         _removeIcon: function () {\r
7956                 if (this.options.riseOnHover) {\r
7957                         this.off({\r
7958                                 mouseover: this._bringToFront,\r
7959                                 mouseout: this._resetZIndex\r
7960                         });\r
7961                 }\r
7962 \r
7963                 if (this.options.autoPanOnFocus) {\r
7964                         off(this._icon, 'focus', this._panOnFocus, this);\r
7965                 }\r
7966 \r
7967                 remove(this._icon);\r
7968                 this.removeInteractiveTarget(this._icon);\r
7969 \r
7970                 this._icon = null;\r
7971         },\r
7972 \r
7973         _removeShadow: function () {\r
7974                 if (this._shadow) {\r
7975                         remove(this._shadow);\r
7976                 }\r
7977                 this._shadow = null;\r
7978         },\r
7979 \r
7980         _setPos: function (pos) {\r
7981 \r
7982                 if (this._icon) {\r
7983                         setPosition(this._icon, pos);\r
7984                 }\r
7985 \r
7986                 if (this._shadow) {\r
7987                         setPosition(this._shadow, pos);\r
7988                 }\r
7989 \r
7990                 this._zIndex = pos.y + this.options.zIndexOffset;\r
7991 \r
7992                 this._resetZIndex();\r
7993         },\r
7994 \r
7995         _updateZIndex: function (offset) {\r
7996                 if (this._icon) {\r
7997                         this._icon.style.zIndex = this._zIndex + offset;\r
7998                 }\r
7999         },\r
8000 \r
8001         _animateZoom: function (opt) {\r
8002                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();\r
8003 \r
8004                 this._setPos(pos);\r
8005         },\r
8006 \r
8007         _initInteraction: function () {\r
8008 \r
8009                 if (!this.options.interactive) { return; }\r
8010 \r
8011                 addClass(this._icon, 'leaflet-interactive');\r
8012 \r
8013                 this.addInteractiveTarget(this._icon);\r
8014 \r
8015                 if (MarkerDrag) {\r
8016                         var draggable = this.options.draggable;\r
8017                         if (this.dragging) {\r
8018                                 draggable = this.dragging.enabled();\r
8019                                 this.dragging.disable();\r
8020                         }\r
8021 \r
8022                         this.dragging = new MarkerDrag(this);\r
8023 \r
8024                         if (draggable) {\r
8025                                 this.dragging.enable();\r
8026                         }\r
8027                 }\r
8028         },\r
8029 \r
8030         // @method setOpacity(opacity: Number): this\r
8031         // Changes the opacity of the marker.\r
8032         setOpacity: function (opacity) {\r
8033                 this.options.opacity = opacity;\r
8034                 if (this._map) {\r
8035                         this._updateOpacity();\r
8036                 }\r
8037 \r
8038                 return this;\r
8039         },\r
8040 \r
8041         _updateOpacity: function () {\r
8042                 var opacity = this.options.opacity;\r
8043 \r
8044                 if (this._icon) {\r
8045                         setOpacity(this._icon, opacity);\r
8046                 }\r
8047 \r
8048                 if (this._shadow) {\r
8049                         setOpacity(this._shadow, opacity);\r
8050                 }\r
8051         },\r
8052 \r
8053         _bringToFront: function () {\r
8054                 this._updateZIndex(this.options.riseOffset);\r
8055         },\r
8056 \r
8057         _resetZIndex: function () {\r
8058                 this._updateZIndex(0);\r
8059         },\r
8060 \r
8061         _panOnFocus: function () {\r
8062                 var map = this._map;\r
8063                 if (!map) { return; }\r
8064 \r
8065                 var iconOpts = this.options.icon.options;\r
8066                 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);\r
8067                 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);\r
8068 \r
8069                 map.panInside(this._latlng, {\r
8070                         paddingTopLeft: anchor,\r
8071                         paddingBottomRight: size.subtract(anchor)\r
8072                 });\r
8073         },\r
8074 \r
8075         _getPopupAnchor: function () {\r
8076                 return this.options.icon.options.popupAnchor;\r
8077         },\r
8078 \r
8079         _getTooltipAnchor: function () {\r
8080                 return this.options.icon.options.tooltipAnchor;\r
8081         }\r
8082 });\r
8083 \r
8084 \r
8085 // factory L.marker(latlng: LatLng, options? : Marker options)\r
8086 \r
8087 // @factory L.marker(latlng: LatLng, options? : Marker options)\r
8088 // Instantiates a Marker object given a geographical point and optionally an options object.\r
8089 function marker(latlng, options) {\r
8090         return new Marker(latlng, options);\r
8091 }
8092
8093 /*
8094  * @class Path
8095  * @aka L.Path
8096  * @inherits Interactive layer
8097  *
8098  * An abstract class that contains options and constants shared between vector
8099  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8100  */
8101
8102 var Path = Layer.extend({
8103
8104         // @section
8105         // @aka Path options
8106         options: {
8107                 // @option stroke: Boolean = true
8108                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8109                 stroke: true,
8110
8111                 // @option color: String = '#3388ff'
8112                 // Stroke color
8113                 color: '#3388ff',
8114
8115                 // @option weight: Number = 3
8116                 // Stroke width in pixels
8117                 weight: 3,
8118
8119                 // @option opacity: Number = 1.0
8120                 // Stroke opacity
8121                 opacity: 1,
8122
8123                 // @option lineCap: String= 'round'
8124                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8125                 lineCap: 'round',
8126
8127                 // @option lineJoin: String = 'round'
8128                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8129                 lineJoin: 'round',
8130
8131                 // @option dashArray: String = null
8132                 // 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).
8133                 dashArray: null,
8134
8135                 // @option dashOffset: String = null
8136                 // 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).
8137                 dashOffset: null,
8138
8139                 // @option fill: Boolean = depends
8140                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8141                 fill: false,
8142
8143                 // @option fillColor: String = *
8144                 // Fill color. Defaults to the value of the [`color`](#path-color) option
8145                 fillColor: null,
8146
8147                 // @option fillOpacity: Number = 0.2
8148                 // Fill opacity.
8149                 fillOpacity: 0.2,
8150
8151                 // @option fillRule: String = 'evenodd'
8152                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8153                 fillRule: 'evenodd',
8154
8155                 // className: '',
8156
8157                 // Option inherited from "Interactive layer" abstract class
8158                 interactive: true,
8159
8160                 // @option bubblingMouseEvents: Boolean = true
8161                 // When `true`, a mouse event on this path will trigger the same event on the map
8162                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
8163                 bubblingMouseEvents: true
8164         },
8165
8166         beforeAdd: function (map) {
8167                 // Renderer is set here because we need to call renderer.getEvents
8168                 // before this.getEvents.
8169                 this._renderer = map.getRenderer(this);
8170         },
8171
8172         onAdd: function () {
8173                 this._renderer._initPath(this);
8174                 this._reset();
8175                 this._renderer._addPath(this);
8176         },
8177
8178         onRemove: function () {
8179                 this._renderer._removePath(this);
8180         },
8181
8182         // @method redraw(): this
8183         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8184         redraw: function () {
8185                 if (this._map) {
8186                         this._renderer._updatePath(this);
8187                 }
8188                 return this;
8189         },
8190
8191         // @method setStyle(style: Path options): this
8192         // Changes the appearance of a Path based on the options in the `Path options` object.
8193         setStyle: function (style) {
8194                 setOptions(this, style);
8195                 if (this._renderer) {
8196                         this._renderer._updateStyle(this);
8197                         if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
8198                                 this._updateBounds();
8199                         }
8200                 }
8201                 return this;
8202         },
8203
8204         // @method bringToFront(): this
8205         // Brings the layer to the top of all path layers.
8206         bringToFront: function () {
8207                 if (this._renderer) {
8208                         this._renderer._bringToFront(this);
8209                 }
8210                 return this;
8211         },
8212
8213         // @method bringToBack(): this
8214         // Brings the layer to the bottom of all path layers.
8215         bringToBack: function () {
8216                 if (this._renderer) {
8217                         this._renderer._bringToBack(this);
8218                 }
8219                 return this;
8220         },
8221
8222         getElement: function () {
8223                 return this._path;
8224         },
8225
8226         _reset: function () {
8227                 // defined in child classes
8228                 this._project();
8229                 this._update();
8230         },
8231
8232         _clickTolerance: function () {
8233                 // used when doing hit detection for Canvas layers
8234                 return (this.options.stroke ? this.options.weight / 2 : 0) +
8235                   (this._renderer.options.tolerance || 0);
8236         }
8237 });
8238
8239 /*
8240  * @class CircleMarker
8241  * @aka L.CircleMarker
8242  * @inherits Path
8243  *
8244  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8245  */
8246
8247 var CircleMarker = Path.extend({
8248
8249         // @section
8250         // @aka CircleMarker options
8251         options: {
8252                 fill: true,
8253
8254                 // @option radius: Number = 10
8255                 // Radius of the circle marker, in pixels
8256                 radius: 10
8257         },
8258
8259         initialize: function (latlng, options) {
8260                 setOptions(this, options);
8261                 this._latlng = toLatLng(latlng);
8262                 this._radius = this.options.radius;
8263         },
8264
8265         // @method setLatLng(latLng: LatLng): this
8266         // Sets the position of a circle marker to a new location.
8267         setLatLng: function (latlng) {
8268                 var oldLatLng = this._latlng;
8269                 this._latlng = toLatLng(latlng);
8270                 this.redraw();
8271
8272                 // @event move: Event
8273                 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
8274                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
8275         },
8276
8277         // @method getLatLng(): LatLng
8278         // Returns the current geographical position of the circle marker
8279         getLatLng: function () {
8280                 return this._latlng;
8281         },
8282
8283         // @method setRadius(radius: Number): this
8284         // Sets the radius of a circle marker. Units are in pixels.
8285         setRadius: function (radius) {
8286                 this.options.radius = this._radius = radius;
8287                 return this.redraw();
8288         },
8289
8290         // @method getRadius(): Number
8291         // Returns the current radius of the circle
8292         getRadius: function () {
8293                 return this._radius;
8294         },
8295
8296         setStyle : function (options) {
8297                 var radius = options && options.radius || this._radius;
8298                 Path.prototype.setStyle.call(this, options);
8299                 this.setRadius(radius);
8300                 return this;
8301         },
8302
8303         _project: function () {
8304                 this._point = this._map.latLngToLayerPoint(this._latlng);
8305                 this._updateBounds();
8306         },
8307
8308         _updateBounds: function () {
8309                 var r = this._radius,
8310                     r2 = this._radiusY || r,
8311                     w = this._clickTolerance(),
8312                     p = [r + w, r2 + w];
8313                 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
8314         },
8315
8316         _update: function () {
8317                 if (this._map) {
8318                         this._updatePath();
8319                 }
8320         },
8321
8322         _updatePath: function () {
8323                 this._renderer._updateCircle(this);
8324         },
8325
8326         _empty: function () {
8327                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8328         },
8329
8330         // Needed by the `Canvas` renderer for interactivity
8331         _containsPoint: function (p) {
8332                 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8333         }
8334 });
8335
8336
8337 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8338 // Instantiates a circle marker object given a geographical point, and an optional options object.
8339 function circleMarker(latlng, options) {
8340         return new CircleMarker(latlng, options);
8341 }
8342
8343 /*
8344  * @class Circle
8345  * @aka L.Circle
8346  * @inherits CircleMarker
8347  *
8348  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8349  *
8350  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8351  *
8352  * @example
8353  *
8354  * ```js
8355  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8356  * ```
8357  */
8358
8359 var Circle = CircleMarker.extend({
8360
8361         initialize: function (latlng, options, legacyOptions) {
8362                 if (typeof options === 'number') {
8363                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8364                         options = extend({}, legacyOptions, {radius: options});
8365                 }
8366                 setOptions(this, options);
8367                 this._latlng = toLatLng(latlng);
8368
8369                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8370
8371                 // @section
8372                 // @aka Circle options
8373                 // @option radius: Number; Radius of the circle, in meters.
8374                 this._mRadius = this.options.radius;
8375         },
8376
8377         // @method setRadius(radius: Number): this
8378         // Sets the radius of a circle. Units are in meters.
8379         setRadius: function (radius) {
8380                 this._mRadius = radius;
8381                 return this.redraw();
8382         },
8383
8384         // @method getRadius(): Number
8385         // Returns the current radius of a circle. Units are in meters.
8386         getRadius: function () {
8387                 return this._mRadius;
8388         },
8389
8390         // @method getBounds(): LatLngBounds
8391         // Returns the `LatLngBounds` of the path.
8392         getBounds: function () {
8393                 var half = [this._radius, this._radiusY || this._radius];
8394
8395                 return new LatLngBounds(
8396                         this._map.layerPointToLatLng(this._point.subtract(half)),
8397                         this._map.layerPointToLatLng(this._point.add(half)));
8398         },
8399
8400         setStyle: Path.prototype.setStyle,
8401
8402         _project: function () {
8403
8404                 var lng = this._latlng.lng,
8405                     lat = this._latlng.lat,
8406                     map = this._map,
8407                     crs = map.options.crs;
8408
8409                 if (crs.distance === Earth.distance) {
8410                         var d = Math.PI / 180,
8411                             latR = (this._mRadius / Earth.R) / d,
8412                             top = map.project([lat + latR, lng]),
8413                             bottom = map.project([lat - latR, lng]),
8414                             p = top.add(bottom).divideBy(2),
8415                             lat2 = map.unproject(p).lat,
8416                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8417                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8418
8419                         if (isNaN(lngR) || lngR === 0) {
8420                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8421                         }
8422
8423                         this._point = p.subtract(map.getPixelOrigin());
8424                         this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8425                         this._radiusY = p.y - top.y;
8426
8427                 } else {
8428                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8429
8430                         this._point = map.latLngToLayerPoint(this._latlng);
8431                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8432                 }
8433
8434                 this._updateBounds();
8435         }
8436 });
8437
8438 // @factory L.circle(latlng: LatLng, options?: Circle options)
8439 // Instantiates a circle object given a geographical point, and an options object
8440 // which contains the circle radius.
8441 // @alternative
8442 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8443 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8444 // Do not use in new applications or plugins.
8445 function circle(latlng, options, legacyOptions) {
8446         return new Circle(latlng, options, legacyOptions);
8447 }
8448
8449 /*
8450  * @class Polyline
8451  * @aka L.Polyline
8452  * @inherits Path
8453  *
8454  * A class for drawing polyline overlays on a map. Extends `Path`.
8455  *
8456  * @example
8457  *
8458  * ```js
8459  * // create a red polyline from an array of LatLng points
8460  * var latlngs = [
8461  *      [45.51, -122.68],
8462  *      [37.77, -122.43],
8463  *      [34.04, -118.2]
8464  * ];
8465  *
8466  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8467  *
8468  * // zoom the map to the polyline
8469  * map.fitBounds(polyline.getBounds());
8470  * ```
8471  *
8472  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8473  *
8474  * ```js
8475  * // create a red polyline from an array of arrays of LatLng points
8476  * var latlngs = [
8477  *      [[45.51, -122.68],
8478  *       [37.77, -122.43],
8479  *       [34.04, -118.2]],
8480  *      [[40.78, -73.91],
8481  *       [41.83, -87.62],
8482  *       [32.76, -96.72]]
8483  * ];
8484  * ```
8485  */
8486
8487
8488 var Polyline = Path.extend({
8489
8490         // @section
8491         // @aka Polyline options
8492         options: {
8493                 // @option smoothFactor: Number = 1.0
8494                 // How much to simplify the polyline on each zoom level. More means
8495                 // better performance and smoother look, and less means more accurate representation.
8496                 smoothFactor: 1.0,
8497
8498                 // @option noClip: Boolean = false
8499                 // Disable polyline clipping.
8500                 noClip: false
8501         },
8502
8503         initialize: function (latlngs, options) {
8504                 setOptions(this, options);
8505                 this._setLatLngs(latlngs);
8506         },
8507
8508         // @method getLatLngs(): LatLng[]
8509         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8510         getLatLngs: function () {
8511                 return this._latlngs;
8512         },
8513
8514         // @method setLatLngs(latlngs: LatLng[]): this
8515         // Replaces all the points in the polyline with the given array of geographical points.
8516         setLatLngs: function (latlngs) {
8517                 this._setLatLngs(latlngs);
8518                 return this.redraw();
8519         },
8520
8521         // @method isEmpty(): Boolean
8522         // Returns `true` if the Polyline has no LatLngs.
8523         isEmpty: function () {
8524                 return !this._latlngs.length;
8525         },
8526
8527         // @method closestLayerPoint(p: Point): Point
8528         // Returns the point closest to `p` on the Polyline.
8529         closestLayerPoint: function (p) {
8530                 var minDistance = Infinity,
8531                     minPoint = null,
8532                     closest = _sqClosestPointOnSegment,
8533                     p1, p2;
8534
8535                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8536                         var points = this._parts[j];
8537
8538                         for (var i = 1, len = points.length; i < len; i++) {
8539                                 p1 = points[i - 1];
8540                                 p2 = points[i];
8541
8542                                 var sqDist = closest(p, p1, p2, true);
8543
8544                                 if (sqDist < minDistance) {
8545                                         minDistance = sqDist;
8546                                         minPoint = closest(p, p1, p2);
8547                                 }
8548                         }
8549                 }
8550                 if (minPoint) {
8551                         minPoint.distance = Math.sqrt(minDistance);
8552                 }
8553                 return minPoint;
8554         },
8555
8556         // @method getCenter(): LatLng
8557         // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
8558         getCenter: function () {
8559                 // throws error when not yet added to map as this center calculation requires projected coordinates
8560                 if (!this._map) {
8561                         throw new Error('Must add layer to map before using getCenter()');
8562                 }
8563                 return polylineCenter(this._defaultShape(), this._map.options.crs);
8564         },
8565
8566         // @method getBounds(): LatLngBounds
8567         // Returns the `LatLngBounds` of the path.
8568         getBounds: function () {
8569                 return this._bounds;
8570         },
8571
8572         // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8573         // Adds a given point to the polyline. By default, adds to the first ring of
8574         // the polyline in case of a multi-polyline, but can be overridden by passing
8575         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8576         addLatLng: function (latlng, latlngs) {
8577                 latlngs = latlngs || this._defaultShape();
8578                 latlng = toLatLng(latlng);
8579                 latlngs.push(latlng);
8580                 this._bounds.extend(latlng);
8581                 return this.redraw();
8582         },
8583
8584         _setLatLngs: function (latlngs) {
8585                 this._bounds = new LatLngBounds();
8586                 this._latlngs = this._convertLatLngs(latlngs);
8587         },
8588
8589         _defaultShape: function () {
8590                 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8591         },
8592
8593         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8594         _convertLatLngs: function (latlngs) {
8595                 var result = [],
8596                     flat = isFlat(latlngs);
8597
8598                 for (var i = 0, len = latlngs.length; i < len; i++) {
8599                         if (flat) {
8600                                 result[i] = toLatLng(latlngs[i]);
8601                                 this._bounds.extend(result[i]);
8602                         } else {
8603                                 result[i] = this._convertLatLngs(latlngs[i]);
8604                         }
8605                 }
8606
8607                 return result;
8608         },
8609
8610         _project: function () {
8611                 var pxBounds = new Bounds();
8612                 this._rings = [];
8613                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8614
8615                 if (this._bounds.isValid() && pxBounds.isValid()) {
8616                         this._rawPxBounds = pxBounds;
8617                         this._updateBounds();
8618                 }
8619         },
8620
8621         _updateBounds: function () {
8622                 var w = this._clickTolerance(),
8623                     p = new Point(w, w);
8624
8625                 if (!this._rawPxBounds) {
8626                         return;
8627                 }
8628
8629                 this._pxBounds = new Bounds([
8630                         this._rawPxBounds.min.subtract(p),
8631                         this._rawPxBounds.max.add(p)
8632                 ]);
8633         },
8634
8635         // recursively turns latlngs into a set of rings with projected coordinates
8636         _projectLatlngs: function (latlngs, result, projectedBounds) {
8637                 var flat = latlngs[0] instanceof LatLng,
8638                     len = latlngs.length,
8639                     i, ring;
8640
8641                 if (flat) {
8642                         ring = [];
8643                         for (i = 0; i < len; i++) {
8644                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8645                                 projectedBounds.extend(ring[i]);
8646                         }
8647                         result.push(ring);
8648                 } else {
8649                         for (i = 0; i < len; i++) {
8650                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8651                         }
8652                 }
8653         },
8654
8655         // clip polyline by renderer bounds so that we have less to render for performance
8656         _clipPoints: function () {
8657                 var bounds = this._renderer._bounds;
8658
8659                 this._parts = [];
8660                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8661                         return;
8662                 }
8663
8664                 if (this.options.noClip) {
8665                         this._parts = this._rings;
8666                         return;
8667                 }
8668
8669                 var parts = this._parts,
8670                     i, j, k, len, len2, segment, points;
8671
8672                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8673                         points = this._rings[i];
8674
8675                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8676                                 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8677
8678                                 if (!segment) { continue; }
8679
8680                                 parts[k] = parts[k] || [];
8681                                 parts[k].push(segment[0]);
8682
8683                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8684                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8685                                         parts[k].push(segment[1]);
8686                                         k++;
8687                                 }
8688                         }
8689                 }
8690         },
8691
8692         // simplify each clipped part of the polyline for performance
8693         _simplifyPoints: function () {
8694                 var parts = this._parts,
8695                     tolerance = this.options.smoothFactor;
8696
8697                 for (var i = 0, len = parts.length; i < len; i++) {
8698                         parts[i] = simplify(parts[i], tolerance);
8699                 }
8700         },
8701
8702         _update: function () {
8703                 if (!this._map) { return; }
8704
8705                 this._clipPoints();
8706                 this._simplifyPoints();
8707                 this._updatePath();
8708         },
8709
8710         _updatePath: function () {
8711                 this._renderer._updatePoly(this);
8712         },
8713
8714         // Needed by the `Canvas` renderer for interactivity
8715         _containsPoint: function (p, closed) {
8716                 var i, j, k, len, len2, part,
8717                     w = this._clickTolerance();
8718
8719                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8720
8721                 // hit detection for polylines
8722                 for (i = 0, len = this._parts.length; i < len; i++) {
8723                         part = this._parts[i];
8724
8725                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8726                                 if (!closed && (j === 0)) { continue; }
8727
8728                                 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8729                                         return true;
8730                                 }
8731                         }
8732                 }
8733                 return false;
8734         }
8735 });
8736
8737 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8738 // Instantiates a polyline object given an array of geographical points and
8739 // optionally an options object. You can create a `Polyline` object with
8740 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8741 // of geographic points.
8742 function polyline(latlngs, options) {
8743         return new Polyline(latlngs, options);
8744 }
8745
8746 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8747 Polyline._flat = _flat;
8748
8749 /*
8750  * @class Polygon
8751  * @aka L.Polygon
8752  * @inherits Polyline
8753  *
8754  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8755  *
8756  * 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.
8757  *
8758  *
8759  * @example
8760  *
8761  * ```js
8762  * // create a red polygon from an array of LatLng points
8763  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8764  *
8765  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8766  *
8767  * // zoom the map to the polygon
8768  * map.fitBounds(polygon.getBounds());
8769  * ```
8770  *
8771  * 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:
8772  *
8773  * ```js
8774  * var latlngs = [
8775  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8776  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8777  * ];
8778  * ```
8779  *
8780  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8781  *
8782  * ```js
8783  * var latlngs = [
8784  *   [ // first polygon
8785  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8786  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8787  *   ],
8788  *   [ // second polygon
8789  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8790  *   ]
8791  * ];
8792  * ```
8793  */
8794
8795 var Polygon = Polyline.extend({
8796
8797         options: {
8798                 fill: true
8799         },
8800
8801         isEmpty: function () {
8802                 return !this._latlngs.length || !this._latlngs[0].length;
8803         },
8804
8805         // @method getCenter(): LatLng
8806         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
8807         getCenter: function () {
8808                 // throws error when not yet added to map as this center calculation requires projected coordinates
8809                 if (!this._map) {
8810                         throw new Error('Must add layer to map before using getCenter()');
8811                 }
8812                 return polygonCenter(this._defaultShape(), this._map.options.crs);
8813         },
8814
8815         _convertLatLngs: function (latlngs) {
8816                 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8817                     len = result.length;
8818
8819                 // remove last point if it equals first one
8820                 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8821                         result.pop();
8822                 }
8823                 return result;
8824         },
8825
8826         _setLatLngs: function (latlngs) {
8827                 Polyline.prototype._setLatLngs.call(this, latlngs);
8828                 if (isFlat(this._latlngs)) {
8829                         this._latlngs = [this._latlngs];
8830                 }
8831         },
8832
8833         _defaultShape: function () {
8834                 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8835         },
8836
8837         _clipPoints: function () {
8838                 // polygons need a different clipping algorithm so we redefine that
8839
8840                 var bounds = this._renderer._bounds,
8841                     w = this.options.weight,
8842                     p = new Point(w, w);
8843
8844                 // increase clip padding by stroke width to avoid stroke on clip edges
8845                 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8846
8847                 this._parts = [];
8848                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8849                         return;
8850                 }
8851
8852                 if (this.options.noClip) {
8853                         this._parts = this._rings;
8854                         return;
8855                 }
8856
8857                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8858                         clipped = clipPolygon(this._rings[i], bounds, true);
8859                         if (clipped.length) {
8860                                 this._parts.push(clipped);
8861                         }
8862                 }
8863         },
8864
8865         _updatePath: function () {
8866                 this._renderer._updatePoly(this, true);
8867         },
8868
8869         // Needed by the `Canvas` renderer for interactivity
8870         _containsPoint: function (p) {
8871                 var inside = false,
8872                     part, p1, p2, i, j, k, len, len2;
8873
8874                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8875
8876                 // ray casting algorithm for detecting if point is in polygon
8877                 for (i = 0, len = this._parts.length; i < len; i++) {
8878                         part = this._parts[i];
8879
8880                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8881                                 p1 = part[j];
8882                                 p2 = part[k];
8883
8884                                 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)) {
8885                                         inside = !inside;
8886                                 }
8887                         }
8888                 }
8889
8890                 // also check if it's on polygon stroke
8891                 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8892         }
8893
8894 });
8895
8896
8897 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8898 function polygon(latlngs, options) {
8899         return new Polygon(latlngs, options);
8900 }
8901
8902 /*\r
8903  * @class GeoJSON\r
8904  * @aka L.GeoJSON\r
8905  * @inherits FeatureGroup\r
8906  *\r
8907  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse\r
8908  * GeoJSON data and display it on the map. Extends `FeatureGroup`.\r
8909  *\r
8910  * @example\r
8911  *\r
8912  * ```js\r
8913  * L.geoJSON(data, {\r
8914  *      style: function (feature) {\r
8915  *              return {color: feature.properties.color};\r
8916  *      }\r
8917  * }).bindPopup(function (layer) {\r
8918  *      return layer.feature.properties.description;\r
8919  * }).addTo(map);\r
8920  * ```\r
8921  */\r
8922 \r
8923 var GeoJSON = FeatureGroup.extend({\r
8924 \r
8925         /* @section\r
8926          * @aka GeoJSON options\r
8927          *\r
8928          * @option pointToLayer: Function = *\r
8929          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally\r
8930          * called when data is added, passing the GeoJSON point feature and its `LatLng`.\r
8931          * The default is to spawn a default `Marker`:\r
8932          * ```js\r
8933          * function(geoJsonPoint, latlng) {\r
8934          *      return L.marker(latlng);\r
8935          * }\r
8936          * ```\r
8937          *\r
8938          * @option style: Function = *\r
8939          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,\r
8940          * called internally when data is added.\r
8941          * The default value is to not override any defaults:\r
8942          * ```js\r
8943          * function (geoJsonFeature) {\r
8944          *      return {}\r
8945          * }\r
8946          * ```\r
8947          *\r
8948          * @option onEachFeature: Function = *\r
8949          * A `Function` that will be called once for each created `Feature`, after it has\r
8950          * been created and styled. Useful for attaching events and popups to features.\r
8951          * The default is to do nothing with the newly created layers:\r
8952          * ```js\r
8953          * function (feature, layer) {}\r
8954          * ```\r
8955          *\r
8956          * @option filter: Function = *\r
8957          * A `Function` that will be used to decide whether to include a feature or not.\r
8958          * The default is to include all features:\r
8959          * ```js\r
8960          * function (geoJsonFeature) {\r
8961          *      return true;\r
8962          * }\r
8963          * ```\r
8964          * Note: dynamically changing the `filter` option will have effect only on newly\r
8965          * added data. It will _not_ re-evaluate already included features.\r
8966          *\r
8967          * @option coordsToLatLng: Function = *\r
8968          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.\r
8969          * The default is the `coordsToLatLng` static method.\r
8970          *\r
8971          * @option markersInheritOptions: Boolean = false\r
8972          * Whether default Markers for "Point" type Features inherit from group options.\r
8973          */\r
8974 \r
8975         initialize: function (geojson, options) {\r
8976                 setOptions(this, options);\r
8977 \r
8978                 this._layers = {};\r
8979 \r
8980                 if (geojson) {\r
8981                         this.addData(geojson);\r
8982                 }\r
8983         },\r
8984 \r
8985         // @method addData( <GeoJSON> data ): this\r
8986         // Adds a GeoJSON object to the layer.\r
8987         addData: function (geojson) {\r
8988                 var features = isArray(geojson) ? geojson : geojson.features,\r
8989                     i, len, feature;\r
8990 \r
8991                 if (features) {\r
8992                         for (i = 0, len = features.length; i < len; i++) {\r
8993                                 // only add this if geometry or geometries are set and not null\r
8994                                 feature = features[i];\r
8995                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {\r
8996                                         this.addData(feature);\r
8997                                 }\r
8998                         }\r
8999                         return this;\r
9000                 }\r
9001 \r
9002                 var options = this.options;\r
9003 \r
9004                 if (options.filter && !options.filter(geojson)) { return this; }\r
9005 \r
9006                 var layer = geometryToLayer(geojson, options);\r
9007                 if (!layer) {\r
9008                         return this;\r
9009                 }\r
9010                 layer.feature = asFeature(geojson);\r
9011 \r
9012                 layer.defaultOptions = layer.options;\r
9013                 this.resetStyle(layer);\r
9014 \r
9015                 if (options.onEachFeature) {\r
9016                         options.onEachFeature(geojson, layer);\r
9017                 }\r
9018 \r
9019                 return this.addLayer(layer);\r
9020         },\r
9021 \r
9022         // @method resetStyle( <Path> layer? ): this\r
9023         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.\r
9024         // If `layer` is omitted, the style of all features in the current layer is reset.\r
9025         resetStyle: function (layer) {\r
9026                 if (layer === undefined) {\r
9027                         return this.eachLayer(this.resetStyle, this);\r
9028                 }\r
9029                 // reset any custom styles\r
9030                 layer.options = extend({}, layer.defaultOptions);\r
9031                 this._setLayerStyle(layer, this.options.style);\r
9032                 return this;\r
9033         },\r
9034 \r
9035         // @method setStyle( <Function> style ): this\r
9036         // Changes styles of GeoJSON vector layers with the given style function.\r
9037         setStyle: function (style) {\r
9038                 return this.eachLayer(function (layer) {\r
9039                         this._setLayerStyle(layer, style);\r
9040                 }, this);\r
9041         },\r
9042 \r
9043         _setLayerStyle: function (layer, style) {\r
9044                 if (layer.setStyle) {\r
9045                         if (typeof style === 'function') {\r
9046                                 style = style(layer.feature);\r
9047                         }\r
9048                         layer.setStyle(style);\r
9049                 }\r
9050         }\r
9051 });\r
9052 \r
9053 // @section\r
9054 // There are several static functions which can be called without instantiating L.GeoJSON:\r
9055 \r
9056 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer\r
9057 // Creates a `Layer` from a given GeoJSON feature. Can use a custom\r
9058 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)\r
9059 // functions if provided as options.\r
9060 function geometryToLayer(geojson, options) {\r
9061 \r
9062         var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,\r
9063             coords = geometry ? geometry.coordinates : null,\r
9064             layers = [],\r
9065             pointToLayer = options && options.pointToLayer,\r
9066             _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,\r
9067             latlng, latlngs, i, len;\r
9068 \r
9069         if (!coords && !geometry) {\r
9070                 return null;\r
9071         }\r
9072 \r
9073         switch (geometry.type) {\r
9074         case 'Point':\r
9075                 latlng = _coordsToLatLng(coords);\r
9076                 return _pointToLayer(pointToLayer, geojson, latlng, options);\r
9077 \r
9078         case 'MultiPoint':\r
9079                 for (i = 0, len = coords.length; i < len; i++) {\r
9080                         latlng = _coordsToLatLng(coords[i]);\r
9081                         layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));\r
9082                 }\r
9083                 return new FeatureGroup(layers);\r
9084 \r
9085         case 'LineString':\r
9086         case 'MultiLineString':\r
9087                 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);\r
9088                 return new Polyline(latlngs, options);\r
9089 \r
9090         case 'Polygon':\r
9091         case 'MultiPolygon':\r
9092                 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);\r
9093                 return new Polygon(latlngs, options);\r
9094 \r
9095         case 'GeometryCollection':\r
9096                 for (i = 0, len = geometry.geometries.length; i < len; i++) {\r
9097                         var geoLayer = geometryToLayer({\r
9098                                 geometry: geometry.geometries[i],\r
9099                                 type: 'Feature',\r
9100                                 properties: geojson.properties\r
9101                         }, options);\r
9102 \r
9103                         if (geoLayer) {\r
9104                                 layers.push(geoLayer);\r
9105                         }\r
9106                 }\r
9107                 return new FeatureGroup(layers);\r
9108 \r
9109         case 'FeatureCollection':\r
9110                 for (i = 0, len = geometry.features.length; i < len; i++) {\r
9111                         var featureLayer = geometryToLayer(geometry.features[i], options);\r
9112 \r
9113                         if (featureLayer) {\r
9114                                 layers.push(featureLayer);\r
9115                         }\r
9116                 }\r
9117                 return new FeatureGroup(layers);\r
9118 \r
9119         default:\r
9120                 throw new Error('Invalid GeoJSON object.');\r
9121         }\r
9122 }\r
9123 \r
9124 function _pointToLayer(pointToLayerFn, geojson, latlng, options) {\r
9125         return pointToLayerFn ?\r
9126                 pointToLayerFn(geojson, latlng) :\r
9127                 new Marker(latlng, options && options.markersInheritOptions && options);\r
9128 }\r
9129 \r
9130 // @function coordsToLatLng(coords: Array): LatLng\r
9131 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)\r
9132 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.\r
9133 function coordsToLatLng(coords) {\r
9134         return new LatLng(coords[1], coords[0], coords[2]);\r
9135 }\r
9136 \r
9137 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array\r
9138 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.\r
9139 // `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
9140 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.\r
9141 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {\r
9142         var latlngs = [];\r
9143 \r
9144         for (var i = 0, len = coords.length, latlng; i < len; i++) {\r
9145                 latlng = levelsDeep ?\r
9146                         coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :\r
9147                         (_coordsToLatLng || coordsToLatLng)(coords[i]);\r
9148 \r
9149                 latlngs.push(latlng);\r
9150         }\r
9151 \r
9152         return latlngs;\r
9153 }\r
9154 \r
9155 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array\r
9156 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)\r
9157 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.\r
9158 function latLngToCoords(latlng, precision) {\r
9159         latlng = toLatLng(latlng);\r
9160         return latlng.alt !== undefined ?\r
9161                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :\r
9162                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];\r
9163 }\r
9164 \r
9165 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array\r
9166 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)\r
9167 // `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
9168 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.\r
9169 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {\r
9170         var coords = [];\r
9171 \r
9172         for (var i = 0, len = latlngs.length; i < len; i++) {\r
9173                 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion\r
9174                 coords.push(levelsDeep ?\r
9175                         latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :\r
9176                         latLngToCoords(latlngs[i], precision));\r
9177         }\r
9178 \r
9179         if (!levelsDeep && closed && coords.length > 0) {\r
9180                 coords.push(coords[0].slice());\r
9181         }\r
9182 \r
9183         return coords;\r
9184 }\r
9185 \r
9186 function getFeature(layer, newGeometry) {\r
9187         return layer.feature ?\r
9188                 extend({}, layer.feature, {geometry: newGeometry}) :\r
9189                 asFeature(newGeometry);\r
9190 }\r
9191 \r
9192 // @function asFeature(geojson: Object): Object\r
9193 // Normalize GeoJSON geometries/features into GeoJSON features.\r
9194 function asFeature(geojson) {\r
9195         if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {\r
9196                 return geojson;\r
9197         }\r
9198 \r
9199         return {\r
9200                 type: 'Feature',\r
9201                 properties: {},\r
9202                 geometry: geojson\r
9203         };\r
9204 }\r
9205 \r
9206 var PointToGeoJSON = {\r
9207         toGeoJSON: function (precision) {\r
9208                 return getFeature(this, {\r
9209                         type: 'Point',\r
9210                         coordinates: latLngToCoords(this.getLatLng(), precision)\r
9211                 });\r
9212         }\r
9213 };\r
9214 \r
9215 // @namespace Marker\r
9216 // @section Other methods\r
9217 // @method toGeoJSON(precision?: Number|false): Object\r
9218 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.\r
9219 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).\r
9220 Marker.include(PointToGeoJSON);\r
9221 \r
9222 // @namespace CircleMarker\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 circle marker (as a GeoJSON `Point` Feature).\r
9226 Circle.include(PointToGeoJSON);\r
9227 CircleMarker.include(PointToGeoJSON);\r
9228 \r
9229 \r
9230 // @namespace Polyline\r
9231 // @method toGeoJSON(precision?: Number|false): Object\r
9232 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.\r
9233 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).\r
9234 Polyline.include({\r
9235         toGeoJSON: function (precision) {\r
9236                 var multi = !isFlat(this._latlngs);\r
9237 \r
9238                 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);\r
9239 \r
9240                 return getFeature(this, {\r
9241                         type: (multi ? 'Multi' : '') + 'LineString',\r
9242                         coordinates: coords\r
9243                 });\r
9244         }\r
9245 });\r
9246 \r
9247 // @namespace Polygon\r
9248 // @method toGeoJSON(precision?: Number|false): Object\r
9249 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.\r
9250 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).\r
9251 Polygon.include({\r
9252         toGeoJSON: function (precision) {\r
9253                 var holes = !isFlat(this._latlngs),\r
9254                     multi = holes && !isFlat(this._latlngs[0]);\r
9255 \r
9256                 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);\r
9257 \r
9258                 if (!holes) {\r
9259                         coords = [coords];\r
9260                 }\r
9261 \r
9262                 return getFeature(this, {\r
9263                         type: (multi ? 'Multi' : '') + 'Polygon',\r
9264                         coordinates: coords\r
9265                 });\r
9266         }\r
9267 });\r
9268 \r
9269 \r
9270 // @namespace LayerGroup\r
9271 LayerGroup.include({\r
9272         toMultiPoint: function (precision) {\r
9273                 var coords = [];\r
9274 \r
9275                 this.eachLayer(function (layer) {\r
9276                         coords.push(layer.toGeoJSON(precision).geometry.coordinates);\r
9277                 });\r
9278 \r
9279                 return getFeature(this, {\r
9280                         type: 'MultiPoint',\r
9281                         coordinates: coords\r
9282                 });\r
9283         },\r
9284 \r
9285         // @method toGeoJSON(precision?: Number|false): Object\r
9286         // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.\r
9287         // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).\r
9288         toGeoJSON: function (precision) {\r
9289 \r
9290                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;\r
9291 \r
9292                 if (type === 'MultiPoint') {\r
9293                         return this.toMultiPoint(precision);\r
9294                 }\r
9295 \r
9296                 var isGeometryCollection = type === 'GeometryCollection',\r
9297                     jsons = [];\r
9298 \r
9299                 this.eachLayer(function (layer) {\r
9300                         if (layer.toGeoJSON) {\r
9301                                 var json = layer.toGeoJSON(precision);\r
9302                                 if (isGeometryCollection) {\r
9303                                         jsons.push(json.geometry);\r
9304                                 } else {\r
9305                                         var feature = asFeature(json);\r
9306                                         // Squash nested feature collections\r
9307                                         if (feature.type === 'FeatureCollection') {\r
9308                                                 jsons.push.apply(jsons, feature.features);\r
9309                                         } else {\r
9310                                                 jsons.push(feature);\r
9311                                         }\r
9312                                 }\r
9313                         }\r
9314                 });\r
9315 \r
9316                 if (isGeometryCollection) {\r
9317                         return getFeature(this, {\r
9318                                 geometries: jsons,\r
9319                                 type: 'GeometryCollection'\r
9320                         });\r
9321                 }\r
9322 \r
9323                 return {\r
9324                         type: 'FeatureCollection',\r
9325                         features: jsons\r
9326                 };\r
9327         }\r
9328 });\r
9329 \r
9330 // @namespace GeoJSON\r
9331 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)\r
9332 // Creates a GeoJSON layer. Optionally accepts an object in\r
9333 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map\r
9334 // (you can alternatively add it later with `addData` method) and an `options` object.\r
9335 function geoJSON(geojson, options) {\r
9336         return new GeoJSON(geojson, options);\r
9337 }\r
9338 \r
9339 // Backward compatibility.\r
9340 var geoJson = geoJSON;
9341
9342 /*\r
9343  * @class ImageOverlay\r
9344  * @aka L.ImageOverlay\r
9345  * @inherits Interactive layer\r
9346  *\r
9347  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.\r
9348  *\r
9349  * @example\r
9350  *\r
9351  * ```js\r
9352  * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',\r
9353  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];\r
9354  * L.imageOverlay(imageUrl, imageBounds).addTo(map);\r
9355  * ```\r
9356  */\r
9357 \r
9358 var ImageOverlay = Layer.extend({\r
9359 \r
9360         // @section\r
9361         // @aka ImageOverlay options\r
9362         options: {\r
9363                 // @option opacity: Number = 1.0\r
9364                 // The opacity of the image overlay.\r
9365                 opacity: 1,\r
9366 \r
9367                 // @option alt: String = ''\r
9368                 // Text for the `alt` attribute of the image (useful for accessibility).\r
9369                 alt: '',\r
9370 \r
9371                 // @option interactive: Boolean = false\r
9372                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.\r
9373                 interactive: false,\r
9374 \r
9375                 // @option crossOrigin: Boolean|String = false\r
9376                 // Whether the crossOrigin attribute will be added to the image.\r
9377                 // 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
9378                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.\r
9379                 crossOrigin: false,\r
9380 \r
9381                 // @option errorOverlayUrl: String = ''\r
9382                 // URL to the overlay image to show in place of the overlay that failed to load.\r
9383                 errorOverlayUrl: '',\r
9384 \r
9385                 // @option zIndex: Number = 1\r
9386                 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.\r
9387                 zIndex: 1,\r
9388 \r
9389                 // @option className: String = ''\r
9390                 // A custom class name to assign to the image. Empty by default.\r
9391                 className: ''\r
9392         },\r
9393 \r
9394         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)\r
9395                 this._url = url;\r
9396                 this._bounds = toLatLngBounds(bounds);\r
9397 \r
9398                 setOptions(this, options);\r
9399         },\r
9400 \r
9401         onAdd: function () {\r
9402                 if (!this._image) {\r
9403                         this._initImage();\r
9404 \r
9405                         if (this.options.opacity < 1) {\r
9406                                 this._updateOpacity();\r
9407                         }\r
9408                 }\r
9409 \r
9410                 if (this.options.interactive) {\r
9411                         addClass(this._image, 'leaflet-interactive');\r
9412                         this.addInteractiveTarget(this._image);\r
9413                 }\r
9414 \r
9415                 this.getPane().appendChild(this._image);\r
9416                 this._reset();\r
9417         },\r
9418 \r
9419         onRemove: function () {\r
9420                 remove(this._image);\r
9421                 if (this.options.interactive) {\r
9422                         this.removeInteractiveTarget(this._image);\r
9423                 }\r
9424         },\r
9425 \r
9426         // @method setOpacity(opacity: Number): this\r
9427         // Sets the opacity of the overlay.\r
9428         setOpacity: function (opacity) {\r
9429                 this.options.opacity = opacity;\r
9430 \r
9431                 if (this._image) {\r
9432                         this._updateOpacity();\r
9433                 }\r
9434                 return this;\r
9435         },\r
9436 \r
9437         setStyle: function (styleOpts) {\r
9438                 if (styleOpts.opacity) {\r
9439                         this.setOpacity(styleOpts.opacity);\r
9440                 }\r
9441                 return this;\r
9442         },\r
9443 \r
9444         // @method bringToFront(): this\r
9445         // Brings the layer to the top of all overlays.\r
9446         bringToFront: function () {\r
9447                 if (this._map) {\r
9448                         toFront(this._image);\r
9449                 }\r
9450                 return this;\r
9451         },\r
9452 \r
9453         // @method bringToBack(): this\r
9454         // Brings the layer to the bottom of all overlays.\r
9455         bringToBack: function () {\r
9456                 if (this._map) {\r
9457                         toBack(this._image);\r
9458                 }\r
9459                 return this;\r
9460         },\r
9461 \r
9462         // @method setUrl(url: String): this\r
9463         // Changes the URL of the image.\r
9464         setUrl: function (url) {\r
9465                 this._url = url;\r
9466 \r
9467                 if (this._image) {\r
9468                         this._image.src = url;\r
9469                 }\r
9470                 return this;\r
9471         },\r
9472 \r
9473         // @method setBounds(bounds: LatLngBounds): this\r
9474         // Update the bounds that this ImageOverlay covers\r
9475         setBounds: function (bounds) {\r
9476                 this._bounds = toLatLngBounds(bounds);\r
9477 \r
9478                 if (this._map) {\r
9479                         this._reset();\r
9480                 }\r
9481                 return this;\r
9482         },\r
9483 \r
9484         getEvents: function () {\r
9485                 var events = {\r
9486                         zoom: this._reset,\r
9487                         viewreset: this._reset\r
9488                 };\r
9489 \r
9490                 if (this._zoomAnimated) {\r
9491                         events.zoomanim = this._animateZoom;\r
9492                 }\r
9493 \r
9494                 return events;\r
9495         },\r
9496 \r
9497         // @method setZIndex(value: Number): this\r
9498         // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.\r
9499         setZIndex: function (value) {\r
9500                 this.options.zIndex = value;\r
9501                 this._updateZIndex();\r
9502                 return this;\r
9503         },\r
9504 \r
9505         // @method getBounds(): LatLngBounds\r
9506         // Get the bounds that this ImageOverlay covers\r
9507         getBounds: function () {\r
9508                 return this._bounds;\r
9509         },\r
9510 \r
9511         // @method getElement(): HTMLElement\r
9512         // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)\r
9513         // used by this overlay.\r
9514         getElement: function () {\r
9515                 return this._image;\r
9516         },\r
9517 \r
9518         _initImage: function () {\r
9519                 var wasElementSupplied = this._url.tagName === 'IMG';\r
9520                 var img = this._image = wasElementSupplied ? this._url : create$1('img');\r
9521 \r
9522                 addClass(img, 'leaflet-image-layer');\r
9523                 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }\r
9524                 if (this.options.className) { addClass(img, this.options.className); }\r
9525 \r
9526                 img.onselectstart = falseFn;\r
9527                 img.onmousemove = falseFn;\r
9528 \r
9529                 // @event load: Event\r
9530                 // Fired when the ImageOverlay layer has loaded its image\r
9531                 img.onload = bind(this.fire, this, 'load');\r
9532                 img.onerror = bind(this._overlayOnError, this, 'error');\r
9533 \r
9534                 if (this.options.crossOrigin || this.options.crossOrigin === '') {\r
9535                         img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;\r
9536                 }\r
9537 \r
9538                 if (this.options.zIndex) {\r
9539                         this._updateZIndex();\r
9540                 }\r
9541 \r
9542                 if (wasElementSupplied) {\r
9543                         this._url = img.src;\r
9544                         return;\r
9545                 }\r
9546 \r
9547                 img.src = this._url;\r
9548                 img.alt = this.options.alt;\r
9549         },\r
9550 \r
9551         _animateZoom: function (e) {\r
9552                 var scale = this._map.getZoomScale(e.zoom),\r
9553                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;\r
9554 \r
9555                 setTransform(this._image, offset, scale);\r
9556         },\r
9557 \r
9558         _reset: function () {\r
9559                 var image = this._image,\r
9560                     bounds = new Bounds(\r
9561                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),\r
9562                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),\r
9563                     size = bounds.getSize();\r
9564 \r
9565                 setPosition(image, bounds.min);\r
9566 \r
9567                 image.style.width  = size.x + 'px';\r
9568                 image.style.height = size.y + 'px';\r
9569         },\r
9570 \r
9571         _updateOpacity: function () {\r
9572                 setOpacity(this._image, this.options.opacity);\r
9573         },\r
9574 \r
9575         _updateZIndex: function () {\r
9576                 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {\r
9577                         this._image.style.zIndex = this.options.zIndex;\r
9578                 }\r
9579         },\r
9580 \r
9581         _overlayOnError: function () {\r
9582                 // @event error: Event\r
9583                 // Fired when the ImageOverlay layer fails to load its image\r
9584                 this.fire('error');\r
9585 \r
9586                 var errorUrl = this.options.errorOverlayUrl;\r
9587                 if (errorUrl && this._url !== errorUrl) {\r
9588                         this._url = errorUrl;\r
9589                         this._image.src = errorUrl;\r
9590                 }\r
9591         },\r
9592 \r
9593         // @method getCenter(): LatLng\r
9594         // Returns the center of the ImageOverlay.\r
9595         getCenter: function () {\r
9596                 return this._bounds.getCenter();\r
9597         }\r
9598 });\r
9599 \r
9600 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)\r
9601 // Instantiates an image overlay object given the URL of the image and the\r
9602 // geographical bounds it is tied to.\r
9603 var imageOverlay = function (url, bounds, options) {\r
9604         return new ImageOverlay(url, bounds, options);\r
9605 };
9606
9607 /*\r
9608  * @class VideoOverlay\r
9609  * @aka L.VideoOverlay\r
9610  * @inherits ImageOverlay\r
9611  *\r
9612  * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.\r
9613  *\r
9614  * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)\r
9615  * HTML5 element.\r
9616  *\r
9617  * @example\r
9618  *\r
9619  * ```js\r
9620  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',\r
9621  *      videoBounds = [[ 32, -130], [ 13, -100]];\r
9622  * L.videoOverlay(videoUrl, videoBounds ).addTo(map);\r
9623  * ```\r
9624  */\r
9625 \r
9626 var VideoOverlay = ImageOverlay.extend({\r
9627 \r
9628         // @section\r
9629         // @aka VideoOverlay options\r
9630         options: {\r
9631                 // @option autoplay: Boolean = true\r
9632                 // Whether the video starts playing automatically when loaded.\r
9633                 // On some browsers autoplay will only work with `muted: true`\r
9634                 autoplay: true,\r
9635 \r
9636                 // @option loop: Boolean = true\r
9637                 // Whether the video will loop back to the beginning when played.\r
9638                 loop: true,\r
9639 \r
9640                 // @option keepAspectRatio: Boolean = true\r
9641                 // Whether the video will save aspect ratio after the projection.\r
9642                 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)\r
9643                 keepAspectRatio: true,\r
9644 \r
9645                 // @option muted: Boolean = false\r
9646                 // Whether the video starts on mute when loaded.\r
9647                 muted: false,\r
9648 \r
9649                 // @option playsInline: Boolean = true\r
9650                 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.\r
9651                 playsInline: true\r
9652         },\r
9653 \r
9654         _initImage: function () {\r
9655                 var wasElementSupplied = this._url.tagName === 'VIDEO';\r
9656                 var vid = this._image = wasElementSupplied ? this._url : create$1('video');\r
9657 \r
9658                 addClass(vid, 'leaflet-image-layer');\r
9659                 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }\r
9660                 if (this.options.className) { addClass(vid, this.options.className); }\r
9661 \r
9662                 vid.onselectstart = falseFn;\r
9663                 vid.onmousemove = falseFn;\r
9664 \r
9665                 // @event load: Event\r
9666                 // Fired when the video has finished loading the first frame\r
9667                 vid.onloadeddata = bind(this.fire, this, 'load');\r
9668 \r
9669                 if (wasElementSupplied) {\r
9670                         var sourceElements = vid.getElementsByTagName('source');\r
9671                         var sources = [];\r
9672                         for (var j = 0; j < sourceElements.length; j++) {\r
9673                                 sources.push(sourceElements[j].src);\r
9674                         }\r
9675 \r
9676                         this._url = (sourceElements.length > 0) ? sources : [vid.src];\r
9677                         return;\r
9678                 }\r
9679 \r
9680                 if (!isArray(this._url)) { this._url = [this._url]; }\r
9681 \r
9682                 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {\r
9683                         vid.style['objectFit'] = 'fill';\r
9684                 }\r
9685                 vid.autoplay = !!this.options.autoplay;\r
9686                 vid.loop = !!this.options.loop;\r
9687                 vid.muted = !!this.options.muted;\r
9688                 vid.playsInline = !!this.options.playsInline;\r
9689                 for (var i = 0; i < this._url.length; i++) {\r
9690                         var source = create$1('source');\r
9691                         source.src = this._url[i];\r
9692                         vid.appendChild(source);\r
9693                 }\r
9694         }\r
9695 \r
9696         // @method getElement(): HTMLVideoElement\r
9697         // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)\r
9698         // used by this overlay.\r
9699 });\r
9700 \r
9701 \r
9702 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)\r
9703 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the\r
9704 // geographical bounds it is tied to.\r
9705 \r
9706 function videoOverlay(video, bounds, options) {\r
9707         return new VideoOverlay(video, bounds, options);\r
9708 }
9709
9710 /*
9711  * @class SVGOverlay
9712  * @aka L.SVGOverlay
9713  * @inherits ImageOverlay
9714  *
9715  * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9716  *
9717  * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9718  *
9719  * @example
9720  *
9721  * ```js
9722  * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9723  * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9724  * svgElement.setAttribute('viewBox', "0 0 200 200");
9725  * 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"/>';
9726  * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9727  * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9728  * ```
9729  */
9730
9731 var SVGOverlay = ImageOverlay.extend({
9732         _initImage: function () {
9733                 var el = this._image = this._url;
9734
9735                 addClass(el, 'leaflet-image-layer');
9736                 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9737                 if (this.options.className) { addClass(el, this.options.className); }
9738
9739                 el.onselectstart = falseFn;
9740                 el.onmousemove = falseFn;
9741         }
9742
9743         // @method getElement(): SVGElement
9744         // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9745         // used by this overlay.
9746 });
9747
9748
9749 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9750 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9751 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9752
9753 function svgOverlay(el, bounds, options) {
9754         return new SVGOverlay(el, bounds, options);
9755 }
9756
9757 /*\r
9758  * @class DivOverlay\r
9759  * @inherits Interactive layer\r
9760  * @aka L.DivOverlay\r
9761  * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.\r
9762  */\r
9763 \r
9764 // @namespace DivOverlay\r
9765 var DivOverlay = Layer.extend({\r
9766 \r
9767         // @section\r
9768         // @aka DivOverlay options\r
9769         options: {\r
9770                 // @option interactive: Boolean = false\r
9771                 // If true, the popup/tooltip will listen to the mouse events.\r
9772                 interactive: false,\r
9773 \r
9774                 // @option offset: Point = Point(0, 0)\r
9775                 // The offset of the overlay position.\r
9776                 offset: [0, 0],\r
9777 \r
9778                 // @option className: String = ''\r
9779                 // A custom CSS class name to assign to the overlay.\r
9780                 className: '',\r
9781 \r
9782                 // @option pane: String = undefined\r
9783                 // `Map pane` where the overlay will be added.\r
9784                 pane: undefined,\r
9785 \r
9786                 // @option content: String|HTMLElement|Function = ''\r
9787                 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be\r
9788                 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.\r
9789                 content: ''\r
9790         },\r
9791 \r
9792         initialize: function (options, source) {\r
9793                 if (options && (options instanceof LatLng || isArray(options))) {\r
9794                         this._latlng = toLatLng(options);\r
9795                         setOptions(this, source);\r
9796                 } else {\r
9797                         setOptions(this, options);\r
9798                         this._source = source;\r
9799                 }\r
9800                 if (this.options.content) {\r
9801                         this._content = this.options.content;\r
9802                 }\r
9803         },\r
9804 \r
9805         // @method openOn(map: Map): this\r
9806         // Adds the overlay to the map.\r
9807         // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.\r
9808         openOn: function (map) {\r
9809                 map = arguments.length ? map : this._source._map; // experimental, not the part of public api\r
9810                 if (!map.hasLayer(this)) {\r
9811                         map.addLayer(this);\r
9812                 }\r
9813                 return this;\r
9814         },\r
9815 \r
9816         // @method close(): this\r
9817         // Closes the overlay.\r
9818         // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`\r
9819         // and `layer.closePopup()`/`.closeTooltip()`.\r
9820         close: function () {\r
9821                 if (this._map) {\r
9822                         this._map.removeLayer(this);\r
9823                 }\r
9824                 return this;\r
9825         },\r
9826 \r
9827         // @method toggle(layer?: Layer): this\r
9828         // Opens or closes the overlay bound to layer depending on its current state.\r
9829         // Argument may be omitted only for overlay bound to layer.\r
9830         // Alternative to `layer.togglePopup()`/`.toggleTooltip()`.\r
9831         toggle: function (layer) {\r
9832                 if (this._map) {\r
9833                         this.close();\r
9834                 } else {\r
9835                         if (arguments.length) {\r
9836                                 this._source = layer;\r
9837                         } else {\r
9838                                 layer = this._source;\r
9839                         }\r
9840                         this._prepareOpen();\r
9841 \r
9842                         // open the overlay on the map\r
9843                         this.openOn(layer._map);\r
9844                 }\r
9845                 return this;\r
9846         },\r
9847 \r
9848         onAdd: function (map) {\r
9849                 this._zoomAnimated = map._zoomAnimated;\r
9850 \r
9851                 if (!this._container) {\r
9852                         this._initLayout();\r
9853                 }\r
9854 \r
9855                 if (map._fadeAnimated) {\r
9856                         setOpacity(this._container, 0);\r
9857                 }\r
9858 \r
9859                 clearTimeout(this._removeTimeout);\r
9860                 this.getPane().appendChild(this._container);\r
9861                 this.update();\r
9862 \r
9863                 if (map._fadeAnimated) {\r
9864                         setOpacity(this._container, 1);\r
9865                 }\r
9866 \r
9867                 this.bringToFront();\r
9868 \r
9869                 if (this.options.interactive) {\r
9870                         addClass(this._container, 'leaflet-interactive');\r
9871                         this.addInteractiveTarget(this._container);\r
9872                 }\r
9873         },\r
9874 \r
9875         onRemove: function (map) {\r
9876                 if (map._fadeAnimated) {\r
9877                         setOpacity(this._container, 0);\r
9878                         this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);\r
9879                 } else {\r
9880                         remove(this._container);\r
9881                 }\r
9882 \r
9883                 if (this.options.interactive) {\r
9884                         removeClass(this._container, 'leaflet-interactive');\r
9885                         this.removeInteractiveTarget(this._container);\r
9886                 }\r
9887         },\r
9888 \r
9889         // @namespace DivOverlay\r
9890         // @method getLatLng: LatLng\r
9891         // Returns the geographical point of the overlay.\r
9892         getLatLng: function () {\r
9893                 return this._latlng;\r
9894         },\r
9895 \r
9896         // @method setLatLng(latlng: LatLng): this\r
9897         // Sets the geographical point where the overlay will open.\r
9898         setLatLng: function (latlng) {\r
9899                 this._latlng = toLatLng(latlng);\r
9900                 if (this._map) {\r
9901                         this._updatePosition();\r
9902                         this._adjustPan();\r
9903                 }\r
9904                 return this;\r
9905         },\r
9906 \r
9907         // @method getContent: String|HTMLElement\r
9908         // Returns the content of the overlay.\r
9909         getContent: function () {\r
9910                 return this._content;\r
9911         },\r
9912 \r
9913         // @method setContent(htmlContent: String|HTMLElement|Function): this\r
9914         // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.\r
9915         // The function should return a `String` or `HTMLElement` to be used in the overlay.\r
9916         setContent: function (content) {\r
9917                 this._content = content;\r
9918                 this.update();\r
9919                 return this;\r
9920         },\r
9921 \r
9922         // @method getElement: String|HTMLElement\r
9923         // Returns the HTML container of the overlay.\r
9924         getElement: function () {\r
9925                 return this._container;\r
9926         },\r
9927 \r
9928         // @method update: null\r
9929         // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.\r
9930         update: function () {\r
9931                 if (!this._map) { return; }\r
9932 \r
9933                 this._container.style.visibility = 'hidden';\r
9934 \r
9935                 this._updateContent();\r
9936                 this._updateLayout();\r
9937                 this._updatePosition();\r
9938 \r
9939                 this._container.style.visibility = '';\r
9940 \r
9941                 this._adjustPan();\r
9942         },\r
9943 \r
9944         getEvents: function () {\r
9945                 var events = {\r
9946                         zoom: this._updatePosition,\r
9947                         viewreset: this._updatePosition\r
9948                 };\r
9949 \r
9950                 if (this._zoomAnimated) {\r
9951                         events.zoomanim = this._animateZoom;\r
9952                 }\r
9953                 return events;\r
9954         },\r
9955 \r
9956         // @method isOpen: Boolean\r
9957         // Returns `true` when the overlay is visible on the map.\r
9958         isOpen: function () {\r
9959                 return !!this._map && this._map.hasLayer(this);\r
9960         },\r
9961 \r
9962         // @method bringToFront: this\r
9963         // Brings this overlay in front of other overlays (in the same map pane).\r
9964         bringToFront: function () {\r
9965                 if (this._map) {\r
9966                         toFront(this._container);\r
9967                 }\r
9968                 return this;\r
9969         },\r
9970 \r
9971         // @method bringToBack: this\r
9972         // Brings this overlay to the back of other overlays (in the same map pane).\r
9973         bringToBack: function () {\r
9974                 if (this._map) {\r
9975                         toBack(this._container);\r
9976                 }\r
9977                 return this;\r
9978         },\r
9979 \r
9980         // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)\r
9981         _prepareOpen: function (latlng) {\r
9982                 var source = this._source;\r
9983                 if (!source._map) { return false; }\r
9984 \r
9985                 if (source instanceof FeatureGroup) {\r
9986                         source = null;\r
9987                         var layers = this._source._layers;\r
9988                         for (var id in layers) {\r
9989                                 if (layers[id]._map) {\r
9990                                         source = layers[id];\r
9991                                         break;\r
9992                                 }\r
9993                         }\r
9994                         if (!source) { return false; } // Unable to get source layer.\r
9995 \r
9996                         // set overlay source to this layer\r
9997                         this._source = source;\r
9998                 }\r
9999 \r
10000                 if (!latlng) {\r
10001                         if (source.getCenter) {\r
10002                                 latlng = source.getCenter();\r
10003                         } else if (source.getLatLng) {\r
10004                                 latlng = source.getLatLng();\r
10005                         } else if (source.getBounds) {\r
10006                                 latlng = source.getBounds().getCenter();\r
10007                         } else {\r
10008                                 throw new Error('Unable to get source layer LatLng.');\r
10009                         }\r
10010                 }\r
10011                 this.setLatLng(latlng);\r
10012 \r
10013                 if (this._map) {\r
10014                         // update the overlay (content, layout, etc...)\r
10015                         this.update();\r
10016                 }\r
10017 \r
10018                 return true;\r
10019         },\r
10020 \r
10021         _updateContent: function () {\r
10022                 if (!this._content) { return; }\r
10023 \r
10024                 var node = this._contentNode;\r
10025                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;\r
10026 \r
10027                 if (typeof content === 'string') {\r
10028                         node.innerHTML = content;\r
10029                 } else {\r
10030                         while (node.hasChildNodes()) {\r
10031                                 node.removeChild(node.firstChild);\r
10032                         }\r
10033                         node.appendChild(content);\r
10034                 }\r
10035 \r
10036                 // @namespace DivOverlay\r
10037                 // @section DivOverlay events\r
10038                 // @event contentupdate: Event\r
10039                 // Fired when the content of the overlay is updated\r
10040                 this.fire('contentupdate');\r
10041         },\r
10042 \r
10043         _updatePosition: function () {\r
10044                 if (!this._map) { return; }\r
10045 \r
10046                 var pos = this._map.latLngToLayerPoint(this._latlng),\r
10047                     offset = toPoint(this.options.offset),\r
10048                     anchor = this._getAnchor();\r
10049 \r
10050                 if (this._zoomAnimated) {\r
10051                         setPosition(this._container, pos.add(anchor));\r
10052                 } else {\r
10053                         offset = offset.add(pos).add(anchor);\r
10054                 }\r
10055 \r
10056                 var bottom = this._containerBottom = -offset.y,\r
10057                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;\r
10058 \r
10059                 // bottom position the overlay in case the height of the overlay changes (images loading etc)\r
10060                 this._container.style.bottom = bottom + 'px';\r
10061                 this._container.style.left = left + 'px';\r
10062         },\r
10063 \r
10064         _getAnchor: function () {\r
10065                 return [0, 0];\r
10066         }\r
10067 \r
10068 });\r
10069 \r
10070 Map.include({\r
10071         _initOverlay: function (OverlayClass, content, latlng, options) {\r
10072                 var overlay = content;\r
10073                 if (!(overlay instanceof OverlayClass)) {\r
10074                         overlay = new OverlayClass(options).setContent(content);\r
10075                 }\r
10076                 if (latlng) {\r
10077                         overlay.setLatLng(latlng);\r
10078                 }\r
10079                 return overlay;\r
10080         }\r
10081 });\r
10082 \r
10083 \r
10084 Layer.include({\r
10085         _initOverlay: function (OverlayClass, old, content, options) {\r
10086                 var overlay = content;\r
10087                 if (overlay instanceof OverlayClass) {\r
10088                         setOptions(overlay, options);\r
10089                         overlay._source = this;\r
10090                 } else {\r
10091                         overlay = (old && !options) ? old : new OverlayClass(options, this);\r
10092                         overlay.setContent(content);\r
10093                 }\r
10094                 return overlay;\r
10095         }\r
10096 });
10097
10098 /*\r
10099  * @class Popup\r
10100  * @inherits DivOverlay\r
10101  * @aka L.Popup\r
10102  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to\r
10103  * open popups while making sure that only one popup is open at one time\r
10104  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.\r
10105  *\r
10106  * @example\r
10107  *\r
10108  * If you want to just bind a popup to marker click and then open it, it's really easy:\r
10109  *\r
10110  * ```js\r
10111  * marker.bindPopup(popupContent).openPopup();\r
10112  * ```\r
10113  * Path overlays like polylines also have a `bindPopup` method.\r
10114  *\r
10115  * A popup can be also standalone:\r
10116  *\r
10117  * ```js\r
10118  * var popup = L.popup()\r
10119  *      .setLatLng(latlng)\r
10120  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')\r
10121  *      .openOn(map);\r
10122  * ```\r
10123  * or\r
10124  * ```js\r
10125  * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')\r
10126  *      .openOn(map);\r
10127  * ```\r
10128  */\r
10129 \r
10130 \r
10131 // @namespace Popup\r
10132 var Popup = DivOverlay.extend({\r
10133 \r
10134         // @section\r
10135         // @aka Popup options\r
10136         options: {\r
10137                 // @option pane: String = 'popupPane'\r
10138                 // `Map pane` where the popup will be added.\r
10139                 pane: 'popupPane',\r
10140 \r
10141                 // @option offset: Point = Point(0, 7)\r
10142                 // The offset of the popup position.\r
10143                 offset: [0, 7],\r
10144 \r
10145                 // @option maxWidth: Number = 300\r
10146                 // Max width of the popup, in pixels.\r
10147                 maxWidth: 300,\r
10148 \r
10149                 // @option minWidth: Number = 50\r
10150                 // Min width of the popup, in pixels.\r
10151                 minWidth: 50,\r
10152 \r
10153                 // @option maxHeight: Number = null\r
10154                 // If set, creates a scrollable container of the given height\r
10155                 // inside a popup if its content exceeds it.\r
10156                 // The scrollable container can be styled using the\r
10157                 // `leaflet-popup-scrolled` CSS class selector.\r
10158                 maxHeight: null,\r
10159 \r
10160                 // @option autoPan: Boolean = true\r
10161                 // Set it to `false` if you don't want the map to do panning animation\r
10162                 // to fit the opened popup.\r
10163                 autoPan: true,\r
10164 \r
10165                 // @option autoPanPaddingTopLeft: Point = null\r
10166                 // The margin between the popup and the top left corner of the map\r
10167                 // view after autopanning was performed.\r
10168                 autoPanPaddingTopLeft: null,\r
10169 \r
10170                 // @option autoPanPaddingBottomRight: Point = null\r
10171                 // The margin between the popup and the bottom right corner of the map\r
10172                 // view after autopanning was performed.\r
10173                 autoPanPaddingBottomRight: null,\r
10174 \r
10175                 // @option autoPanPadding: Point = Point(5, 5)\r
10176                 // Equivalent of setting both top left and bottom right autopan padding to the same value.\r
10177                 autoPanPadding: [5, 5],\r
10178 \r
10179                 // @option keepInView: Boolean = false\r
10180                 // Set it to `true` if you want to prevent users from panning the popup\r
10181                 // off of the screen while it is open.\r
10182                 keepInView: false,\r
10183 \r
10184                 // @option closeButton: Boolean = true\r
10185                 // Controls the presence of a close button in the popup.\r
10186                 closeButton: true,\r
10187 \r
10188                 // @option autoClose: Boolean = true\r
10189                 // Set it to `false` if you want to override the default behavior of\r
10190                 // the popup closing when another popup is opened.\r
10191                 autoClose: true,\r
10192 \r
10193                 // @option closeOnEscapeKey: Boolean = true\r
10194                 // Set it to `false` if you want to override the default behavior of\r
10195                 // the ESC key for closing of the popup.\r
10196                 closeOnEscapeKey: true,\r
10197 \r
10198                 // @option closeOnClick: Boolean = *\r
10199                 // Set it if you want to override the default behavior of the popup closing when user clicks\r
10200                 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.\r
10201 \r
10202                 // @option className: String = ''\r
10203                 // A custom CSS class name to assign to the popup.\r
10204                 className: ''\r
10205         },\r
10206 \r
10207         // @namespace Popup\r
10208         // @method openOn(map: Map): this\r
10209         // Alternative to `map.openPopup(popup)`.\r
10210         // Adds the popup to the map and closes the previous one.\r
10211         openOn: function (map) {\r
10212                 map = arguments.length ? map : this._source._map; // experimental, not the part of public api\r
10213 \r
10214                 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {\r
10215                         map.removeLayer(map._popup);\r
10216                 }\r
10217                 map._popup = this;\r
10218 \r
10219                 return DivOverlay.prototype.openOn.call(this, map);\r
10220         },\r
10221 \r
10222         onAdd: function (map) {\r
10223                 DivOverlay.prototype.onAdd.call(this, map);\r
10224 \r
10225                 // @namespace Map\r
10226                 // @section Popup events\r
10227                 // @event popupopen: PopupEvent\r
10228                 // Fired when a popup is opened in the map\r
10229                 map.fire('popupopen', {popup: this});\r
10230 \r
10231                 if (this._source) {\r
10232                         // @namespace Layer\r
10233                         // @section Popup events\r
10234                         // @event popupopen: PopupEvent\r
10235                         // Fired when a popup bound to this layer is opened\r
10236                         this._source.fire('popupopen', {popup: this}, true);\r
10237                         // For non-path layers, we toggle the popup when clicking\r
10238                         // again the layer, so prevent the map to reopen it.\r
10239                         if (!(this._source instanceof Path)) {\r
10240                                 this._source.on('preclick', stopPropagation);\r
10241                         }\r
10242                 }\r
10243         },\r
10244 \r
10245         onRemove: function (map) {\r
10246                 DivOverlay.prototype.onRemove.call(this, map);\r
10247 \r
10248                 // @namespace Map\r
10249                 // @section Popup events\r
10250                 // @event popupclose: PopupEvent\r
10251                 // Fired when a popup in the map is closed\r
10252                 map.fire('popupclose', {popup: this});\r
10253 \r
10254                 if (this._source) {\r
10255                         // @namespace Layer\r
10256                         // @section Popup events\r
10257                         // @event popupclose: PopupEvent\r
10258                         // Fired when a popup bound to this layer is closed\r
10259                         this._source.fire('popupclose', {popup: this}, true);\r
10260                         if (!(this._source instanceof Path)) {\r
10261                                 this._source.off('preclick', stopPropagation);\r
10262                         }\r
10263                 }\r
10264         },\r
10265 \r
10266         getEvents: function () {\r
10267                 var events = DivOverlay.prototype.getEvents.call(this);\r
10268 \r
10269                 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {\r
10270                         events.preclick = this.close;\r
10271                 }\r
10272 \r
10273                 if (this.options.keepInView) {\r
10274                         events.moveend = this._adjustPan;\r
10275                 }\r
10276 \r
10277                 return events;\r
10278         },\r
10279 \r
10280         _initLayout: function () {\r
10281                 var prefix = 'leaflet-popup',\r
10282                     container = this._container = create$1('div',\r
10283                         prefix + ' ' + (this.options.className || '') +\r
10284                         ' leaflet-zoom-animated');\r
10285 \r
10286                 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);\r
10287                 this._contentNode = create$1('div', prefix + '-content', wrapper);\r
10288 \r
10289                 disableClickPropagation(container);\r
10290                 disableScrollPropagation(this._contentNode);\r
10291                 on(container, 'contextmenu', stopPropagation);\r
10292 \r
10293                 this._tipContainer = create$1('div', prefix + '-tip-container', container);\r
10294                 this._tip = create$1('div', prefix + '-tip', this._tipContainer);\r
10295 \r
10296                 if (this.options.closeButton) {\r
10297                         var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);\r
10298                         closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399\r
10299                         closeButton.setAttribute('aria-label', 'Close popup');\r
10300                         closeButton.href = '#close';\r
10301                         closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';\r
10302 \r
10303                         on(closeButton, 'click', function (ev) {\r
10304                                 preventDefault(ev);\r
10305                                 this.close();\r
10306                         }, this);\r
10307                 }\r
10308         },\r
10309 \r
10310         _updateLayout: function () {\r
10311                 var container = this._contentNode,\r
10312                     style = container.style;\r
10313 \r
10314                 style.width = '';\r
10315                 style.whiteSpace = 'nowrap';\r
10316 \r
10317                 var width = container.offsetWidth;\r
10318                 width = Math.min(width, this.options.maxWidth);\r
10319                 width = Math.max(width, this.options.minWidth);\r
10320 \r
10321                 style.width = (width + 1) + 'px';\r
10322                 style.whiteSpace = '';\r
10323 \r
10324                 style.height = '';\r
10325 \r
10326                 var height = container.offsetHeight,\r
10327                     maxHeight = this.options.maxHeight,\r
10328                     scrolledClass = 'leaflet-popup-scrolled';\r
10329 \r
10330                 if (maxHeight && height > maxHeight) {\r
10331                         style.height = maxHeight + 'px';\r
10332                         addClass(container, scrolledClass);\r
10333                 } else {\r
10334                         removeClass(container, scrolledClass);\r
10335                 }\r
10336 \r
10337                 this._containerWidth = this._container.offsetWidth;\r
10338         },\r
10339 \r
10340         _animateZoom: function (e) {\r
10341                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),\r
10342                     anchor = this._getAnchor();\r
10343                 setPosition(this._container, pos.add(anchor));\r
10344         },\r
10345 \r
10346         _adjustPan: function () {\r
10347                 if (!this.options.autoPan) { return; }\r
10348                 if (this._map._panAnim) { this._map._panAnim.stop(); }\r
10349 \r
10350                 // We can endlessly recurse if keepInView is set and the view resets.\r
10351                 // Let's guard against that by exiting early if we're responding to our own autopan.\r
10352                 if (this._autopanning) {\r
10353                         this._autopanning = false;\r
10354                         return;\r
10355                 }\r
10356 \r
10357                 var map = this._map,\r
10358                     marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,\r
10359                     containerHeight = this._container.offsetHeight + marginBottom,\r
10360                     containerWidth = this._containerWidth,\r
10361                     layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);\r
10362 \r
10363                 layerPos._add(getPosition(this._container));\r
10364 \r
10365                 var containerPos = map.layerPointToContainerPoint(layerPos),\r
10366                     padding = toPoint(this.options.autoPanPadding),\r
10367                     paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),\r
10368                     paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),\r
10369                     size = map.getSize(),\r
10370                     dx = 0,\r
10371                     dy = 0;\r
10372 \r
10373                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right\r
10374                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;\r
10375                 }\r
10376                 if (containerPos.x - dx - paddingTL.x < 0) { // left\r
10377                         dx = containerPos.x - paddingTL.x;\r
10378                 }\r
10379                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom\r
10380                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;\r
10381                 }\r
10382                 if (containerPos.y - dy - paddingTL.y < 0) { // top\r
10383                         dy = containerPos.y - paddingTL.y;\r
10384                 }\r
10385 \r
10386                 // @namespace Map\r
10387                 // @section Popup events\r
10388                 // @event autopanstart: Event\r
10389                 // Fired when the map starts autopanning when opening a popup.\r
10390                 if (dx || dy) {\r
10391                         // Track that we're autopanning, as this function will be re-ran on moveend\r
10392                         if (this.options.keepInView) {\r
10393                                 this._autopanning = true;\r
10394                         }\r
10395 \r
10396                         map\r
10397                             .fire('autopanstart')\r
10398                             .panBy([dx, dy]);\r
10399                 }\r
10400         },\r
10401 \r
10402         _getAnchor: function () {\r
10403                 // Where should we anchor the popup on the source layer?\r
10404                 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);\r
10405         }\r
10406 \r
10407 });\r
10408 \r
10409 // @namespace Popup\r
10410 // @factory L.popup(options?: Popup options, source?: Layer)\r
10411 // 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
10412 // @alternative\r
10413 // @factory L.popup(latlng: LatLng, options?: Popup options)\r
10414 // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.\r
10415 var popup = function (options, source) {\r
10416         return new Popup(options, source);\r
10417 };\r
10418 \r
10419 \r
10420 /* @namespace Map\r
10421  * @section Interaction Options\r
10422  * @option closePopupOnClick: Boolean = true\r
10423  * Set it to `false` if you don't want popups to close when user clicks the map.\r
10424  */\r
10425 Map.mergeOptions({\r
10426         closePopupOnClick: true\r
10427 });\r
10428 \r
10429 \r
10430 // @namespace Map\r
10431 // @section Methods for Layers and Controls\r
10432 Map.include({\r
10433         // @method openPopup(popup: Popup): this\r
10434         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).\r
10435         // @alternative\r
10436         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this\r
10437         // Creates a popup with the specified content and options and opens it in the given point on a map.\r
10438         openPopup: function (popup, latlng, options) {\r
10439                 this._initOverlay(Popup, popup, latlng, options)\r
10440                   .openOn(this);\r
10441 \r
10442                 return this;\r
10443         },\r
10444 \r
10445         // @method closePopup(popup?: Popup): this\r
10446         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).\r
10447         closePopup: function (popup) {\r
10448                 popup = arguments.length ? popup : this._popup;\r
10449                 if (popup) {\r
10450                         popup.close();\r
10451                 }\r
10452                 return this;\r
10453         }\r
10454 });\r
10455 \r
10456 /*\r
10457  * @namespace Layer\r
10458  * @section Popup methods example\r
10459  *\r
10460  * All layers share a set of methods convenient for binding popups to it.\r
10461  *\r
10462  * ```js\r
10463  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);\r
10464  * layer.openPopup();\r
10465  * layer.closePopup();\r
10466  * ```\r
10467  *\r
10468  * 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
10469  */\r
10470 \r
10471 // @section Popup methods\r
10472 Layer.include({\r
10473 \r
10474         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this\r
10475         // Binds a popup to the layer with the passed `content` and sets up the\r
10476         // necessary event listeners. If a `Function` is passed it will receive\r
10477         // the layer as the first argument and should return a `String` or `HTMLElement`.\r
10478         bindPopup: function (content, options) {\r
10479                 this._popup = this._initOverlay(Popup, this._popup, content, options);\r
10480                 if (!this._popupHandlersAdded) {\r
10481                         this.on({\r
10482                                 click: this._openPopup,\r
10483                                 keypress: this._onKeyPress,\r
10484                                 remove: this.closePopup,\r
10485                                 move: this._movePopup\r
10486                         });\r
10487                         this._popupHandlersAdded = true;\r
10488                 }\r
10489 \r
10490                 return this;\r
10491         },\r
10492 \r
10493         // @method unbindPopup(): this\r
10494         // Removes the popup previously bound with `bindPopup`.\r
10495         unbindPopup: function () {\r
10496                 if (this._popup) {\r
10497                         this.off({\r
10498                                 click: this._openPopup,\r
10499                                 keypress: this._onKeyPress,\r
10500                                 remove: this.closePopup,\r
10501                                 move: this._movePopup\r
10502                         });\r
10503                         this._popupHandlersAdded = false;\r
10504                         this._popup = null;\r
10505                 }\r
10506                 return this;\r
10507         },\r
10508 \r
10509         // @method openPopup(latlng?: LatLng): this\r
10510         // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.\r
10511         openPopup: function (latlng) {\r
10512                 if (this._popup) {\r
10513                         if (!(this instanceof FeatureGroup)) {\r
10514                                 this._popup._source = this;\r
10515                         }\r
10516                         if (this._popup._prepareOpen(latlng || this._latlng)) {\r
10517                                 // open the popup on the map\r
10518                                 this._popup.openOn(this._map);\r
10519                         }\r
10520                 }\r
10521                 return this;\r
10522         },\r
10523 \r
10524         // @method closePopup(): this\r
10525         // Closes the popup bound to this layer if it is open.\r
10526         closePopup: function () {\r
10527                 if (this._popup) {\r
10528                         this._popup.close();\r
10529                 }\r
10530                 return this;\r
10531         },\r
10532 \r
10533         // @method togglePopup(): this\r
10534         // Opens or closes the popup bound to this layer depending on its current state.\r
10535         togglePopup: function () {\r
10536                 if (this._popup) {\r
10537                         this._popup.toggle(this);\r
10538                 }\r
10539                 return this;\r
10540         },\r
10541 \r
10542         // @method isPopupOpen(): boolean\r
10543         // Returns `true` if the popup bound to this layer is currently open.\r
10544         isPopupOpen: function () {\r
10545                 return (this._popup ? this._popup.isOpen() : false);\r
10546         },\r
10547 \r
10548         // @method setPopupContent(content: String|HTMLElement|Popup): this\r
10549         // Sets the content of the popup bound to this layer.\r
10550         setPopupContent: function (content) {\r
10551                 if (this._popup) {\r
10552                         this._popup.setContent(content);\r
10553                 }\r
10554                 return this;\r
10555         },\r
10556 \r
10557         // @method getPopup(): Popup\r
10558         // Returns the popup bound to this layer.\r
10559         getPopup: function () {\r
10560                 return this._popup;\r
10561         },\r
10562 \r
10563         _openPopup: function (e) {\r
10564                 if (!this._popup || !this._map) {\r
10565                         return;\r
10566                 }\r
10567                 // prevent map click\r
10568                 stop(e);\r
10569 \r
10570                 var target = e.layer || e.target;\r
10571                 if (this._popup._source === target && !(target instanceof Path)) {\r
10572                         // treat it like a marker and figure out\r
10573                         // if we should toggle it open/closed\r
10574                         if (this._map.hasLayer(this._popup)) {\r
10575                                 this.closePopup();\r
10576                         } else {\r
10577                                 this.openPopup(e.latlng);\r
10578                         }\r
10579                         return;\r
10580                 }\r
10581                 this._popup._source = target;\r
10582                 this.openPopup(e.latlng);\r
10583         },\r
10584 \r
10585         _movePopup: function (e) {\r
10586                 this._popup.setLatLng(e.latlng);\r
10587         },\r
10588 \r
10589         _onKeyPress: function (e) {\r
10590                 if (e.originalEvent.keyCode === 13) {\r
10591                         this._openPopup(e);\r
10592                 }\r
10593         }\r
10594 });
10595
10596 /*
10597  * @class Tooltip
10598  * @inherits DivOverlay
10599  * @aka L.Tooltip
10600  * Used to display small texts on top of map layers.
10601  *
10602  * @example
10603  * If you want to just bind a tooltip to marker:
10604  *
10605  * ```js
10606  * marker.bindTooltip("my tooltip text").openTooltip();
10607  * ```
10608  * Path overlays like polylines also have a `bindTooltip` method.
10609  *
10610  * A tooltip can be also standalone:
10611  *
10612  * ```js
10613  * var tooltip = L.tooltip()
10614  *      .setLatLng(latlng)
10615  *      .setContent('Hello world!<br />This is a nice tooltip.')
10616  *      .addTo(map);
10617  * ```
10618  * or
10619  * ```js
10620  * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
10621  *      .addTo(map);
10622  * ```
10623  *
10624  *
10625  * Note about tooltip offset. Leaflet takes two options in consideration
10626  * for computing tooltip offsetting:
10627  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10628  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
10629  *   move it to the bottom. Negatives will move to the left and top.
10630  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10631  *   should adapt this value if you use a custom icon.
10632  */
10633
10634
10635 // @namespace Tooltip
10636 var Tooltip = DivOverlay.extend({
10637
10638         // @section
10639         // @aka Tooltip options
10640         options: {
10641                 // @option pane: String = 'tooltipPane'
10642                 // `Map pane` where the tooltip will be added.
10643                 pane: 'tooltipPane',
10644
10645                 // @option offset: Point = Point(0, 0)
10646                 // Optional offset of the tooltip position.
10647                 offset: [0, 0],
10648
10649                 // @option direction: String = 'auto'
10650                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10651                 // `top`, `bottom`, `center`, `auto`.
10652                 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10653                 // position on the map.
10654                 direction: 'auto',
10655
10656                 // @option permanent: Boolean = false
10657                 // Whether to open the tooltip permanently or only on mouseover.
10658                 permanent: false,
10659
10660                 // @option sticky: Boolean = false
10661                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10662                 sticky: false,
10663
10664                 // @option opacity: Number = 0.9
10665                 // Tooltip container opacity.
10666                 opacity: 0.9
10667         },
10668
10669         onAdd: function (map) {
10670                 DivOverlay.prototype.onAdd.call(this, map);
10671                 this.setOpacity(this.options.opacity);
10672
10673                 // @namespace Map
10674                 // @section Tooltip events
10675                 // @event tooltipopen: TooltipEvent
10676                 // Fired when a tooltip is opened in the map.
10677                 map.fire('tooltipopen', {tooltip: this});
10678
10679                 if (this._source) {
10680                         this.addEventParent(this._source);
10681
10682                         // @namespace Layer
10683                         // @section Tooltip events
10684                         // @event tooltipopen: TooltipEvent
10685                         // Fired when a tooltip bound to this layer is opened.
10686                         this._source.fire('tooltipopen', {tooltip: this}, true);
10687                 }
10688         },
10689
10690         onRemove: function (map) {
10691                 DivOverlay.prototype.onRemove.call(this, map);
10692
10693                 // @namespace Map
10694                 // @section Tooltip events
10695                 // @event tooltipclose: TooltipEvent
10696                 // Fired when a tooltip in the map is closed.
10697                 map.fire('tooltipclose', {tooltip: this});
10698
10699                 if (this._source) {
10700                         this.removeEventParent(this._source);
10701
10702                         // @namespace Layer
10703                         // @section Tooltip events
10704                         // @event tooltipclose: TooltipEvent
10705                         // Fired when a tooltip bound to this layer is closed.
10706                         this._source.fire('tooltipclose', {tooltip: this}, true);
10707                 }
10708         },
10709
10710         getEvents: function () {
10711                 var events = DivOverlay.prototype.getEvents.call(this);
10712
10713                 if (!this.options.permanent) {
10714                         events.preclick = this.close;
10715                 }
10716
10717                 return events;
10718         },
10719
10720         _initLayout: function () {
10721                 var prefix = 'leaflet-tooltip',
10722                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10723
10724                 this._contentNode = this._container = create$1('div', className);
10725
10726                 this._container.setAttribute('role', 'tooltip');
10727                 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this));
10728         },
10729
10730         _updateLayout: function () {},
10731
10732         _adjustPan: function () {},
10733
10734         _setPosition: function (pos) {
10735                 var subX, subY,
10736                     map = this._map,
10737                     container = this._container,
10738                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
10739                     tooltipPoint = map.layerPointToContainerPoint(pos),
10740                     direction = this.options.direction,
10741                     tooltipWidth = container.offsetWidth,
10742                     tooltipHeight = container.offsetHeight,
10743                     offset = toPoint(this.options.offset),
10744                     anchor = this._getAnchor();
10745
10746                 if (direction === 'top') {
10747                         subX = tooltipWidth / 2;
10748                         subY = tooltipHeight;
10749                 } else if (direction === 'bottom') {
10750                         subX = tooltipWidth / 2;
10751                         subY = 0;
10752                 } else if (direction === 'center') {
10753                         subX = tooltipWidth / 2;
10754                         subY = tooltipHeight / 2;
10755                 } else if (direction === 'right') {
10756                         subX = 0;
10757                         subY = tooltipHeight / 2;
10758                 } else if (direction === 'left') {
10759                         subX = tooltipWidth;
10760                         subY = tooltipHeight / 2;
10761                 } else if (tooltipPoint.x < centerPoint.x) {
10762                         direction = 'right';
10763                         subX = 0;
10764                         subY = tooltipHeight / 2;
10765                 } else {
10766                         direction = 'left';
10767                         subX = tooltipWidth + (offset.x + anchor.x) * 2;
10768                         subY = tooltipHeight / 2;
10769                 }
10770
10771                 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10772
10773                 removeClass(container, 'leaflet-tooltip-right');
10774                 removeClass(container, 'leaflet-tooltip-left');
10775                 removeClass(container, 'leaflet-tooltip-top');
10776                 removeClass(container, 'leaflet-tooltip-bottom');
10777                 addClass(container, 'leaflet-tooltip-' + direction);
10778                 setPosition(container, pos);
10779         },
10780
10781         _updatePosition: function () {
10782                 var pos = this._map.latLngToLayerPoint(this._latlng);
10783                 this._setPosition(pos);
10784         },
10785
10786         setOpacity: function (opacity) {
10787                 this.options.opacity = opacity;
10788
10789                 if (this._container) {
10790                         setOpacity(this._container, opacity);
10791                 }
10792         },
10793
10794         _animateZoom: function (e) {
10795                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10796                 this._setPosition(pos);
10797         },
10798
10799         _getAnchor: function () {
10800                 // Where should we anchor the tooltip on the source layer?
10801                 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10802         }
10803
10804 });
10805
10806 // @namespace Tooltip
10807 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10808 // 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.
10809 // @alternative
10810 // @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
10811 // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
10812 var tooltip = function (options, source) {
10813         return new Tooltip(options, source);
10814 };
10815
10816 // @namespace Map
10817 // @section Methods for Layers and Controls
10818 Map.include({
10819
10820         // @method openTooltip(tooltip: Tooltip): this
10821         // Opens the specified tooltip.
10822         // @alternative
10823         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10824         // Creates a tooltip with the specified content and options and open it.
10825         openTooltip: function (tooltip, latlng, options) {
10826                 this._initOverlay(Tooltip, tooltip, latlng, options)
10827                   .openOn(this);
10828
10829                 return this;
10830         },
10831
10832         // @method closeTooltip(tooltip: Tooltip): this
10833         // Closes the tooltip given as parameter.
10834         closeTooltip: function (tooltip) {
10835                 tooltip.close();
10836                 return this;
10837         }
10838
10839 });
10840
10841 /*
10842  * @namespace Layer
10843  * @section Tooltip methods example
10844  *
10845  * All layers share a set of methods convenient for binding tooltips to it.
10846  *
10847  * ```js
10848  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10849  * layer.openTooltip();
10850  * layer.closeTooltip();
10851  * ```
10852  */
10853
10854 // @section Tooltip methods
10855 Layer.include({
10856
10857         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10858         // Binds a tooltip to the layer with the passed `content` and sets up the
10859         // necessary event listeners. If a `Function` is passed it will receive
10860         // the layer as the first argument and should return a `String` or `HTMLElement`.
10861         bindTooltip: function (content, options) {
10862
10863                 if (this._tooltip && this.isTooltipOpen()) {
10864                         this.unbindTooltip();
10865                 }
10866
10867                 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
10868                 this._initTooltipInteractions();
10869
10870                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10871                         this.openTooltip();
10872                 }
10873
10874                 return this;
10875         },
10876
10877         // @method unbindTooltip(): this
10878         // Removes the tooltip previously bound with `bindTooltip`.
10879         unbindTooltip: function () {
10880                 if (this._tooltip) {
10881                         this._initTooltipInteractions(true);
10882                         this.closeTooltip();
10883                         this._tooltip = null;
10884                 }
10885                 return this;
10886         },
10887
10888         _initTooltipInteractions: function (remove) {
10889                 if (!remove && this._tooltipHandlersAdded) { return; }
10890                 var onOff = remove ? 'off' : 'on',
10891                     events = {
10892                         remove: this.closeTooltip,
10893                         move: this._moveTooltip
10894                     };
10895                 if (!this._tooltip.options.permanent) {
10896                         events.mouseover = this._openTooltip;
10897                         events.mouseout = this.closeTooltip;
10898                         events.click = this._openTooltip;
10899                         if (this._map) {
10900                                 this._addFocusListeners();
10901                         } else {
10902                                 events.add = this._addFocusListeners;
10903                         }
10904                 } else {
10905                         events.add = this._openTooltip;
10906                 }
10907                 if (this._tooltip.options.sticky) {
10908                         events.mousemove = this._moveTooltip;
10909                 }
10910                 this[onOff](events);
10911                 this._tooltipHandlersAdded = !remove;
10912         },
10913
10914         // @method openTooltip(latlng?: LatLng): this
10915         // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10916         openTooltip: function (latlng) {
10917                 if (this._tooltip) {
10918                         if (!(this instanceof FeatureGroup)) {
10919                                 this._tooltip._source = this;
10920                         }
10921                         if (this._tooltip._prepareOpen(latlng)) {
10922                                 // open the tooltip on the map
10923                                 this._tooltip.openOn(this._map);
10924
10925                                 if (this.getElement) {
10926                                         this._setAriaDescribedByOnLayer(this);
10927                                 } else if (this.eachLayer) {
10928                                         this.eachLayer(this._setAriaDescribedByOnLayer, this);
10929                                 }
10930                         }
10931                 }
10932                 return this;
10933         },
10934
10935         // @method closeTooltip(): this
10936         // Closes the tooltip bound to this layer if it is open.
10937         closeTooltip: function () {
10938                 if (this._tooltip) {
10939                         return this._tooltip.close();
10940                 }
10941         },
10942
10943         // @method toggleTooltip(): this
10944         // Opens or closes the tooltip bound to this layer depending on its current state.
10945         toggleTooltip: function () {
10946                 if (this._tooltip) {
10947                         this._tooltip.toggle(this);
10948                 }
10949                 return this;
10950         },
10951
10952         // @method isTooltipOpen(): boolean
10953         // Returns `true` if the tooltip bound to this layer is currently open.
10954         isTooltipOpen: function () {
10955                 return this._tooltip.isOpen();
10956         },
10957
10958         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10959         // Sets the content of the tooltip bound to this layer.
10960         setTooltipContent: function (content) {
10961                 if (this._tooltip) {
10962                         this._tooltip.setContent(content);
10963                 }
10964                 return this;
10965         },
10966
10967         // @method getTooltip(): Tooltip
10968         // Returns the tooltip bound to this layer.
10969         getTooltip: function () {
10970                 return this._tooltip;
10971         },
10972
10973         _addFocusListeners: function () {
10974                 if (this.getElement) {
10975                         this._addFocusListenersOnLayer(this);
10976                 } else if (this.eachLayer) {
10977                         this.eachLayer(this._addFocusListenersOnLayer, this);
10978                 }
10979         },
10980
10981         _addFocusListenersOnLayer: function (layer) {
10982                 var el = typeof layer.getElement === 'function' && layer.getElement();
10983                 if (el) {
10984                         on(el, 'focus', function () {
10985                                 this._tooltip._source = layer;
10986                                 this.openTooltip();
10987                         }, this);
10988                         on(el, 'blur', this.closeTooltip, this);
10989                 }
10990         },
10991
10992         _setAriaDescribedByOnLayer: function (layer) {
10993                 var el = typeof layer.getElement === 'function' && layer.getElement();
10994                 if (el) {
10995                         el.setAttribute('aria-describedby', this._tooltip._container.id);
10996                 }
10997         },
10998
10999
11000         _openTooltip: function (e) {
11001                 if (!this._tooltip || !this._map) {
11002                         return;
11003                 }
11004
11005                 // If the map is moving, we will show the tooltip after it's done.
11006                 if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) {
11007                         this._openOnceFlag = true;
11008                         var that = this;
11009                         this._map.once('moveend', function () {
11010                                 that._openOnceFlag = false;
11011                                 that._openTooltip(e);
11012                         });
11013                         return;
11014                 }
11015
11016                 this._tooltip._source = e.layer || e.target;
11017
11018                 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
11019         },
11020
11021         _moveTooltip: function (e) {
11022                 var latlng = e.latlng, containerPoint, layerPoint;
11023                 if (this._tooltip.options.sticky && e.originalEvent) {
11024                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
11025                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
11026                         latlng = this._map.layerPointToLatLng(layerPoint);
11027                 }
11028                 this._tooltip.setLatLng(latlng);
11029         }
11030 });
11031
11032 /*
11033  * @class DivIcon
11034  * @aka L.DivIcon
11035  * @inherits Icon
11036  *
11037  * Represents a lightweight icon for markers that uses a simple `<div>`
11038  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
11039  *
11040  * @example
11041  * ```js
11042  * var myIcon = L.divIcon({className: 'my-div-icon'});
11043  * // you can set .my-div-icon styles in CSS
11044  *
11045  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
11046  * ```
11047  *
11048  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
11049  */
11050
11051 var DivIcon = Icon.extend({
11052         options: {
11053                 // @section
11054                 // @aka DivIcon options
11055                 iconSize: [12, 12], // also can be set through CSS
11056
11057                 // iconAnchor: (Point),
11058                 // popupAnchor: (Point),
11059
11060                 // @option html: String|HTMLElement = ''
11061                 // Custom HTML code to put inside the div element, empty by default. Alternatively,
11062                 // an instance of `HTMLElement`.
11063                 html: false,
11064
11065                 // @option bgPos: Point = [0, 0]
11066                 // Optional relative position of the background, in pixels
11067                 bgPos: null,
11068
11069                 className: 'leaflet-div-icon'
11070         },
11071
11072         createIcon: function (oldIcon) {
11073                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
11074                     options = this.options;
11075
11076                 if (options.html instanceof Element) {
11077                         empty(div);
11078                         div.appendChild(options.html);
11079                 } else {
11080                         div.innerHTML = options.html !== false ? options.html : '';
11081                 }
11082
11083                 if (options.bgPos) {
11084                         var bgPos = toPoint(options.bgPos);
11085                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
11086                 }
11087                 this._setIconStyles(div, 'icon');
11088
11089                 return div;
11090         },
11091
11092         createShadow: function () {
11093                 return null;
11094         }
11095 });
11096
11097 // @factory L.divIcon(options: DivIcon options)
11098 // Creates a `DivIcon` instance with the given options.
11099 function divIcon(options) {
11100         return new DivIcon(options);
11101 }
11102
11103 Icon.Default = IconDefault;
11104
11105 /*
11106  * @class GridLayer
11107  * @inherits Layer
11108  * @aka L.GridLayer
11109  *
11110  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
11111  * 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.
11112  *
11113  *
11114  * @section Synchronous usage
11115  * @example
11116  *
11117  * 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.
11118  *
11119  * ```js
11120  * var CanvasLayer = L.GridLayer.extend({
11121  *     createTile: function(coords){
11122  *         // create a <canvas> element for drawing
11123  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11124  *
11125  *         // setup tile width and height according to the options
11126  *         var size = this.getTileSize();
11127  *         tile.width = size.x;
11128  *         tile.height = size.y;
11129  *
11130  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
11131  *         var ctx = tile.getContext('2d');
11132  *
11133  *         // return the tile so it can be rendered on screen
11134  *         return tile;
11135  *     }
11136  * });
11137  * ```
11138  *
11139  * @section Asynchronous usage
11140  * @example
11141  *
11142  * 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.
11143  *
11144  * ```js
11145  * var CanvasLayer = L.GridLayer.extend({
11146  *     createTile: function(coords, done){
11147  *         var error;
11148  *
11149  *         // create a <canvas> element for drawing
11150  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11151  *
11152  *         // setup tile width and height according to the options
11153  *         var size = this.getTileSize();
11154  *         tile.width = size.x;
11155  *         tile.height = size.y;
11156  *
11157  *         // draw something asynchronously and pass the tile to the done() callback
11158  *         setTimeout(function() {
11159  *             done(error, tile);
11160  *         }, 1000);
11161  *
11162  *         return tile;
11163  *     }
11164  * });
11165  * ```
11166  *
11167  * @section
11168  */
11169
11170
11171 var GridLayer = Layer.extend({
11172
11173         // @section
11174         // @aka GridLayer options
11175         options: {
11176                 // @option tileSize: Number|Point = 256
11177                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
11178                 tileSize: 256,
11179
11180                 // @option opacity: Number = 1.0
11181                 // Opacity of the tiles. Can be used in the `createTile()` function.
11182                 opacity: 1,
11183
11184                 // @option updateWhenIdle: Boolean = (depends)
11185                 // Load new tiles only when panning ends.
11186                 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
11187                 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
11188                 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
11189                 updateWhenIdle: Browser.mobile,
11190
11191                 // @option updateWhenZooming: Boolean = true
11192                 // 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.
11193                 updateWhenZooming: true,
11194
11195                 // @option updateInterval: Number = 200
11196                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
11197                 updateInterval: 200,
11198
11199                 // @option zIndex: Number = 1
11200                 // The explicit zIndex of the tile layer.
11201                 zIndex: 1,
11202
11203                 // @option bounds: LatLngBounds = undefined
11204                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
11205                 bounds: null,
11206
11207                 // @option minZoom: Number = 0
11208                 // The minimum zoom level down to which this layer will be displayed (inclusive).
11209                 minZoom: 0,
11210
11211                 // @option maxZoom: Number = undefined
11212                 // The maximum zoom level up to which this layer will be displayed (inclusive).
11213                 maxZoom: undefined,
11214
11215                 // @option maxNativeZoom: Number = undefined
11216                 // Maximum zoom number the tile source has available. If it is specified,
11217                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
11218                 // from `maxNativeZoom` level and auto-scaled.
11219                 maxNativeZoom: undefined,
11220
11221                 // @option minNativeZoom: Number = undefined
11222                 // Minimum zoom number the tile source has available. If it is specified,
11223                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
11224                 // from `minNativeZoom` level and auto-scaled.
11225                 minNativeZoom: undefined,
11226
11227                 // @option noWrap: Boolean = false
11228                 // Whether the layer is wrapped around the antimeridian. If `true`, the
11229                 // GridLayer will only be displayed once at low zoom levels. Has no
11230                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
11231                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
11232                 // tiles outside the CRS limits.
11233                 noWrap: false,
11234
11235                 // @option pane: String = 'tilePane'
11236                 // `Map pane` where the grid layer will be added.
11237                 pane: 'tilePane',
11238
11239                 // @option className: String = ''
11240                 // A custom class name to assign to the tile layer. Empty by default.
11241                 className: '',
11242
11243                 // @option keepBuffer: Number = 2
11244                 // When panning the map, keep this many rows and columns of tiles before unloading them.
11245                 keepBuffer: 2
11246         },
11247
11248         initialize: function (options) {
11249                 setOptions(this, options);
11250         },
11251
11252         onAdd: function () {
11253                 this._initContainer();
11254
11255                 this._levels = {};
11256                 this._tiles = {};
11257
11258                 this._resetView(); // implicit _update() call
11259         },
11260
11261         beforeAdd: function (map) {
11262                 map._addZoomLimit(this);
11263         },
11264
11265         onRemove: function (map) {
11266                 this._removeAllTiles();
11267                 remove(this._container);
11268                 map._removeZoomLimit(this);
11269                 this._container = null;
11270                 this._tileZoom = undefined;
11271         },
11272
11273         // @method bringToFront: this
11274         // Brings the tile layer to the top of all tile layers.
11275         bringToFront: function () {
11276                 if (this._map) {
11277                         toFront(this._container);
11278                         this._setAutoZIndex(Math.max);
11279                 }
11280                 return this;
11281         },
11282
11283         // @method bringToBack: this
11284         // Brings the tile layer to the bottom of all tile layers.
11285         bringToBack: function () {
11286                 if (this._map) {
11287                         toBack(this._container);
11288                         this._setAutoZIndex(Math.min);
11289                 }
11290                 return this;
11291         },
11292
11293         // @method getContainer: HTMLElement
11294         // Returns the HTML element that contains the tiles for this layer.
11295         getContainer: function () {
11296                 return this._container;
11297         },
11298
11299         // @method setOpacity(opacity: Number): this
11300         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
11301         setOpacity: function (opacity) {
11302                 this.options.opacity = opacity;
11303                 this._updateOpacity();
11304                 return this;
11305         },
11306
11307         // @method setZIndex(zIndex: Number): this
11308         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
11309         setZIndex: function (zIndex) {
11310                 this.options.zIndex = zIndex;
11311                 this._updateZIndex();
11312
11313                 return this;
11314         },
11315
11316         // @method isLoading: Boolean
11317         // Returns `true` if any tile in the grid layer has not finished loading.
11318         isLoading: function () {
11319                 return this._loading;
11320         },
11321
11322         // @method redraw: this
11323         // Causes the layer to clear all the tiles and request them again.
11324         redraw: function () {
11325                 if (this._map) {
11326                         this._removeAllTiles();
11327                         var tileZoom = this._clampZoom(this._map.getZoom());
11328                         if (tileZoom !== this._tileZoom) {
11329                                 this._tileZoom = tileZoom;
11330                                 this._updateLevels();
11331                         }
11332                         this._update();
11333                 }
11334                 return this;
11335         },
11336
11337         getEvents: function () {
11338                 var events = {
11339                         viewprereset: this._invalidateAll,
11340                         viewreset: this._resetView,
11341                         zoom: this._resetView,
11342                         moveend: this._onMoveEnd
11343                 };
11344
11345                 if (!this.options.updateWhenIdle) {
11346                         // update tiles on move, but not more often than once per given interval
11347                         if (!this._onMove) {
11348                                 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
11349                         }
11350
11351                         events.move = this._onMove;
11352                 }
11353
11354                 if (this._zoomAnimated) {
11355                         events.zoomanim = this._animateZoom;
11356                 }
11357
11358                 return events;
11359         },
11360
11361         // @section Extension methods
11362         // Layers extending `GridLayer` shall reimplement the following method.
11363         // @method createTile(coords: Object, done?: Function): HTMLElement
11364         // Called only internally, must be overridden by classes extending `GridLayer`.
11365         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
11366         // is specified, it must be called when the tile has finished loading and drawing.
11367         createTile: function () {
11368                 return document.createElement('div');
11369         },
11370
11371         // @section
11372         // @method getTileSize: Point
11373         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
11374         getTileSize: function () {
11375                 var s = this.options.tileSize;
11376                 return s instanceof Point ? s : new Point(s, s);
11377         },
11378
11379         _updateZIndex: function () {
11380                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
11381                         this._container.style.zIndex = this.options.zIndex;
11382                 }
11383         },
11384
11385         _setAutoZIndex: function (compare) {
11386                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
11387
11388                 var layers = this.getPane().children,
11389                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
11390
11391                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
11392
11393                         zIndex = layers[i].style.zIndex;
11394
11395                         if (layers[i] !== this._container && zIndex) {
11396                                 edgeZIndex = compare(edgeZIndex, +zIndex);
11397                         }
11398                 }
11399
11400                 if (isFinite(edgeZIndex)) {
11401                         this.options.zIndex = edgeZIndex + compare(-1, 1);
11402                         this._updateZIndex();
11403                 }
11404         },
11405
11406         _updateOpacity: function () {
11407                 if (!this._map) { return; }
11408
11409                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
11410                 if (Browser.ielt9) { return; }
11411
11412                 setOpacity(this._container, this.options.opacity);
11413
11414                 var now = +new Date(),
11415                     nextFrame = false,
11416                     willPrune = false;
11417
11418                 for (var key in this._tiles) {
11419                         var tile = this._tiles[key];
11420                         if (!tile.current || !tile.loaded) { continue; }
11421
11422                         var fade = Math.min(1, (now - tile.loaded) / 200);
11423
11424                         setOpacity(tile.el, fade);
11425                         if (fade < 1) {
11426                                 nextFrame = true;
11427                         } else {
11428                                 if (tile.active) {
11429                                         willPrune = true;
11430                                 } else {
11431                                         this._onOpaqueTile(tile);
11432                                 }
11433                                 tile.active = true;
11434                         }
11435                 }
11436
11437                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
11438
11439                 if (nextFrame) {
11440                         cancelAnimFrame(this._fadeFrame);
11441                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11442                 }
11443         },
11444
11445         _onOpaqueTile: falseFn,
11446
11447         _initContainer: function () {
11448                 if (this._container) { return; }
11449
11450                 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11451                 this._updateZIndex();
11452
11453                 if (this.options.opacity < 1) {
11454                         this._updateOpacity();
11455                 }
11456
11457                 this.getPane().appendChild(this._container);
11458         },
11459
11460         _updateLevels: function () {
11461
11462                 var zoom = this._tileZoom,
11463                     maxZoom = this.options.maxZoom;
11464
11465                 if (zoom === undefined) { return undefined; }
11466
11467                 for (var z in this._levels) {
11468                         z = Number(z);
11469                         if (this._levels[z].el.children.length || z === zoom) {
11470                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11471                                 this._onUpdateLevel(z);
11472                         } else {
11473                                 remove(this._levels[z].el);
11474                                 this._removeTilesAtZoom(z);
11475                                 this._onRemoveLevel(z);
11476                                 delete this._levels[z];
11477                         }
11478                 }
11479
11480                 var level = this._levels[zoom],
11481                     map = this._map;
11482
11483                 if (!level) {
11484                         level = this._levels[zoom] = {};
11485
11486                         level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11487                         level.el.style.zIndex = maxZoom;
11488
11489                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11490                         level.zoom = zoom;
11491
11492                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
11493
11494                         // force the browser to consider the newly added element for transition
11495                         falseFn(level.el.offsetWidth);
11496
11497                         this._onCreateLevel(level);
11498                 }
11499
11500                 this._level = level;
11501
11502                 return level;
11503         },
11504
11505         _onUpdateLevel: falseFn,
11506
11507         _onRemoveLevel: falseFn,
11508
11509         _onCreateLevel: falseFn,
11510
11511         _pruneTiles: function () {
11512                 if (!this._map) {
11513                         return;
11514                 }
11515
11516                 var key, tile;
11517
11518                 var zoom = this._map.getZoom();
11519                 if (zoom > this.options.maxZoom ||
11520                         zoom < this.options.minZoom) {
11521                         this._removeAllTiles();
11522                         return;
11523                 }
11524
11525                 for (key in this._tiles) {
11526                         tile = this._tiles[key];
11527                         tile.retain = tile.current;
11528                 }
11529
11530                 for (key in this._tiles) {
11531                         tile = this._tiles[key];
11532                         if (tile.current && !tile.active) {
11533                                 var coords = tile.coords;
11534                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11535                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11536                                 }
11537                         }
11538                 }
11539
11540                 for (key in this._tiles) {
11541                         if (!this._tiles[key].retain) {
11542                                 this._removeTile(key);
11543                         }
11544                 }
11545         },
11546
11547         _removeTilesAtZoom: function (zoom) {
11548                 for (var key in this._tiles) {
11549                         if (this._tiles[key].coords.z !== zoom) {
11550                                 continue;
11551                         }
11552                         this._removeTile(key);
11553                 }
11554         },
11555
11556         _removeAllTiles: function () {
11557                 for (var key in this._tiles) {
11558                         this._removeTile(key);
11559                 }
11560         },
11561
11562         _invalidateAll: function () {
11563                 for (var z in this._levels) {
11564                         remove(this._levels[z].el);
11565                         this._onRemoveLevel(Number(z));
11566                         delete this._levels[z];
11567                 }
11568                 this._removeAllTiles();
11569
11570                 this._tileZoom = undefined;
11571         },
11572
11573         _retainParent: function (x, y, z, minZoom) {
11574                 var x2 = Math.floor(x / 2),
11575                     y2 = Math.floor(y / 2),
11576                     z2 = z - 1,
11577                     coords2 = new Point(+x2, +y2);
11578                 coords2.z = +z2;
11579
11580                 var key = this._tileCoordsToKey(coords2),
11581                     tile = this._tiles[key];
11582
11583                 if (tile && tile.active) {
11584                         tile.retain = true;
11585                         return true;
11586
11587                 } else if (tile && tile.loaded) {
11588                         tile.retain = true;
11589                 }
11590
11591                 if (z2 > minZoom) {
11592                         return this._retainParent(x2, y2, z2, minZoom);
11593                 }
11594
11595                 return false;
11596         },
11597
11598         _retainChildren: function (x, y, z, maxZoom) {
11599
11600                 for (var i = 2 * x; i < 2 * x + 2; i++) {
11601                         for (var j = 2 * y; j < 2 * y + 2; j++) {
11602
11603                                 var coords = new Point(i, j);
11604                                 coords.z = z + 1;
11605
11606                                 var key = this._tileCoordsToKey(coords),
11607                                     tile = this._tiles[key];
11608
11609                                 if (tile && tile.active) {
11610                                         tile.retain = true;
11611                                         continue;
11612
11613                                 } else if (tile && tile.loaded) {
11614                                         tile.retain = true;
11615                                 }
11616
11617                                 if (z + 1 < maxZoom) {
11618                                         this._retainChildren(i, j, z + 1, maxZoom);
11619                                 }
11620                         }
11621                 }
11622         },
11623
11624         _resetView: function (e) {
11625                 var animating = e && (e.pinch || e.flyTo);
11626                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11627         },
11628
11629         _animateZoom: function (e) {
11630                 this._setView(e.center, e.zoom, true, e.noUpdate);
11631         },
11632
11633         _clampZoom: function (zoom) {
11634                 var options = this.options;
11635
11636                 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11637                         return options.minNativeZoom;
11638                 }
11639
11640                 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11641                         return options.maxNativeZoom;
11642                 }
11643
11644                 return zoom;
11645         },
11646
11647         _setView: function (center, zoom, noPrune, noUpdate) {
11648                 var tileZoom = Math.round(zoom);
11649                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11650                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11651                         tileZoom = undefined;
11652                 } else {
11653                         tileZoom = this._clampZoom(tileZoom);
11654                 }
11655
11656                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11657
11658                 if (!noUpdate || tileZoomChanged) {
11659
11660                         this._tileZoom = tileZoom;
11661
11662                         if (this._abortLoading) {
11663                                 this._abortLoading();
11664                         }
11665
11666                         this._updateLevels();
11667                         this._resetGrid();
11668
11669                         if (tileZoom !== undefined) {
11670                                 this._update(center);
11671                         }
11672
11673                         if (!noPrune) {
11674                                 this._pruneTiles();
11675                         }
11676
11677                         // Flag to prevent _updateOpacity from pruning tiles during
11678                         // a zoom anim or a pinch gesture
11679                         this._noPrune = !!noPrune;
11680                 }
11681
11682                 this._setZoomTransforms(center, zoom);
11683         },
11684
11685         _setZoomTransforms: function (center, zoom) {
11686                 for (var i in this._levels) {
11687                         this._setZoomTransform(this._levels[i], center, zoom);
11688                 }
11689         },
11690
11691         _setZoomTransform: function (level, center, zoom) {
11692                 var scale = this._map.getZoomScale(zoom, level.zoom),
11693                     translate = level.origin.multiplyBy(scale)
11694                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11695
11696                 if (Browser.any3d) {
11697                         setTransform(level.el, translate, scale);
11698                 } else {
11699                         setPosition(level.el, translate);
11700                 }
11701         },
11702
11703         _resetGrid: function () {
11704                 var map = this._map,
11705                     crs = map.options.crs,
11706                     tileSize = this._tileSize = this.getTileSize(),
11707                     tileZoom = this._tileZoom;
11708
11709                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11710                 if (bounds) {
11711                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
11712                 }
11713
11714                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11715                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11716                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11717                 ];
11718                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11719                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11720                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11721                 ];
11722         },
11723
11724         _onMoveEnd: function () {
11725                 if (!this._map || this._map._animatingZoom) { return; }
11726
11727                 this._update();
11728         },
11729
11730         _getTiledPixelBounds: function (center) {
11731                 var map = this._map,
11732                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11733                     scale = map.getZoomScale(mapZoom, this._tileZoom),
11734                     pixelCenter = map.project(center, this._tileZoom).floor(),
11735                     halfSize = map.getSize().divideBy(scale * 2);
11736
11737                 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11738         },
11739
11740         // Private method to load tiles in the grid's active zoom level according to map bounds
11741         _update: function (center) {
11742                 var map = this._map;
11743                 if (!map) { return; }
11744                 var zoom = this._clampZoom(map.getZoom());
11745
11746                 if (center === undefined) { center = map.getCenter(); }
11747                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
11748
11749                 var pixelBounds = this._getTiledPixelBounds(center),
11750                     tileRange = this._pxBoundsToTileRange(pixelBounds),
11751                     tileCenter = tileRange.getCenter(),
11752                     queue = [],
11753                     margin = this.options.keepBuffer,
11754                     noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11755                                               tileRange.getTopRight().add([margin, -margin]));
11756
11757                 // Sanity check: panic if the tile range contains Infinity somewhere.
11758                 if (!(isFinite(tileRange.min.x) &&
11759                       isFinite(tileRange.min.y) &&
11760                       isFinite(tileRange.max.x) &&
11761                       isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11762
11763                 for (var key in this._tiles) {
11764                         var c = this._tiles[key].coords;
11765                         if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11766                                 this._tiles[key].current = false;
11767                         }
11768                 }
11769
11770                 // _update just loads more tiles. If the tile zoom level differs too much
11771                 // from the map's, let _setView reset levels and prune old tiles.
11772                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11773
11774                 // create a queue of coordinates to load tiles from
11775                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11776                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11777                                 var coords = new Point(i, j);
11778                                 coords.z = this._tileZoom;
11779
11780                                 if (!this._isValidTile(coords)) { continue; }
11781
11782                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
11783                                 if (tile) {
11784                                         tile.current = true;
11785                                 } else {
11786                                         queue.push(coords);
11787                                 }
11788                         }
11789                 }
11790
11791                 // sort tile queue to load tiles in order of their distance to center
11792                 queue.sort(function (a, b) {
11793                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11794                 });
11795
11796                 if (queue.length !== 0) {
11797                         // if it's the first batch of tiles to load
11798                         if (!this._loading) {
11799                                 this._loading = true;
11800                                 // @event loading: Event
11801                                 // Fired when the grid layer starts loading tiles.
11802                                 this.fire('loading');
11803                         }
11804
11805                         // create DOM fragment to append tiles in one batch
11806                         var fragment = document.createDocumentFragment();
11807
11808                         for (i = 0; i < queue.length; i++) {
11809                                 this._addTile(queue[i], fragment);
11810                         }
11811
11812                         this._level.el.appendChild(fragment);
11813                 }
11814         },
11815
11816         _isValidTile: function (coords) {
11817                 var crs = this._map.options.crs;
11818
11819                 if (!crs.infinite) {
11820                         // don't load tile if it's out of bounds and not wrapped
11821                         var bounds = this._globalTileRange;
11822                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11823                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11824                 }
11825
11826                 if (!this.options.bounds) { return true; }
11827
11828                 // don't load tile if it doesn't intersect the bounds in options
11829                 var tileBounds = this._tileCoordsToBounds(coords);
11830                 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11831         },
11832
11833         _keyToBounds: function (key) {
11834                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11835         },
11836
11837         _tileCoordsToNwSe: function (coords) {
11838                 var map = this._map,
11839                     tileSize = this.getTileSize(),
11840                     nwPoint = coords.scaleBy(tileSize),
11841                     sePoint = nwPoint.add(tileSize),
11842                     nw = map.unproject(nwPoint, coords.z),
11843                     se = map.unproject(sePoint, coords.z);
11844                 return [nw, se];
11845         },
11846
11847         // converts tile coordinates to its geographical bounds
11848         _tileCoordsToBounds: function (coords) {
11849                 var bp = this._tileCoordsToNwSe(coords),
11850                     bounds = new LatLngBounds(bp[0], bp[1]);
11851
11852                 if (!this.options.noWrap) {
11853                         bounds = this._map.wrapLatLngBounds(bounds);
11854                 }
11855                 return bounds;
11856         },
11857         // converts tile coordinates to key for the tile cache
11858         _tileCoordsToKey: function (coords) {
11859                 return coords.x + ':' + coords.y + ':' + coords.z;
11860         },
11861
11862         // converts tile cache key to coordinates
11863         _keyToTileCoords: function (key) {
11864                 var k = key.split(':'),
11865                     coords = new Point(+k[0], +k[1]);
11866                 coords.z = +k[2];
11867                 return coords;
11868         },
11869
11870         _removeTile: function (key) {
11871                 var tile = this._tiles[key];
11872                 if (!tile) { return; }
11873
11874                 remove(tile.el);
11875
11876                 delete this._tiles[key];
11877
11878                 // @event tileunload: TileEvent
11879                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11880                 this.fire('tileunload', {
11881                         tile: tile.el,
11882                         coords: this._keyToTileCoords(key)
11883                 });
11884         },
11885
11886         _initTile: function (tile) {
11887                 addClass(tile, 'leaflet-tile');
11888
11889                 var tileSize = this.getTileSize();
11890                 tile.style.width = tileSize.x + 'px';
11891                 tile.style.height = tileSize.y + 'px';
11892
11893                 tile.onselectstart = falseFn;
11894                 tile.onmousemove = falseFn;
11895
11896                 // update opacity on tiles in IE7-8 because of filter inheritance problems
11897                 if (Browser.ielt9 && this.options.opacity < 1) {
11898                         setOpacity(tile, this.options.opacity);
11899                 }
11900         },
11901
11902         _addTile: function (coords, container) {
11903                 var tilePos = this._getTilePos(coords),
11904                     key = this._tileCoordsToKey(coords);
11905
11906                 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11907
11908                 this._initTile(tile);
11909
11910                 // if createTile is defined with a second argument ("done" callback),
11911                 // we know that tile is async and will be ready later; otherwise
11912                 if (this.createTile.length < 2) {
11913                         // mark tile as ready, but delay one frame for opacity animation to happen
11914                         requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11915                 }
11916
11917                 setPosition(tile, tilePos);
11918
11919                 // save tile in cache
11920                 this._tiles[key] = {
11921                         el: tile,
11922                         coords: coords,
11923                         current: true
11924                 };
11925
11926                 container.appendChild(tile);
11927                 // @event tileloadstart: TileEvent
11928                 // Fired when a tile is requested and starts loading.
11929                 this.fire('tileloadstart', {
11930                         tile: tile,
11931                         coords: coords
11932                 });
11933         },
11934
11935         _tileReady: function (coords, err, tile) {
11936                 if (err) {
11937                         // @event tileerror: TileErrorEvent
11938                         // Fired when there is an error loading a tile.
11939                         this.fire('tileerror', {
11940                                 error: err,
11941                                 tile: tile,
11942                                 coords: coords
11943                         });
11944                 }
11945
11946                 var key = this._tileCoordsToKey(coords);
11947
11948                 tile = this._tiles[key];
11949                 if (!tile) { return; }
11950
11951                 tile.loaded = +new Date();
11952                 if (this._map._fadeAnimated) {
11953                         setOpacity(tile.el, 0);
11954                         cancelAnimFrame(this._fadeFrame);
11955                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11956                 } else {
11957                         tile.active = true;
11958                         this._pruneTiles();
11959                 }
11960
11961                 if (!err) {
11962                         addClass(tile.el, 'leaflet-tile-loaded');
11963
11964                         // @event tileload: TileEvent
11965                         // Fired when a tile loads.
11966                         this.fire('tileload', {
11967                                 tile: tile.el,
11968                                 coords: coords
11969                         });
11970                 }
11971
11972                 if (this._noTilesToLoad()) {
11973                         this._loading = false;
11974                         // @event load: Event
11975                         // Fired when the grid layer loaded all visible tiles.
11976                         this.fire('load');
11977
11978                         if (Browser.ielt9 || !this._map._fadeAnimated) {
11979                                 requestAnimFrame(this._pruneTiles, this);
11980                         } else {
11981                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11982                                 // to trigger a pruning.
11983                                 setTimeout(bind(this._pruneTiles, this), 250);
11984                         }
11985                 }
11986         },
11987
11988         _getTilePos: function (coords) {
11989                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11990         },
11991
11992         _wrapCoords: function (coords) {
11993                 var newCoords = new Point(
11994                         this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11995                         this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11996                 newCoords.z = coords.z;
11997                 return newCoords;
11998         },
11999
12000         _pxBoundsToTileRange: function (bounds) {
12001                 var tileSize = this.getTileSize();
12002                 return new Bounds(
12003                         bounds.min.unscaleBy(tileSize).floor(),
12004                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
12005         },
12006
12007         _noTilesToLoad: function () {
12008                 for (var key in this._tiles) {
12009                         if (!this._tiles[key].loaded) { return false; }
12010                 }
12011                 return true;
12012         }
12013 });
12014
12015 // @factory L.gridLayer(options?: GridLayer options)
12016 // Creates a new instance of GridLayer with the supplied options.
12017 function gridLayer(options) {
12018         return new GridLayer(options);
12019 }
12020
12021 /*\r
12022  * @class TileLayer\r
12023  * @inherits GridLayer\r
12024  * @aka L.TileLayer\r
12025  * 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
12026  *\r
12027  * @example\r
12028  *\r
12029  * ```js\r
12030  * 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);
12031  * ```\r
12032  *\r
12033  * @section URL template\r
12034  * @example\r
12035  *\r
12036  * A string of the following form:\r
12037  *\r
12038  * ```\r
12039  * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'\r
12040  * ```\r
12041  *\r
12042  * `{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
12043  *\r
12044  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:\r
12045  *\r
12046  * ```\r
12047  * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});\r
12048  * ```\r
12049  */\r
12050 \r
12051 \r
12052 var TileLayer = GridLayer.extend({\r
12053 \r
12054         // @section\r
12055         // @aka TileLayer options\r
12056         options: {\r
12057                 // @option minZoom: Number = 0\r
12058                 // The minimum zoom level down to which this layer will be displayed (inclusive).\r
12059                 minZoom: 0,\r
12060 \r
12061                 // @option maxZoom: Number = 18\r
12062                 // The maximum zoom level up to which this layer will be displayed (inclusive).\r
12063                 maxZoom: 18,\r
12064 \r
12065                 // @option subdomains: String|String[] = 'abc'\r
12066                 // 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
12067                 subdomains: 'abc',\r
12068 \r
12069                 // @option errorTileUrl: String = ''\r
12070                 // URL to the tile image to show in place of the tile that failed to load.\r
12071                 errorTileUrl: '',\r
12072 \r
12073                 // @option zoomOffset: Number = 0\r
12074                 // The zoom number used in tile URLs will be offset with this value.\r
12075                 zoomOffset: 0,\r
12076 \r
12077                 // @option tms: Boolean = false\r
12078                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).\r
12079                 tms: false,\r
12080 \r
12081                 // @option zoomReverse: Boolean = false\r
12082                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)\r
12083                 zoomReverse: false,\r
12084 \r
12085                 // @option detectRetina: Boolean = false\r
12086                 // 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
12087                 detectRetina: false,\r
12088 \r
12089                 // @option crossOrigin: Boolean|String = false\r
12090                 // Whether the crossOrigin attribute will be added to the tiles.\r
12091                 // 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
12092                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.\r
12093                 crossOrigin: false,\r
12094 \r
12095                 // @option referrerPolicy: Boolean|String = false\r
12096                 // Whether the referrerPolicy attribute will be added to the tiles.\r
12097                 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.\r
12098                 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer\r
12099                 // (e.g. to validate an API token).\r
12100                 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.\r
12101                 referrerPolicy: false\r
12102         },\r
12103 \r
12104         initialize: function (url, options) {\r
12105 \r
12106                 this._url = url;\r
12107 \r
12108                 options = setOptions(this, options);\r
12109 \r
12110                 // detecting retina displays, adjusting tileSize and zoom levels\r
12111                 if (options.detectRetina && Browser.retina && options.maxZoom > 0) {\r
12112 \r
12113                         options.tileSize = Math.floor(options.tileSize / 2);\r
12114 \r
12115                         if (!options.zoomReverse) {\r
12116                                 options.zoomOffset++;\r
12117                                 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);\r
12118                         } else {\r
12119                                 options.zoomOffset--;\r
12120                                 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);\r
12121                         }\r
12122 \r
12123                         options.minZoom = Math.max(0, options.minZoom);\r
12124                 } else if (!options.zoomReverse) {\r
12125                         // make sure maxZoom is gte minZoom\r
12126                         options.maxZoom = Math.max(options.minZoom, options.maxZoom);\r
12127                 } else {\r
12128                         // make sure minZoom is lte maxZoom\r
12129                         options.minZoom = Math.min(options.maxZoom, options.minZoom);\r
12130                 }\r
12131 \r
12132                 if (typeof options.subdomains === 'string') {\r
12133                         options.subdomains = options.subdomains.split('');\r
12134                 }\r
12135 \r
12136                 this.on('tileunload', this._onTileRemove);\r
12137         },\r
12138 \r
12139         // @method setUrl(url: String, noRedraw?: Boolean): this\r
12140         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).\r
12141         // If the URL does not change, the layer will not be redrawn unless\r
12142         // the noRedraw parameter is set to false.\r
12143         setUrl: function (url, noRedraw) {\r
12144                 if (this._url === url && noRedraw === undefined) {\r
12145                         noRedraw = true;\r
12146                 }\r
12147 \r
12148                 this._url = url;\r
12149 \r
12150                 if (!noRedraw) {\r
12151                         this.redraw();\r
12152                 }\r
12153                 return this;\r
12154         },\r
12155 \r
12156         // @method createTile(coords: Object, done?: Function): HTMLElement\r
12157         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)\r
12158         // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`\r
12159         // callback is called when the tile has been loaded.\r
12160         createTile: function (coords, done) {\r
12161                 var tile = document.createElement('img');\r
12162 \r
12163                 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));\r
12164                 on(tile, 'error', bind(this._tileOnError, this, done, tile));\r
12165 \r
12166                 if (this.options.crossOrigin || this.options.crossOrigin === '') {\r
12167                         tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;\r
12168                 }\r
12169 \r
12170                 // for this new option we follow the documented behavior\r
12171                 // more closely by only setting the property when string\r
12172                 if (typeof this.options.referrerPolicy === 'string') {\r
12173                         tile.referrerPolicy = this.options.referrerPolicy;\r
12174                 }\r
12175 \r
12176                 // The alt attribute is set to the empty string,\r
12177                 // allowing screen readers to ignore the decorative image tiles.\r
12178                 // https://www.w3.org/WAI/tutorials/images/decorative/\r
12179                 // https://www.w3.org/TR/html-aria/#el-img-empty-alt\r
12180                 tile.alt = '';\r
12181 \r
12182                 tile.src = this.getTileUrl(coords);\r
12183 \r
12184                 return tile;\r
12185         },\r
12186 \r
12187         // @section Extension methods\r
12188         // @uninheritable\r
12189         // Layers extending `TileLayer` might reimplement the following method.\r
12190         // @method getTileUrl(coords: Object): String\r
12191         // Called only internally, returns the URL for a tile given its coordinates.\r
12192         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.\r
12193         getTileUrl: function (coords) {\r
12194                 var data = {\r
12195                         r: Browser.retina ? '@2x' : '',\r
12196                         s: this._getSubdomain(coords),\r
12197                         x: coords.x,\r
12198                         y: coords.y,\r
12199                         z: this._getZoomForUrl()\r
12200                 };\r
12201                 if (this._map && !this._map.options.crs.infinite) {\r
12202                         var invertedY = this._globalTileRange.max.y - coords.y;\r
12203                         if (this.options.tms) {\r
12204                                 data['y'] = invertedY;\r
12205                         }\r
12206                         data['-y'] = invertedY;\r
12207                 }\r
12208 \r
12209                 return template(this._url, extend(data, this.options));\r
12210         },\r
12211 \r
12212         _tileOnLoad: function (done, tile) {\r
12213                 // For https://github.com/Leaflet/Leaflet/issues/3332\r
12214                 if (Browser.ielt9) {\r
12215                         setTimeout(bind(done, this, null, tile), 0);\r
12216                 } else {\r
12217                         done(null, tile);\r
12218                 }\r
12219         },\r
12220 \r
12221         _tileOnError: function (done, tile, e) {\r
12222                 var errorUrl = this.options.errorTileUrl;\r
12223                 if (errorUrl && tile.getAttribute('src') !== errorUrl) {\r
12224                         tile.src = errorUrl;\r
12225                 }\r
12226                 done(e, tile);\r
12227         },\r
12228 \r
12229         _onTileRemove: function (e) {\r
12230                 e.tile.onload = null;\r
12231         },\r
12232 \r
12233         _getZoomForUrl: function () {\r
12234                 var zoom = this._tileZoom,\r
12235                 maxZoom = this.options.maxZoom,\r
12236                 zoomReverse = this.options.zoomReverse,\r
12237                 zoomOffset = this.options.zoomOffset;\r
12238 \r
12239                 if (zoomReverse) {\r
12240                         zoom = maxZoom - zoom;\r
12241                 }\r
12242 \r
12243                 return zoom + zoomOffset;\r
12244         },\r
12245 \r
12246         _getSubdomain: function (tilePoint) {\r
12247                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;\r
12248                 return this.options.subdomains[index];\r
12249         },\r
12250 \r
12251         // stops loading all tiles in the background layer\r
12252         _abortLoading: function () {\r
12253                 var i, tile;\r
12254                 for (i in this._tiles) {\r
12255                         if (this._tiles[i].coords.z !== this._tileZoom) {\r
12256                                 tile = this._tiles[i].el;\r
12257 \r
12258                                 tile.onload = falseFn;\r
12259                                 tile.onerror = falseFn;\r
12260 \r
12261                                 if (!tile.complete) {\r
12262                                         tile.src = emptyImageUrl;\r
12263                                         var coords = this._tiles[i].coords;\r
12264                                         remove(tile);\r
12265                                         delete this._tiles[i];\r
12266                                         // @event tileabort: TileEvent\r
12267                                         // Fired when a tile was loading but is now not wanted.\r
12268                                         this.fire('tileabort', {\r
12269                                                 tile: tile,\r
12270                                                 coords: coords\r
12271                                         });\r
12272                                 }\r
12273                         }\r
12274                 }\r
12275         },\r
12276 \r
12277         _removeTile: function (key) {\r
12278                 var tile = this._tiles[key];\r
12279                 if (!tile) { return; }\r
12280 \r
12281                 // Cancels any pending http requests associated with the tile\r
12282                 tile.el.setAttribute('src', emptyImageUrl);\r
12283 \r
12284                 return GridLayer.prototype._removeTile.call(this, key);\r
12285         },\r
12286 \r
12287         _tileReady: function (coords, err, tile) {\r
12288                 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {\r
12289                         return;\r
12290                 }\r
12291 \r
12292                 return GridLayer.prototype._tileReady.call(this, coords, err, tile);\r
12293         }\r
12294 });\r
12295 \r
12296 \r
12297 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)\r
12298 // Instantiates a tile layer object given a `URL template` and optionally an options object.\r
12299 \r
12300 function tileLayer(url, options) {\r
12301         return new TileLayer(url, options);\r
12302 }
12303
12304 /*\r
12305  * @class TileLayer.WMS\r
12306  * @inherits TileLayer\r
12307  * @aka L.TileLayer.WMS\r
12308  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.\r
12309  *\r
12310  * @example\r
12311  *\r
12312  * ```js\r
12313  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {\r
12314  *      layers: 'nexrad-n0r-900913',\r
12315  *      format: 'image/png',\r
12316  *      transparent: true,\r
12317  *      attribution: "Weather data © 2012 IEM Nexrad"\r
12318  * });\r
12319  * ```\r
12320  */\r
12321 \r
12322 var TileLayerWMS = TileLayer.extend({\r
12323 \r
12324         // @section\r
12325         // @aka TileLayer.WMS options\r
12326         // If any custom options not documented here are used, they will be sent to the\r
12327         // WMS server as extra parameters in each request URL. This can be useful for\r
12328         // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).\r
12329         defaultWmsParams: {\r
12330                 service: 'WMS',\r
12331                 request: 'GetMap',\r
12332 \r
12333                 // @option layers: String = ''\r
12334                 // **(required)** Comma-separated list of WMS layers to show.\r
12335                 layers: '',\r
12336 \r
12337                 // @option styles: String = ''\r
12338                 // Comma-separated list of WMS styles.\r
12339                 styles: '',\r
12340 \r
12341                 // @option format: String = 'image/jpeg'\r
12342                 // WMS image format (use `'image/png'` for layers with transparency).\r
12343                 format: 'image/jpeg',\r
12344 \r
12345                 // @option transparent: Boolean = false\r
12346                 // If `true`, the WMS service will return images with transparency.\r
12347                 transparent: false,\r
12348 \r
12349                 // @option version: String = '1.1.1'\r
12350                 // Version of the WMS service to use\r
12351                 version: '1.1.1'\r
12352         },\r
12353 \r
12354         options: {\r
12355                 // @option crs: CRS = null\r
12356                 // Coordinate Reference System to use for the WMS requests, defaults to\r
12357                 // map CRS. Don't change this if you're not sure what it means.\r
12358                 crs: null,\r
12359 \r
12360                 // @option uppercase: Boolean = false\r
12361                 // If `true`, WMS request parameter keys will be uppercase.\r
12362                 uppercase: false\r
12363         },\r
12364 \r
12365         initialize: function (url, options) {\r
12366 \r
12367                 this._url = url;\r
12368 \r
12369                 var wmsParams = extend({}, this.defaultWmsParams);\r
12370 \r
12371                 // all keys that are not TileLayer options go to WMS params\r
12372                 for (var i in options) {\r
12373                         if (!(i in this.options)) {\r
12374                                 wmsParams[i] = options[i];\r
12375                         }\r
12376                 }\r
12377 \r
12378                 options = setOptions(this, options);\r
12379 \r
12380                 var realRetina = options.detectRetina && Browser.retina ? 2 : 1;\r
12381                 var tileSize = this.getTileSize();\r
12382                 wmsParams.width = tileSize.x * realRetina;\r
12383                 wmsParams.height = tileSize.y * realRetina;\r
12384 \r
12385                 this.wmsParams = wmsParams;\r
12386         },\r
12387 \r
12388         onAdd: function (map) {\r
12389 \r
12390                 this._crs = this.options.crs || map.options.crs;\r
12391                 this._wmsVersion = parseFloat(this.wmsParams.version);\r
12392 \r
12393                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';\r
12394                 this.wmsParams[projectionKey] = this._crs.code;\r
12395 \r
12396                 TileLayer.prototype.onAdd.call(this, map);\r
12397         },\r
12398 \r
12399         getTileUrl: function (coords) {\r
12400 \r
12401                 var tileBounds = this._tileCoordsToNwSe(coords),\r
12402                     crs = this._crs,\r
12403                     bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),\r
12404                     min = bounds.min,\r
12405                     max = bounds.max,\r
12406                     bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?\r
12407                     [min.y, min.x, max.y, max.x] :\r
12408                     [min.x, min.y, max.x, max.y]).join(','),\r
12409                     url = TileLayer.prototype.getTileUrl.call(this, coords);\r
12410                 return url +\r
12411                         getParamString(this.wmsParams, url, this.options.uppercase) +\r
12412                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;\r
12413         },\r
12414 \r
12415         // @method setParams(params: Object, noRedraw?: Boolean): this\r
12416         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).\r
12417         setParams: function (params, noRedraw) {\r
12418 \r
12419                 extend(this.wmsParams, params);\r
12420 \r
12421                 if (!noRedraw) {\r
12422                         this.redraw();\r
12423                 }\r
12424 \r
12425                 return this;\r
12426         }\r
12427 });\r
12428 \r
12429 \r
12430 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)\r
12431 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.\r
12432 function tileLayerWMS(url, options) {\r
12433         return new TileLayerWMS(url, options);\r
12434 }
12435
12436 TileLayer.WMS = TileLayerWMS;
12437 tileLayer.wms = tileLayerWMS;
12438
12439 /*
12440  * @class Renderer
12441  * @inherits Layer
12442  * @aka L.Renderer
12443  *
12444  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
12445  * DOM container of the renderer, its bounds, and its zoom animation.
12446  *
12447  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
12448  * itself can be added or removed to the map. All paths use a renderer, which can
12449  * be implicit (the map will decide the type of renderer and use it automatically)
12450  * or explicit (using the [`renderer`](#path-renderer) option of the path).
12451  *
12452  * Do not use this class directly, use `SVG` and `Canvas` instead.
12453  *
12454  * @event update: Event
12455  * Fired when the renderer updates its bounds, center and zoom, for example when
12456  * its map has moved
12457  */
12458
12459 var Renderer = Layer.extend({
12460
12461         // @section
12462         // @aka Renderer options
12463         options: {
12464                 // @option padding: Number = 0.1
12465                 // How much to extend the clip area around the map view (relative to its size)
12466                 // e.g. 0.1 would be 10% of map view in each direction
12467                 padding: 0.1
12468         },
12469
12470         initialize: function (options) {
12471                 setOptions(this, options);
12472                 stamp(this);
12473                 this._layers = this._layers || {};
12474         },
12475
12476         onAdd: function () {
12477                 if (!this._container) {
12478                         this._initContainer(); // defined by renderer implementations
12479
12480                         // always keep transform-origin as 0 0
12481                         addClass(this._container, 'leaflet-zoom-animated');
12482                 }
12483
12484                 this.getPane().appendChild(this._container);
12485                 this._update();
12486                 this.on('update', this._updatePaths, this);
12487         },
12488
12489         onRemove: function () {
12490                 this.off('update', this._updatePaths, this);
12491                 this._destroyContainer();
12492         },
12493
12494         getEvents: function () {
12495                 var events = {
12496                         viewreset: this._reset,
12497                         zoom: this._onZoom,
12498                         moveend: this._update,
12499                         zoomend: this._onZoomEnd
12500                 };
12501                 if (this._zoomAnimated) {
12502                         events.zoomanim = this._onAnimZoom;
12503                 }
12504                 return events;
12505         },
12506
12507         _onAnimZoom: function (ev) {
12508                 this._updateTransform(ev.center, ev.zoom);
12509         },
12510
12511         _onZoom: function () {
12512                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12513         },
12514
12515         _updateTransform: function (center, zoom) {
12516                 var scale = this._map.getZoomScale(zoom, this._zoom),
12517                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12518                     currentCenterPoint = this._map.project(this._center, zoom),
12519
12520                     topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
12521                                   .subtract(this._map._getNewPixelOrigin(center, zoom));
12522
12523                 if (Browser.any3d) {
12524                         setTransform(this._container, topLeftOffset, scale);
12525                 } else {
12526                         setPosition(this._container, topLeftOffset);
12527                 }
12528         },
12529
12530         _reset: function () {
12531                 this._update();
12532                 this._updateTransform(this._center, this._zoom);
12533
12534                 for (var id in this._layers) {
12535                         this._layers[id]._reset();
12536                 }
12537         },
12538
12539         _onZoomEnd: function () {
12540                 for (var id in this._layers) {
12541                         this._layers[id]._project();
12542                 }
12543         },
12544
12545         _updatePaths: function () {
12546                 for (var id in this._layers) {
12547                         this._layers[id]._update();
12548                 }
12549         },
12550
12551         _update: function () {
12552                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12553                 // Subclasses are responsible of firing the 'update' event.
12554                 var p = this.options.padding,
12555                     size = this._map.getSize(),
12556                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12557
12558                 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12559
12560                 this._center = this._map.getCenter();
12561                 this._zoom = this._map.getZoom();
12562         }
12563 });
12564
12565 /*
12566  * @class Canvas
12567  * @inherits Renderer
12568  * @aka L.Canvas
12569  *
12570  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12571  * Inherits `Renderer`.
12572  *
12573  * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
12574  * available in all web browsers, notably IE8, and overlapping geometries might
12575  * not display properly in some edge cases.
12576  *
12577  * @example
12578  *
12579  * Use Canvas by default for all paths in the map:
12580  *
12581  * ```js
12582  * var map = L.map('map', {
12583  *      renderer: L.canvas()
12584  * });
12585  * ```
12586  *
12587  * Use a Canvas renderer with extra padding for specific vector geometries:
12588  *
12589  * ```js
12590  * var map = L.map('map');
12591  * var myRenderer = L.canvas({ padding: 0.5 });
12592  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12593  * var circle = L.circle( center, { renderer: myRenderer } );
12594  * ```
12595  */
12596
12597 var Canvas = Renderer.extend({
12598
12599         // @section
12600         // @aka Canvas options
12601         options: {
12602                 // @option tolerance: Number = 0
12603                 // How much to extend the click tolerance around a path/object on the map.
12604                 tolerance: 0
12605         },
12606
12607         getEvents: function () {
12608                 var events = Renderer.prototype.getEvents.call(this);
12609                 events.viewprereset = this._onViewPreReset;
12610                 return events;
12611         },
12612
12613         _onViewPreReset: function () {
12614                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12615                 this._postponeUpdatePaths = true;
12616         },
12617
12618         onAdd: function () {
12619                 Renderer.prototype.onAdd.call(this);
12620
12621                 // Redraw vectors since canvas is cleared upon removal,
12622                 // in case of removing the renderer itself from the map.
12623                 this._draw();
12624         },
12625
12626         _initContainer: function () {
12627                 var container = this._container = document.createElement('canvas');
12628
12629                 on(container, 'mousemove', this._onMouseMove, this);
12630                 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12631                 on(container, 'mouseout', this._handleMouseOut, this);
12632                 container['_leaflet_disable_events'] = true;
12633
12634                 this._ctx = container.getContext('2d');
12635         },
12636
12637         _destroyContainer: function () {
12638                 cancelAnimFrame(this._redrawRequest);
12639                 delete this._ctx;
12640                 remove(this._container);
12641                 off(this._container);
12642                 delete this._container;
12643         },
12644
12645         _updatePaths: function () {
12646                 if (this._postponeUpdatePaths) { return; }
12647
12648                 var layer;
12649                 this._redrawBounds = null;
12650                 for (var id in this._layers) {
12651                         layer = this._layers[id];
12652                         layer._update();
12653                 }
12654                 this._redraw();
12655         },
12656
12657         _update: function () {
12658                 if (this._map._animatingZoom && this._bounds) { return; }
12659
12660                 Renderer.prototype._update.call(this);
12661
12662                 var b = this._bounds,
12663                     container = this._container,
12664                     size = b.getSize(),
12665                     m = Browser.retina ? 2 : 1;
12666
12667                 setPosition(container, b.min);
12668
12669                 // set canvas size (also clearing it); use double size on retina
12670                 container.width = m * size.x;
12671                 container.height = m * size.y;
12672                 container.style.width = size.x + 'px';
12673                 container.style.height = size.y + 'px';
12674
12675                 if (Browser.retina) {
12676                         this._ctx.scale(2, 2);
12677                 }
12678
12679                 // translate so we use the same path coordinates after canvas element moves
12680                 this._ctx.translate(-b.min.x, -b.min.y);
12681
12682                 // Tell paths to redraw themselves
12683                 this.fire('update');
12684         },
12685
12686         _reset: function () {
12687                 Renderer.prototype._reset.call(this);
12688
12689                 if (this._postponeUpdatePaths) {
12690                         this._postponeUpdatePaths = false;
12691                         this._updatePaths();
12692                 }
12693         },
12694
12695         _initPath: function (layer) {
12696                 this._updateDashArray(layer);
12697                 this._layers[stamp(layer)] = layer;
12698
12699                 var order = layer._order = {
12700                         layer: layer,
12701                         prev: this._drawLast,
12702                         next: null
12703                 };
12704                 if (this._drawLast) { this._drawLast.next = order; }
12705                 this._drawLast = order;
12706                 this._drawFirst = this._drawFirst || this._drawLast;
12707         },
12708
12709         _addPath: function (layer) {
12710                 this._requestRedraw(layer);
12711         },
12712
12713         _removePath: function (layer) {
12714                 var order = layer._order;
12715                 var next = order.next;
12716                 var prev = order.prev;
12717
12718                 if (next) {
12719                         next.prev = prev;
12720                 } else {
12721                         this._drawLast = prev;
12722                 }
12723                 if (prev) {
12724                         prev.next = next;
12725                 } else {
12726                         this._drawFirst = next;
12727                 }
12728
12729                 delete layer._order;
12730
12731                 delete this._layers[stamp(layer)];
12732
12733                 this._requestRedraw(layer);
12734         },
12735
12736         _updatePath: function (layer) {
12737                 // Redraw the union of the layer's old pixel
12738                 // bounds and the new pixel bounds.
12739                 this._extendRedrawBounds(layer);
12740                 layer._project();
12741                 layer._update();
12742                 // The redraw will extend the redraw bounds
12743                 // with the new pixel bounds.
12744                 this._requestRedraw(layer);
12745         },
12746
12747         _updateStyle: function (layer) {
12748                 this._updateDashArray(layer);
12749                 this._requestRedraw(layer);
12750         },
12751
12752         _updateDashArray: function (layer) {
12753                 if (typeof layer.options.dashArray === 'string') {
12754                         var parts = layer.options.dashArray.split(/[, ]+/),
12755                             dashArray = [],
12756                             dashValue,
12757                             i;
12758                         for (i = 0; i < parts.length; i++) {
12759                                 dashValue = Number(parts[i]);
12760                                 // Ignore dash array containing invalid lengths
12761                                 if (isNaN(dashValue)) { return; }
12762                                 dashArray.push(dashValue);
12763                         }
12764                         layer.options._dashArray = dashArray;
12765                 } else {
12766                         layer.options._dashArray = layer.options.dashArray;
12767                 }
12768         },
12769
12770         _requestRedraw: function (layer) {
12771                 if (!this._map) { return; }
12772
12773                 this._extendRedrawBounds(layer);
12774                 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12775         },
12776
12777         _extendRedrawBounds: function (layer) {
12778                 if (layer._pxBounds) {
12779                         var padding = (layer.options.weight || 0) + 1;
12780                         this._redrawBounds = this._redrawBounds || new Bounds();
12781                         this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12782                         this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12783                 }
12784         },
12785
12786         _redraw: function () {
12787                 this._redrawRequest = null;
12788
12789                 if (this._redrawBounds) {
12790                         this._redrawBounds.min._floor();
12791                         this._redrawBounds.max._ceil();
12792                 }
12793
12794                 this._clear(); // clear layers in redraw bounds
12795                 this._draw(); // draw layers
12796
12797                 this._redrawBounds = null;
12798         },
12799
12800         _clear: function () {
12801                 var bounds = this._redrawBounds;
12802                 if (bounds) {
12803                         var size = bounds.getSize();
12804                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12805                 } else {
12806                         this._ctx.save();
12807                         this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12808                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12809                         this._ctx.restore();
12810                 }
12811         },
12812
12813         _draw: function () {
12814                 var layer, bounds = this._redrawBounds;
12815                 this._ctx.save();
12816                 if (bounds) {
12817                         var size = bounds.getSize();
12818                         this._ctx.beginPath();
12819                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12820                         this._ctx.clip();
12821                 }
12822
12823                 this._drawing = true;
12824
12825                 for (var order = this._drawFirst; order; order = order.next) {
12826                         layer = order.layer;
12827                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12828                                 layer._updatePath();
12829                         }
12830                 }
12831
12832                 this._drawing = false;
12833
12834                 this._ctx.restore();  // Restore state before clipping.
12835         },
12836
12837         _updatePoly: function (layer, closed) {
12838                 if (!this._drawing) { return; }
12839
12840                 var i, j, len2, p,
12841                     parts = layer._parts,
12842                     len = parts.length,
12843                     ctx = this._ctx;
12844
12845                 if (!len) { return; }
12846
12847                 ctx.beginPath();
12848
12849                 for (i = 0; i < len; i++) {
12850                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
12851                                 p = parts[i][j];
12852                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12853                         }
12854                         if (closed) {
12855                                 ctx.closePath();
12856                         }
12857                 }
12858
12859                 this._fillStroke(ctx, layer);
12860
12861                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12862         },
12863
12864         _updateCircle: function (layer) {
12865
12866                 if (!this._drawing || layer._empty()) { return; }
12867
12868                 var p = layer._point,
12869                     ctx = this._ctx,
12870                     r = Math.max(Math.round(layer._radius), 1),
12871                     s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12872
12873                 if (s !== 1) {
12874                         ctx.save();
12875                         ctx.scale(1, s);
12876                 }
12877
12878                 ctx.beginPath();
12879                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12880
12881                 if (s !== 1) {
12882                         ctx.restore();
12883                 }
12884
12885                 this._fillStroke(ctx, layer);
12886         },
12887
12888         _fillStroke: function (ctx, layer) {
12889                 var options = layer.options;
12890
12891                 if (options.fill) {
12892                         ctx.globalAlpha = options.fillOpacity;
12893                         ctx.fillStyle = options.fillColor || options.color;
12894                         ctx.fill(options.fillRule || 'evenodd');
12895                 }
12896
12897                 if (options.stroke && options.weight !== 0) {
12898                         if (ctx.setLineDash) {
12899                                 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12900                         }
12901                         ctx.globalAlpha = options.opacity;
12902                         ctx.lineWidth = options.weight;
12903                         ctx.strokeStyle = options.color;
12904                         ctx.lineCap = options.lineCap;
12905                         ctx.lineJoin = options.lineJoin;
12906                         ctx.stroke();
12907                 }
12908         },
12909
12910         // Canvas obviously doesn't have mouse events for individual drawn objects,
12911         // so we emulate that by calculating what's under the mouse on mousemove/click manually
12912
12913         _onClick: function (e) {
12914                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12915
12916                 for (var order = this._drawFirst; order; order = order.next) {
12917                         layer = order.layer;
12918                         if (layer.options.interactive && layer._containsPoint(point)) {
12919                                 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
12920                                         clickedLayer = layer;
12921                                 }
12922                         }
12923                 }
12924                 this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
12925         },
12926
12927         _onMouseMove: function (e) {
12928                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12929
12930                 var point = this._map.mouseEventToLayerPoint(e);
12931                 this._handleMouseHover(e, point);
12932         },
12933
12934
12935         _handleMouseOut: function (e) {
12936                 var layer = this._hoveredLayer;
12937                 if (layer) {
12938                         // if we're leaving the layer, fire mouseout
12939                         removeClass(this._container, 'leaflet-interactive');
12940                         this._fireEvent([layer], e, 'mouseout');
12941                         this._hoveredLayer = null;
12942                         this._mouseHoverThrottled = false;
12943                 }
12944         },
12945
12946         _handleMouseHover: function (e, point) {
12947                 if (this._mouseHoverThrottled) {
12948                         return;
12949                 }
12950
12951                 var layer, candidateHoveredLayer;
12952
12953                 for (var order = this._drawFirst; order; order = order.next) {
12954                         layer = order.layer;
12955                         if (layer.options.interactive && layer._containsPoint(point)) {
12956                                 candidateHoveredLayer = layer;
12957                         }
12958                 }
12959
12960                 if (candidateHoveredLayer !== this._hoveredLayer) {
12961                         this._handleMouseOut(e);
12962
12963                         if (candidateHoveredLayer) {
12964                                 addClass(this._container, 'leaflet-interactive'); // change cursor
12965                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12966                                 this._hoveredLayer = candidateHoveredLayer;
12967                         }
12968                 }
12969
12970                 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
12971
12972                 this._mouseHoverThrottled = true;
12973                 setTimeout(bind(function () {
12974                         this._mouseHoverThrottled = false;
12975                 }, this), 32);
12976         },
12977
12978         _fireEvent: function (layers, e, type) {
12979                 this._map._fireDOMEvent(e, type || e.type, layers);
12980         },
12981
12982         _bringToFront: function (layer) {
12983                 var order = layer._order;
12984
12985                 if (!order) { return; }
12986
12987                 var next = order.next;
12988                 var prev = order.prev;
12989
12990                 if (next) {
12991                         next.prev = prev;
12992                 } else {
12993                         // Already last
12994                         return;
12995                 }
12996                 if (prev) {
12997                         prev.next = next;
12998                 } else if (next) {
12999                         // Update first entry unless this is the
13000                         // single entry
13001                         this._drawFirst = next;
13002                 }
13003
13004                 order.prev = this._drawLast;
13005                 this._drawLast.next = order;
13006
13007                 order.next = null;
13008                 this._drawLast = order;
13009
13010                 this._requestRedraw(layer);
13011         },
13012
13013         _bringToBack: function (layer) {
13014                 var order = layer._order;
13015
13016                 if (!order) { return; }
13017
13018                 var next = order.next;
13019                 var prev = order.prev;
13020
13021                 if (prev) {
13022                         prev.next = next;
13023                 } else {
13024                         // Already first
13025                         return;
13026                 }
13027                 if (next) {
13028                         next.prev = prev;
13029                 } else if (prev) {
13030                         // Update last entry unless this is the
13031                         // single entry
13032                         this._drawLast = prev;
13033                 }
13034
13035                 order.prev = null;
13036
13037                 order.next = this._drawFirst;
13038                 this._drawFirst.prev = order;
13039                 this._drawFirst = order;
13040
13041                 this._requestRedraw(layer);
13042         }
13043 });
13044
13045 // @factory L.canvas(options?: Renderer options)
13046 // Creates a Canvas renderer with the given options.
13047 function canvas(options) {
13048         return Browser.canvas ? new Canvas(options) : null;
13049 }
13050
13051 /*
13052  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
13053  */
13054
13055
13056 var vmlCreate = (function () {
13057         try {
13058                 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
13059                 return function (name) {
13060                         return document.createElement('<lvml:' + name + ' class="lvml">');
13061                 };
13062         } catch (e) {
13063                 // Do not return fn from catch block so `e` can be garbage collected
13064                 // See https://github.com/Leaflet/Leaflet/pull/7279
13065         }
13066         return function (name) {
13067                 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
13068         };
13069 })();
13070
13071
13072 /*
13073  * @class SVG
13074  *
13075  *
13076  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
13077  * with old versions of Internet Explorer.
13078  */
13079
13080 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
13081 var vmlMixin = {
13082
13083         _initContainer: function () {
13084                 this._container = create$1('div', 'leaflet-vml-container');
13085         },
13086
13087         _update: function () {
13088                 if (this._map._animatingZoom) { return; }
13089                 Renderer.prototype._update.call(this);
13090                 this.fire('update');
13091         },
13092
13093         _initPath: function (layer) {
13094                 var container = layer._container = vmlCreate('shape');
13095
13096                 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
13097
13098                 container.coordsize = '1 1';
13099
13100                 layer._path = vmlCreate('path');
13101                 container.appendChild(layer._path);
13102
13103                 this._updateStyle(layer);
13104                 this._layers[stamp(layer)] = layer;
13105         },
13106
13107         _addPath: function (layer) {
13108                 var container = layer._container;
13109                 this._container.appendChild(container);
13110
13111                 if (layer.options.interactive) {
13112                         layer.addInteractiveTarget(container);
13113                 }
13114         },
13115
13116         _removePath: function (layer) {
13117                 var container = layer._container;
13118                 remove(container);
13119                 layer.removeInteractiveTarget(container);
13120                 delete this._layers[stamp(layer)];
13121         },
13122
13123         _updateStyle: function (layer) {
13124                 var stroke = layer._stroke,
13125                     fill = layer._fill,
13126                     options = layer.options,
13127                     container = layer._container;
13128
13129                 container.stroked = !!options.stroke;
13130                 container.filled = !!options.fill;
13131
13132                 if (options.stroke) {
13133                         if (!stroke) {
13134                                 stroke = layer._stroke = vmlCreate('stroke');
13135                         }
13136                         container.appendChild(stroke);
13137                         stroke.weight = options.weight + 'px';
13138                         stroke.color = options.color;
13139                         stroke.opacity = options.opacity;
13140
13141                         if (options.dashArray) {
13142                                 stroke.dashStyle = isArray(options.dashArray) ?
13143                                     options.dashArray.join(' ') :
13144                                     options.dashArray.replace(/( *, *)/g, ' ');
13145                         } else {
13146                                 stroke.dashStyle = '';
13147                         }
13148                         stroke.endcap = options.lineCap.replace('butt', 'flat');
13149                         stroke.joinstyle = options.lineJoin;
13150
13151                 } else if (stroke) {
13152                         container.removeChild(stroke);
13153                         layer._stroke = null;
13154                 }
13155
13156                 if (options.fill) {
13157                         if (!fill) {
13158                                 fill = layer._fill = vmlCreate('fill');
13159                         }
13160                         container.appendChild(fill);
13161                         fill.color = options.fillColor || options.color;
13162                         fill.opacity = options.fillOpacity;
13163
13164                 } else if (fill) {
13165                         container.removeChild(fill);
13166                         layer._fill = null;
13167                 }
13168         },
13169
13170         _updateCircle: function (layer) {
13171                 var p = layer._point.round(),
13172                     r = Math.round(layer._radius),
13173                     r2 = Math.round(layer._radiusY || r);
13174
13175                 this._setPath(layer, layer._empty() ? 'M0 0' :
13176                         'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
13177         },
13178
13179         _setPath: function (layer, path) {
13180                 layer._path.v = path;
13181         },
13182
13183         _bringToFront: function (layer) {
13184                 toFront(layer._container);
13185         },
13186
13187         _bringToBack: function (layer) {
13188                 toBack(layer._container);
13189         }
13190 };
13191
13192 var create = Browser.vml ? vmlCreate : svgCreate;
13193
13194 /*
13195  * @class SVG
13196  * @inherits Renderer
13197  * @aka L.SVG
13198  *
13199  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
13200  * Inherits `Renderer`.
13201  *
13202  * Due to [technical limitations](https://caniuse.com/svg), SVG is not
13203  * available in all web browsers, notably Android 2.x and 3.x.
13204  *
13205  * Although SVG is not available on IE7 and IE8, these browsers support
13206  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
13207  * (a now deprecated technology), and the SVG renderer will fall back to VML in
13208  * this case.
13209  *
13210  * @example
13211  *
13212  * Use SVG by default for all paths in the map:
13213  *
13214  * ```js
13215  * var map = L.map('map', {
13216  *      renderer: L.svg()
13217  * });
13218  * ```
13219  *
13220  * Use a SVG renderer with extra padding for specific vector geometries:
13221  *
13222  * ```js
13223  * var map = L.map('map');
13224  * var myRenderer = L.svg({ padding: 0.5 });
13225  * var line = L.polyline( coordinates, { renderer: myRenderer } );
13226  * var circle = L.circle( center, { renderer: myRenderer } );
13227  * ```
13228  */
13229
13230 var SVG = Renderer.extend({
13231
13232         _initContainer: function () {
13233                 this._container = create('svg');
13234
13235                 // makes it possible to click through svg root; we'll reset it back in individual paths
13236                 this._container.setAttribute('pointer-events', 'none');
13237
13238                 this._rootGroup = create('g');
13239                 this._container.appendChild(this._rootGroup);
13240         },
13241
13242         _destroyContainer: function () {
13243                 remove(this._container);
13244                 off(this._container);
13245                 delete this._container;
13246                 delete this._rootGroup;
13247                 delete this._svgSize;
13248         },
13249
13250         _update: function () {
13251                 if (this._map._animatingZoom && this._bounds) { return; }
13252
13253                 Renderer.prototype._update.call(this);
13254
13255                 var b = this._bounds,
13256                     size = b.getSize(),
13257                     container = this._container;
13258
13259                 // set size of svg-container if changed
13260                 if (!this._svgSize || !this._svgSize.equals(size)) {
13261                         this._svgSize = size;
13262                         container.setAttribute('width', size.x);
13263                         container.setAttribute('height', size.y);
13264                 }
13265
13266                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
13267                 setPosition(container, b.min);
13268                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
13269
13270                 this.fire('update');
13271         },
13272
13273         // methods below are called by vector layers implementations
13274
13275         _initPath: function (layer) {
13276                 var path = layer._path = create('path');
13277
13278                 // @namespace Path
13279                 // @option className: String = null
13280                 // Custom class name set on an element. Only for SVG renderer.
13281                 if (layer.options.className) {
13282                         addClass(path, layer.options.className);
13283                 }
13284
13285                 if (layer.options.interactive) {
13286                         addClass(path, 'leaflet-interactive');
13287                 }
13288
13289                 this._updateStyle(layer);
13290                 this._layers[stamp(layer)] = layer;
13291         },
13292
13293         _addPath: function (layer) {
13294                 if (!this._rootGroup) { this._initContainer(); }
13295                 this._rootGroup.appendChild(layer._path);
13296                 layer.addInteractiveTarget(layer._path);
13297         },
13298
13299         _removePath: function (layer) {
13300                 remove(layer._path);
13301                 layer.removeInteractiveTarget(layer._path);
13302                 delete this._layers[stamp(layer)];
13303         },
13304
13305         _updatePath: function (layer) {
13306                 layer._project();
13307                 layer._update();
13308         },
13309
13310         _updateStyle: function (layer) {
13311                 var path = layer._path,
13312                     options = layer.options;
13313
13314                 if (!path) { return; }
13315
13316                 if (options.stroke) {
13317                         path.setAttribute('stroke', options.color);
13318                         path.setAttribute('stroke-opacity', options.opacity);
13319                         path.setAttribute('stroke-width', options.weight);
13320                         path.setAttribute('stroke-linecap', options.lineCap);
13321                         path.setAttribute('stroke-linejoin', options.lineJoin);
13322
13323                         if (options.dashArray) {
13324                                 path.setAttribute('stroke-dasharray', options.dashArray);
13325                         } else {
13326                                 path.removeAttribute('stroke-dasharray');
13327                         }
13328
13329                         if (options.dashOffset) {
13330                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
13331                         } else {
13332                                 path.removeAttribute('stroke-dashoffset');
13333                         }
13334                 } else {
13335                         path.setAttribute('stroke', 'none');
13336                 }
13337
13338                 if (options.fill) {
13339                         path.setAttribute('fill', options.fillColor || options.color);
13340                         path.setAttribute('fill-opacity', options.fillOpacity);
13341                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
13342                 } else {
13343                         path.setAttribute('fill', 'none');
13344                 }
13345         },
13346
13347         _updatePoly: function (layer, closed) {
13348                 this._setPath(layer, pointsToPath(layer._parts, closed));
13349         },
13350
13351         _updateCircle: function (layer) {
13352                 var p = layer._point,
13353                     r = Math.max(Math.round(layer._radius), 1),
13354                     r2 = Math.max(Math.round(layer._radiusY), 1) || r,
13355                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
13356
13357                 // drawing a circle with two half-arcs
13358                 var d = layer._empty() ? 'M0 0' :
13359                         'M' + (p.x - r) + ',' + p.y +
13360                         arc + (r * 2) + ',0 ' +
13361                         arc + (-r * 2) + ',0 ';
13362
13363                 this._setPath(layer, d);
13364         },
13365
13366         _setPath: function (layer, path) {
13367                 layer._path.setAttribute('d', path);
13368         },
13369
13370         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
13371         _bringToFront: function (layer) {
13372                 toFront(layer._path);
13373         },
13374
13375         _bringToBack: function (layer) {
13376                 toBack(layer._path);
13377         }
13378 });
13379
13380 if (Browser.vml) {
13381         SVG.include(vmlMixin);
13382 }
13383
13384 // @namespace SVG
13385 // @factory L.svg(options?: Renderer options)
13386 // Creates a SVG renderer with the given options.
13387 function svg(options) {
13388         return Browser.svg || Browser.vml ? new SVG(options) : null;
13389 }
13390
13391 Map.include({
13392         // @namespace Map; @method getRenderer(layer: Path): Renderer
13393         // Returns the instance of `Renderer` that should be used to render the given
13394         // `Path`. It will ensure that the `renderer` options of the map and paths
13395         // are respected, and that the renderers do exist on the map.
13396         getRenderer: function (layer) {
13397                 // @namespace Path; @option renderer: Renderer
13398                 // Use this specific instance of `Renderer` for this path. Takes
13399                 // precedence over the map's [default renderer](#map-renderer).
13400                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
13401
13402                 if (!renderer) {
13403                         renderer = this._renderer = this._createRenderer();
13404                 }
13405
13406                 if (!this.hasLayer(renderer)) {
13407                         this.addLayer(renderer);
13408                 }
13409                 return renderer;
13410         },
13411
13412         _getPaneRenderer: function (name) {
13413                 if (name === 'overlayPane' || name === undefined) {
13414                         return false;
13415                 }
13416
13417                 var renderer = this._paneRenderers[name];
13418                 if (renderer === undefined) {
13419                         renderer = this._createRenderer({pane: name});
13420                         this._paneRenderers[name] = renderer;
13421                 }
13422                 return renderer;
13423         },
13424
13425         _createRenderer: function (options) {
13426                 // @namespace Map; @option preferCanvas: Boolean = false
13427                 // Whether `Path`s should be rendered on a `Canvas` renderer.
13428                 // By default, all `Path`s are rendered in a `SVG` renderer.
13429                 return (this.options.preferCanvas && canvas(options)) || svg(options);
13430         }
13431 });
13432
13433 /*
13434  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
13435  */
13436
13437 /*
13438  * @class Rectangle
13439  * @aka L.Rectangle
13440  * @inherits Polygon
13441  *
13442  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
13443  *
13444  * @example
13445  *
13446  * ```js
13447  * // define rectangle geographical bounds
13448  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
13449  *
13450  * // create an orange rectangle
13451  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
13452  *
13453  * // zoom the map to the rectangle bounds
13454  * map.fitBounds(bounds);
13455  * ```
13456  *
13457  */
13458
13459
13460 var Rectangle = Polygon.extend({
13461         initialize: function (latLngBounds, options) {
13462                 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13463         },
13464
13465         // @method setBounds(latLngBounds: LatLngBounds): this
13466         // Redraws the rectangle with the passed bounds.
13467         setBounds: function (latLngBounds) {
13468                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13469         },
13470
13471         _boundsToLatLngs: function (latLngBounds) {
13472                 latLngBounds = toLatLngBounds(latLngBounds);
13473                 return [
13474                         latLngBounds.getSouthWest(),
13475                         latLngBounds.getNorthWest(),
13476                         latLngBounds.getNorthEast(),
13477                         latLngBounds.getSouthEast()
13478                 ];
13479         }
13480 });
13481
13482
13483 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13484 function rectangle(latLngBounds, options) {
13485         return new Rectangle(latLngBounds, options);
13486 }
13487
13488 SVG.create = create;
13489 SVG.pointsToPath = pointsToPath;
13490
13491 GeoJSON.geometryToLayer = geometryToLayer;
13492 GeoJSON.coordsToLatLng = coordsToLatLng;
13493 GeoJSON.coordsToLatLngs = coordsToLatLngs;
13494 GeoJSON.latLngToCoords = latLngToCoords;
13495 GeoJSON.latLngsToCoords = latLngsToCoords;
13496 GeoJSON.getFeature = getFeature;
13497 GeoJSON.asFeature = asFeature;
13498
13499 /*
13500  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13501  * (zoom to a selected bounding box), enabled by default.
13502  */
13503
13504 // @namespace Map
13505 // @section Interaction Options
13506 Map.mergeOptions({
13507         // @option boxZoom: Boolean = true
13508         // Whether the map can be zoomed to a rectangular area specified by
13509         // dragging the mouse while pressing the shift key.
13510         boxZoom: true
13511 });
13512
13513 var BoxZoom = Handler.extend({
13514         initialize: function (map) {
13515                 this._map = map;
13516                 this._container = map._container;
13517                 this._pane = map._panes.overlayPane;
13518                 this._resetStateTimeout = 0;
13519                 map.on('unload', this._destroy, this);
13520         },
13521
13522         addHooks: function () {
13523                 on(this._container, 'mousedown', this._onMouseDown, this);
13524         },
13525
13526         removeHooks: function () {
13527                 off(this._container, 'mousedown', this._onMouseDown, this);
13528         },
13529
13530         moved: function () {
13531                 return this._moved;
13532         },
13533
13534         _destroy: function () {
13535                 remove(this._pane);
13536                 delete this._pane;
13537         },
13538
13539         _resetState: function () {
13540                 this._resetStateTimeout = 0;
13541                 this._moved = false;
13542         },
13543
13544         _clearDeferredResetState: function () {
13545                 if (this._resetStateTimeout !== 0) {
13546                         clearTimeout(this._resetStateTimeout);
13547                         this._resetStateTimeout = 0;
13548                 }
13549         },
13550
13551         _onMouseDown: function (e) {
13552                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13553
13554                 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13555                 // will interrupt the interaction and orphan a box element in the container.
13556                 this._clearDeferredResetState();
13557                 this._resetState();
13558
13559                 disableTextSelection();
13560                 disableImageDrag();
13561
13562                 this._startPoint = this._map.mouseEventToContainerPoint(e);
13563
13564                 on(document, {
13565                         contextmenu: stop,
13566                         mousemove: this._onMouseMove,
13567                         mouseup: this._onMouseUp,
13568                         keydown: this._onKeyDown
13569                 }, this);
13570         },
13571
13572         _onMouseMove: function (e) {
13573                 if (!this._moved) {
13574                         this._moved = true;
13575
13576                         this._box = create$1('div', 'leaflet-zoom-box', this._container);
13577                         addClass(this._container, 'leaflet-crosshair');
13578
13579                         this._map.fire('boxzoomstart');
13580                 }
13581
13582                 this._point = this._map.mouseEventToContainerPoint(e);
13583
13584                 var bounds = new Bounds(this._point, this._startPoint),
13585                     size = bounds.getSize();
13586
13587                 setPosition(this._box, bounds.min);
13588
13589                 this._box.style.width  = size.x + 'px';
13590                 this._box.style.height = size.y + 'px';
13591         },
13592
13593         _finish: function () {
13594                 if (this._moved) {
13595                         remove(this._box);
13596                         removeClass(this._container, 'leaflet-crosshair');
13597                 }
13598
13599                 enableTextSelection();
13600                 enableImageDrag();
13601
13602                 off(document, {
13603                         contextmenu: stop,
13604                         mousemove: this._onMouseMove,
13605                         mouseup: this._onMouseUp,
13606                         keydown: this._onKeyDown
13607                 }, this);
13608         },
13609
13610         _onMouseUp: function (e) {
13611                 if ((e.which !== 1) && (e.button !== 1)) { return; }
13612
13613                 this._finish();
13614
13615                 if (!this._moved) { return; }
13616                 // Postpone to next JS tick so internal click event handling
13617                 // still see it as "moved".
13618                 this._clearDeferredResetState();
13619                 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13620
13621                 var bounds = new LatLngBounds(
13622                         this._map.containerPointToLatLng(this._startPoint),
13623                         this._map.containerPointToLatLng(this._point));
13624
13625                 this._map
13626                         .fitBounds(bounds)
13627                         .fire('boxzoomend', {boxZoomBounds: bounds});
13628         },
13629
13630         _onKeyDown: function (e) {
13631                 if (e.keyCode === 27) {
13632                         this._finish();
13633                         this._clearDeferredResetState();
13634                         this._resetState();
13635                 }
13636         }
13637 });
13638
13639 // @section Handlers
13640 // @property boxZoom: Handler
13641 // Box (shift-drag with mouse) zoom handler.
13642 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13643
13644 /*
13645  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13646  */
13647
13648 // @namespace Map
13649 // @section Interaction Options
13650
13651 Map.mergeOptions({
13652         // @option doubleClickZoom: Boolean|String = true
13653         // Whether the map can be zoomed in by double clicking on it and
13654         // zoomed out by double clicking while holding shift. If passed
13655         // `'center'`, double-click zoom will zoom to the center of the
13656         //  view regardless of where the mouse was.
13657         doubleClickZoom: true
13658 });
13659
13660 var DoubleClickZoom = Handler.extend({
13661         addHooks: function () {
13662                 this._map.on('dblclick', this._onDoubleClick, this);
13663         },
13664
13665         removeHooks: function () {
13666                 this._map.off('dblclick', this._onDoubleClick, this);
13667         },
13668
13669         _onDoubleClick: function (e) {
13670                 var map = this._map,
13671                     oldZoom = map.getZoom(),
13672                     delta = map.options.zoomDelta,
13673                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13674
13675                 if (map.options.doubleClickZoom === 'center') {
13676                         map.setZoom(zoom);
13677                 } else {
13678                         map.setZoomAround(e.containerPoint, zoom);
13679                 }
13680         }
13681 });
13682
13683 // @section Handlers
13684 //
13685 // Map properties include interaction handlers that allow you to control
13686 // interaction behavior in runtime, enabling or disabling certain features such
13687 // as dragging or touch zoom (see `Handler` methods). For example:
13688 //
13689 // ```js
13690 // map.doubleClickZoom.disable();
13691 // ```
13692 //
13693 // @property doubleClickZoom: Handler
13694 // Double click zoom handler.
13695 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13696
13697 /*
13698  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13699  */
13700
13701 // @namespace Map
13702 // @section Interaction Options
13703 Map.mergeOptions({
13704         // @option dragging: Boolean = true
13705         // Whether the map is draggable with mouse/touch or not.
13706         dragging: true,
13707
13708         // @section Panning Inertia Options
13709         // @option inertia: Boolean = *
13710         // If enabled, panning of the map will have an inertia effect where
13711         // the map builds momentum while dragging and continues moving in
13712         // the same direction for some time. Feels especially nice on touch
13713         // devices. Enabled by default.
13714         inertia: true,
13715
13716         // @option inertiaDeceleration: Number = 3000
13717         // The rate with which the inertial movement slows down, in pixels/second².
13718         inertiaDeceleration: 3400, // px/s^2
13719
13720         // @option inertiaMaxSpeed: Number = Infinity
13721         // Max speed of the inertial movement, in pixels/second.
13722         inertiaMaxSpeed: Infinity, // px/s
13723
13724         // @option easeLinearity: Number = 0.2
13725         easeLinearity: 0.2,
13726
13727         // TODO refactor, move to CRS
13728         // @option worldCopyJump: Boolean = false
13729         // With this option enabled, the map tracks when you pan to another "copy"
13730         // of the world and seamlessly jumps to the original one so that all overlays
13731         // like markers and vector layers are still visible.
13732         worldCopyJump: false,
13733
13734         // @option maxBoundsViscosity: Number = 0.0
13735         // If `maxBounds` is set, this option will control how solid the bounds
13736         // are when dragging the map around. The default value of `0.0` allows the
13737         // user to drag outside the bounds at normal speed, higher values will
13738         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13739         // solid, preventing the user from dragging outside the bounds.
13740         maxBoundsViscosity: 0.0
13741 });
13742
13743 var Drag = Handler.extend({
13744         addHooks: function () {
13745                 if (!this._draggable) {
13746                         var map = this._map;
13747
13748                         this._draggable = new Draggable(map._mapPane, map._container);
13749
13750                         this._draggable.on({
13751                                 dragstart: this._onDragStart,
13752                                 drag: this._onDrag,
13753                                 dragend: this._onDragEnd
13754                         }, this);
13755
13756                         this._draggable.on('predrag', this._onPreDragLimit, this);
13757                         if (map.options.worldCopyJump) {
13758                                 this._draggable.on('predrag', this._onPreDragWrap, this);
13759                                 map.on('zoomend', this._onZoomEnd, this);
13760
13761                                 map.whenReady(this._onZoomEnd, this);
13762                         }
13763                 }
13764                 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13765                 this._draggable.enable();
13766                 this._positions = [];
13767                 this._times = [];
13768         },
13769
13770         removeHooks: function () {
13771                 removeClass(this._map._container, 'leaflet-grab');
13772                 removeClass(this._map._container, 'leaflet-touch-drag');
13773                 this._draggable.disable();
13774         },
13775
13776         moved: function () {
13777                 return this._draggable && this._draggable._moved;
13778         },
13779
13780         moving: function () {
13781                 return this._draggable && this._draggable._moving;
13782         },
13783
13784         _onDragStart: function () {
13785                 var map = this._map;
13786
13787                 map._stop();
13788                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13789                         var bounds = toLatLngBounds(this._map.options.maxBounds);
13790
13791                         this._offsetLimit = toBounds(
13792                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13793                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13794                                         .add(this._map.getSize()));
13795
13796                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13797                 } else {
13798                         this._offsetLimit = null;
13799                 }
13800
13801                 map
13802                     .fire('movestart')
13803                     .fire('dragstart');
13804
13805                 if (map.options.inertia) {
13806                         this._positions = [];
13807                         this._times = [];
13808                 }
13809         },
13810
13811         _onDrag: function (e) {
13812                 if (this._map.options.inertia) {
13813                         var time = this._lastTime = +new Date(),
13814                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13815
13816                         this._positions.push(pos);
13817                         this._times.push(time);
13818
13819                         this._prunePositions(time);
13820                 }
13821
13822                 this._map
13823                     .fire('move', e)
13824                     .fire('drag', e);
13825         },
13826
13827         _prunePositions: function (time) {
13828                 while (this._positions.length > 1 && time - this._times[0] > 50) {
13829                         this._positions.shift();
13830                         this._times.shift();
13831                 }
13832         },
13833
13834         _onZoomEnd: function () {
13835                 var pxCenter = this._map.getSize().divideBy(2),
13836                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13837
13838                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13839                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13840         },
13841
13842         _viscousLimit: function (value, threshold) {
13843                 return value - (value - threshold) * this._viscosity;
13844         },
13845
13846         _onPreDragLimit: function () {
13847                 if (!this._viscosity || !this._offsetLimit) { return; }
13848
13849                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13850
13851                 var limit = this._offsetLimit;
13852                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13853                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13854                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13855                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13856
13857                 this._draggable._newPos = this._draggable._startPos.add(offset);
13858         },
13859
13860         _onPreDragWrap: function () {
13861                 // TODO refactor to be able to adjust map pane position after zoom
13862                 var worldWidth = this._worldWidth,
13863                     halfWidth = Math.round(worldWidth / 2),
13864                     dx = this._initialWorldOffset,
13865                     x = this._draggable._newPos.x,
13866                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13867                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13868                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13869
13870                 this._draggable._absPos = this._draggable._newPos.clone();
13871                 this._draggable._newPos.x = newX;
13872         },
13873
13874         _onDragEnd: function (e) {
13875                 var map = this._map,
13876                     options = map.options,
13877
13878                     noInertia = !options.inertia || e.noInertia || this._times.length < 2;
13879
13880                 map.fire('dragend', e);
13881
13882                 if (noInertia) {
13883                         map.fire('moveend');
13884
13885                 } else {
13886                         this._prunePositions(+new Date());
13887
13888                         var direction = this._lastPos.subtract(this._positions[0]),
13889                             duration = (this._lastTime - this._times[0]) / 1000,
13890                             ease = options.easeLinearity,
13891
13892                             speedVector = direction.multiplyBy(ease / duration),
13893                             speed = speedVector.distanceTo([0, 0]),
13894
13895                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13896                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13897
13898                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13899                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13900
13901                         if (!offset.x && !offset.y) {
13902                                 map.fire('moveend');
13903
13904                         } else {
13905                                 offset = map._limitOffset(offset, map.options.maxBounds);
13906
13907                                 requestAnimFrame(function () {
13908                                         map.panBy(offset, {
13909                                                 duration: decelerationDuration,
13910                                                 easeLinearity: ease,
13911                                                 noMoveStart: true,
13912                                                 animate: true
13913                                         });
13914                                 });
13915                         }
13916                 }
13917         }
13918 });
13919
13920 // @section Handlers
13921 // @property dragging: Handler
13922 // Map dragging handler (by both mouse and touch).
13923 Map.addInitHook('addHandler', 'dragging', Drag);
13924
13925 /*
13926  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13927  */
13928
13929 // @namespace Map
13930 // @section Keyboard Navigation Options
13931 Map.mergeOptions({
13932         // @option keyboard: Boolean = true
13933         // Makes the map focusable and allows users to navigate the map with keyboard
13934         // arrows and `+`/`-` keys.
13935         keyboard: true,
13936
13937         // @option keyboardPanDelta: Number = 80
13938         // Amount of pixels to pan when pressing an arrow key.
13939         keyboardPanDelta: 80
13940 });
13941
13942 var Keyboard = Handler.extend({
13943
13944         keyCodes: {
13945                 left:    [37],
13946                 right:   [39],
13947                 down:    [40],
13948                 up:      [38],
13949                 zoomIn:  [187, 107, 61, 171],
13950                 zoomOut: [189, 109, 54, 173]
13951         },
13952
13953         initialize: function (map) {
13954                 this._map = map;
13955
13956                 this._setPanDelta(map.options.keyboardPanDelta);
13957                 this._setZoomDelta(map.options.zoomDelta);
13958         },
13959
13960         addHooks: function () {
13961                 var container = this._map._container;
13962
13963                 // make the container focusable by tabbing
13964                 if (container.tabIndex <= 0) {
13965                         container.tabIndex = '0';
13966                 }
13967
13968                 on(container, {
13969                         focus: this._onFocus,
13970                         blur: this._onBlur,
13971                         mousedown: this._onMouseDown
13972                 }, this);
13973
13974                 this._map.on({
13975                         focus: this._addHooks,
13976                         blur: this._removeHooks
13977                 }, this);
13978         },
13979
13980         removeHooks: function () {
13981                 this._removeHooks();
13982
13983                 off(this._map._container, {
13984                         focus: this._onFocus,
13985                         blur: this._onBlur,
13986                         mousedown: this._onMouseDown
13987                 }, this);
13988
13989                 this._map.off({
13990                         focus: this._addHooks,
13991                         blur: this._removeHooks
13992                 }, this);
13993         },
13994
13995         _onMouseDown: function () {
13996                 if (this._focused) { return; }
13997
13998                 var body = document.body,
13999                     docEl = document.documentElement,
14000                     top = body.scrollTop || docEl.scrollTop,
14001                     left = body.scrollLeft || docEl.scrollLeft;
14002
14003                 this._map._container.focus();
14004
14005                 window.scrollTo(left, top);
14006         },
14007
14008         _onFocus: function () {
14009                 this._focused = true;
14010                 this._map.fire('focus');
14011         },
14012
14013         _onBlur: function () {
14014                 this._focused = false;
14015                 this._map.fire('blur');
14016         },
14017
14018         _setPanDelta: function (panDelta) {
14019                 var keys = this._panKeys = {},
14020                     codes = this.keyCodes,
14021                     i, len;
14022
14023                 for (i = 0, len = codes.left.length; i < len; i++) {
14024                         keys[codes.left[i]] = [-1 * panDelta, 0];
14025                 }
14026                 for (i = 0, len = codes.right.length; i < len; i++) {
14027                         keys[codes.right[i]] = [panDelta, 0];
14028                 }
14029                 for (i = 0, len = codes.down.length; i < len; i++) {
14030                         keys[codes.down[i]] = [0, panDelta];
14031                 }
14032                 for (i = 0, len = codes.up.length; i < len; i++) {
14033                         keys[codes.up[i]] = [0, -1 * panDelta];
14034                 }
14035         },
14036
14037         _setZoomDelta: function (zoomDelta) {
14038                 var keys = this._zoomKeys = {},
14039                     codes = this.keyCodes,
14040                     i, len;
14041
14042                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
14043                         keys[codes.zoomIn[i]] = zoomDelta;
14044                 }
14045                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
14046                         keys[codes.zoomOut[i]] = -zoomDelta;
14047                 }
14048         },
14049
14050         _addHooks: function () {
14051                 on(document, 'keydown', this._onKeyDown, this);
14052         },
14053
14054         _removeHooks: function () {
14055                 off(document, 'keydown', this._onKeyDown, this);
14056         },
14057
14058         _onKeyDown: function (e) {
14059                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
14060
14061                 var key = e.keyCode,
14062                     map = this._map,
14063                     offset;
14064
14065                 if (key in this._panKeys) {
14066                         if (!map._panAnim || !map._panAnim._inProgress) {
14067                                 offset = this._panKeys[key];
14068                                 if (e.shiftKey) {
14069                                         offset = toPoint(offset).multiplyBy(3);
14070                                 }
14071
14072                                 if (map.options.maxBounds) {
14073                                         offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
14074                                 }
14075
14076                                 if (map.options.worldCopyJump) {
14077                                         var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
14078                                         map.panTo(newLatLng);
14079                                 } else {
14080                                         map.panBy(offset);
14081                                 }
14082                         }
14083                 } else if (key in this._zoomKeys) {
14084                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
14085
14086                 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
14087                         map.closePopup();
14088
14089                 } else {
14090                         return;
14091                 }
14092
14093                 stop(e);
14094         }
14095 });
14096
14097 // @section Handlers
14098 // @section Handlers
14099 // @property keyboard: Handler
14100 // Keyboard navigation handler.
14101 Map.addInitHook('addHandler', 'keyboard', Keyboard);
14102
14103 /*
14104  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
14105  */
14106
14107 // @namespace Map
14108 // @section Interaction Options
14109 Map.mergeOptions({
14110         // @section Mouse wheel options
14111         // @option scrollWheelZoom: Boolean|String = true
14112         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
14113         // it will zoom to the center of the view regardless of where the mouse was.
14114         scrollWheelZoom: true,
14115
14116         // @option wheelDebounceTime: Number = 40
14117         // Limits the rate at which a wheel can fire (in milliseconds). By default
14118         // user can't zoom via wheel more often than once per 40 ms.
14119         wheelDebounceTime: 40,
14120
14121         // @option wheelPxPerZoomLevel: Number = 60
14122         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
14123         // mean a change of one full zoom level. Smaller values will make wheel-zooming
14124         // faster (and vice versa).
14125         wheelPxPerZoomLevel: 60
14126 });
14127
14128 var ScrollWheelZoom = Handler.extend({
14129         addHooks: function () {
14130                 on(this._map._container, 'wheel', this._onWheelScroll, this);
14131
14132                 this._delta = 0;
14133         },
14134
14135         removeHooks: function () {
14136                 off(this._map._container, 'wheel', this._onWheelScroll, this);
14137         },
14138
14139         _onWheelScroll: function (e) {
14140                 var delta = getWheelDelta(e);
14141
14142                 var debounce = this._map.options.wheelDebounceTime;
14143
14144                 this._delta += delta;
14145                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
14146
14147                 if (!this._startTime) {
14148                         this._startTime = +new Date();
14149                 }
14150
14151                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
14152
14153                 clearTimeout(this._timer);
14154                 this._timer = setTimeout(bind(this._performZoom, this), left);
14155
14156                 stop(e);
14157         },
14158
14159         _performZoom: function () {
14160                 var map = this._map,
14161                     zoom = map.getZoom(),
14162                     snap = this._map.options.zoomSnap || 0;
14163
14164                 map._stop(); // stop panning and fly animations if any
14165
14166                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
14167                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
14168                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
14169                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
14170                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
14171
14172                 this._delta = 0;
14173                 this._startTime = null;
14174
14175                 if (!delta) { return; }
14176
14177                 if (map.options.scrollWheelZoom === 'center') {
14178                         map.setZoom(zoom + delta);
14179                 } else {
14180                         map.setZoomAround(this._lastMousePos, zoom + delta);
14181                 }
14182         }
14183 });
14184
14185 // @section Handlers
14186 // @property scrollWheelZoom: Handler
14187 // Scroll wheel zoom handler.
14188 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
14189
14190 /*
14191  * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
14192  * which otherwise is not fired by mobile Safari.
14193  */
14194
14195 var tapHoldDelay = 600;
14196
14197 // @namespace Map
14198 // @section Interaction Options
14199 Map.mergeOptions({
14200         // @section Touch interaction options
14201         // @option tapHold: Boolean
14202         // Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
14203         tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
14204
14205         // @option tapTolerance: Number = 15
14206         // The max number of pixels a user can shift his finger during touch
14207         // for it to be considered a valid tap.
14208         tapTolerance: 15
14209 });
14210
14211 var TapHold = Handler.extend({
14212         addHooks: function () {
14213                 on(this._map._container, 'touchstart', this._onDown, this);
14214         },
14215
14216         removeHooks: function () {
14217                 off(this._map._container, 'touchstart', this._onDown, this);
14218         },
14219
14220         _onDown: function (e) {
14221                 clearTimeout(this._holdTimeout);
14222                 if (e.touches.length !== 1) { return; }
14223
14224                 var first = e.touches[0];
14225                 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
14226
14227                 this._holdTimeout = setTimeout(bind(function () {
14228                         this._cancel();
14229                         if (!this._isTapValid()) { return; }
14230
14231                         // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
14232                         on(document, 'touchend', preventDefault);
14233                         on(document, 'touchend touchcancel', this._cancelClickPrevent);
14234                         this._simulateEvent('contextmenu', first);
14235                 }, this), tapHoldDelay);
14236
14237                 on(document, 'touchend touchcancel contextmenu', this._cancel, this);
14238                 on(document, 'touchmove', this._onMove, this);
14239         },
14240
14241         _cancelClickPrevent: function cancelClickPrevent() {
14242                 off(document, 'touchend', preventDefault);
14243                 off(document, 'touchend touchcancel', cancelClickPrevent);
14244         },
14245
14246         _cancel: function () {
14247                 clearTimeout(this._holdTimeout);
14248                 off(document, 'touchend touchcancel contextmenu', this._cancel, this);
14249                 off(document, 'touchmove', this._onMove, this);
14250         },
14251
14252         _onMove: function (e) {
14253                 var first = e.touches[0];
14254                 this._newPos = new Point(first.clientX, first.clientY);
14255         },
14256
14257         _isTapValid: function () {
14258                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
14259         },
14260
14261         _simulateEvent: function (type, e) {
14262                 var simulatedEvent = new MouseEvent(type, {
14263                         bubbles: true,
14264                         cancelable: true,
14265                         view: window,
14266                         // detail: 1,
14267                         screenX: e.screenX,
14268                         screenY: e.screenY,
14269                         clientX: e.clientX,
14270                         clientY: e.clientY,
14271                         // button: 2,
14272                         // buttons: 2
14273                 });
14274
14275                 simulatedEvent._simulated = true;
14276
14277                 e.target.dispatchEvent(simulatedEvent);
14278         }
14279 });
14280
14281 // @section Handlers
14282 // @property tapHold: Handler
14283 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
14284 Map.addInitHook('addHandler', 'tapHold', TapHold);
14285
14286 /*
14287  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
14288  */
14289
14290 // @namespace Map
14291 // @section Interaction Options
14292 Map.mergeOptions({
14293         // @section Touch interaction options
14294         // @option touchZoom: Boolean|String = *
14295         // Whether the map can be zoomed by touch-dragging with two fingers. If
14296         // passed `'center'`, it will zoom to the center of the view regardless of
14297         // where the touch events (fingers) were. Enabled for touch-capable web
14298         // browsers.
14299         touchZoom: Browser.touch,
14300
14301         // @option bounceAtZoomLimits: Boolean = true
14302         // Set it to false if you don't want the map to zoom beyond min/max zoom
14303         // and then bounce back when pinch-zooming.
14304         bounceAtZoomLimits: true
14305 });
14306
14307 var TouchZoom = Handler.extend({
14308         addHooks: function () {
14309                 addClass(this._map._container, 'leaflet-touch-zoom');
14310                 on(this._map._container, 'touchstart', this._onTouchStart, this);
14311         },
14312
14313         removeHooks: function () {
14314                 removeClass(this._map._container, 'leaflet-touch-zoom');
14315                 off(this._map._container, 'touchstart', this._onTouchStart, this);
14316         },
14317
14318         _onTouchStart: function (e) {
14319                 var map = this._map;
14320                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
14321
14322                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
14323                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
14324
14325                 this._centerPoint = map.getSize()._divideBy(2);
14326                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
14327                 if (map.options.touchZoom !== 'center') {
14328                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
14329                 }
14330
14331                 this._startDist = p1.distanceTo(p2);
14332                 this._startZoom = map.getZoom();
14333
14334                 this._moved = false;
14335                 this._zooming = true;
14336
14337                 map._stop();
14338
14339                 on(document, 'touchmove', this._onTouchMove, this);
14340                 on(document, 'touchend touchcancel', this._onTouchEnd, this);
14341
14342                 preventDefault(e);
14343         },
14344
14345         _onTouchMove: function (e) {
14346                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
14347
14348                 var map = this._map,
14349                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
14350                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
14351                     scale = p1.distanceTo(p2) / this._startDist;
14352
14353                 this._zoom = map.getScaleZoom(scale, this._startZoom);
14354
14355                 if (!map.options.bounceAtZoomLimits && (
14356                         (this._zoom < map.getMinZoom() && scale < 1) ||
14357                         (this._zoom > map.getMaxZoom() && scale > 1))) {
14358                         this._zoom = map._limitZoom(this._zoom);
14359                 }
14360
14361                 if (map.options.touchZoom === 'center') {
14362                         this._center = this._startLatLng;
14363                         if (scale === 1) { return; }
14364                 } else {
14365                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
14366                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
14367                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
14368                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
14369                 }
14370
14371                 if (!this._moved) {
14372                         map._moveStart(true, false);
14373                         this._moved = true;
14374                 }
14375
14376                 cancelAnimFrame(this._animRequest);
14377
14378                 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
14379                 this._animRequest = requestAnimFrame(moveFn, this, true);
14380
14381                 preventDefault(e);
14382         },
14383
14384         _onTouchEnd: function () {
14385                 if (!this._moved || !this._zooming) {
14386                         this._zooming = false;
14387                         return;
14388                 }
14389
14390                 this._zooming = false;
14391                 cancelAnimFrame(this._animRequest);
14392
14393                 off(document, 'touchmove', this._onTouchMove, this);
14394                 off(document, 'touchend touchcancel', this._onTouchEnd, this);
14395
14396                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
14397                 if (this._map.options.zoomAnimation) {
14398                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
14399                 } else {
14400                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
14401                 }
14402         }
14403 });
14404
14405 // @section Handlers
14406 // @property touchZoom: Handler
14407 // Touch zoom handler.
14408 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
14409
14410 Map.BoxZoom = BoxZoom;
14411 Map.DoubleClickZoom = DoubleClickZoom;
14412 Map.Drag = Drag;
14413 Map.Keyboard = Keyboard;
14414 Map.ScrollWheelZoom = ScrollWheelZoom;
14415 Map.TapHold = TapHold;
14416 Map.TouchZoom = TouchZoom;
14417
14418 export { Bounds, Browser, CRS, Canvas, Circle, CircleMarker, Class, Control, DivIcon, DivOverlay, DomEvent, DomUtil, Draggable, Evented, FeatureGroup, GeoJSON, GridLayer, Handler, Icon, ImageOverlay, LatLng, LatLngBounds, Layer, LayerGroup, LineUtil, Map, Marker, Mixin, Path, Point, PolyUtil, Polygon, Polyline, Popup, PosAnimation, index as Projection, Rectangle, Renderer, SVG, SVGOverlay, TileLayer, Tooltip, Transformation, Util, VideoOverlay, bind, toBounds as bounds, canvas, circle, circleMarker, control, divIcon, extend, featureGroup, geoJSON, geoJson, gridLayer, icon, imageOverlay, toLatLng as latLng, toLatLngBounds as latLngBounds, layerGroup, createMap as map, marker, toPoint as point, polygon, polyline, popup, rectangle, setOptions, stamp, svg, svgOverlay, tileLayer, tooltip, toTransformation as transformation, version, videoOverlay };
14419 //# sourceMappingURL=leaflet-src.esm.js.map