Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / httemplate / misc / elements / leaflet / leaflet-src.js
1 /* @preserve
2  * Leaflet 1.2.0+Detached: 1ac320ba232cb85b73ac81f3d82780c9d07f0d4e.1ac320b, a JS library for interactive maps. http://leafletjs.com
3  * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4  */
5 (function (global, factory) {
6         typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
7         typeof define === 'function' && define.amd ? define(['exports'], factory) :
8         (factory((global.L = {})));
9 }(this, (function (exports) { 'use strict';
10
11 var version = "1.2.0+HEAD.1ac320b";
12
13 /*\r
14  * @namespace Util\r
15  *\r
16  * Various utility functions, used by Leaflet internally.\r
17  */\r
18 \r
19 var freeze = Object.freeze;\r
20 Object.freeze = function (obj) { return obj; };\r
21 \r
22 // @function extend(dest: Object, src?: Object): Object\r
23 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.\r
24 function extend(dest) {\r
25         var i, j, len, src;\r
26 \r
27         for (j = 1, len = arguments.length; j < len; j++) {\r
28                 src = arguments[j];\r
29                 for (i in src) {\r
30                         dest[i] = src[i];\r
31                 }\r
32         }\r
33         return dest;\r
34 }\r
35 \r
36 // @function create(proto: Object, properties?: Object): Object\r
37 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)\r
38 var create = Object.create || (function () {\r
39         function F() {}\r
40         return function (proto) {\r
41                 F.prototype = proto;\r
42                 return new F();\r
43         };\r
44 })();\r
45 \r
46 // @function bind(fn: Function, …): Function\r
47 // 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
48 // Has a `L.bind()` shortcut.\r
49 function bind(fn, obj) {\r
50         var slice = Array.prototype.slice;\r
51 \r
52         if (fn.bind) {\r
53                 return fn.bind.apply(fn, slice.call(arguments, 1));\r
54         }\r
55 \r
56         var args = slice.call(arguments, 2);\r
57 \r
58         return function () {\r
59                 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);\r
60         };\r
61 }\r
62 \r
63 // @property lastId: Number\r
64 // Last unique ID used by [`stamp()`](#util-stamp)\r
65 var lastId = 0;\r
66 \r
67 // @function stamp(obj: Object): Number\r
68 // Returns the unique ID of an object, assiging it one if it doesn't have it.\r
69 function stamp(obj) {\r
70         /*eslint-disable */\r
71         obj._leaflet_id = obj._leaflet_id || ++lastId;\r
72         return obj._leaflet_id;\r
73         /*eslint-enable */\r
74 }\r
75 \r
76 // @function throttle(fn: Function, time: Number, context: Object): Function\r
77 // Returns a function which executes function `fn` with the given scope `context`\r
78 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function\r
79 // `fn` will be called no more than one time per given amount of `time`. The arguments\r
80 // received by the bound function will be any arguments passed when binding the\r
81 // function, followed by any arguments passed when invoking the bound function.\r
82 // Has an `L.throttle` shortcut.\r
83 function throttle(fn, time, context) {\r
84         var lock, args, wrapperFn, later;\r
85 \r
86         later = function () {\r
87                 // reset lock and call if queued\r
88                 lock = false;\r
89                 if (args) {\r
90                         wrapperFn.apply(context, args);\r
91                         args = false;\r
92                 }\r
93         };\r
94 \r
95         wrapperFn = function () {\r
96                 if (lock) {\r
97                         // called too soon, queue to call later\r
98                         args = arguments;\r
99 \r
100                 } else {\r
101                         // call and lock until later\r
102                         fn.apply(context, arguments);\r
103                         setTimeout(later, time);\r
104                         lock = true;\r
105                 }\r
106         };\r
107 \r
108         return wrapperFn;\r
109 }\r
110 \r
111 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number\r
112 // Returns the number `num` modulo `range` in such a way so it lies within\r
113 // `range[0]` and `range[1]`. The returned value will be always smaller than\r
114 // `range[1]` unless `includeMax` is set to `true`.\r
115 function wrapNum(x, range, includeMax) {\r
116         var max = range[1],\r
117             min = range[0],\r
118             d = max - min;\r
119         return x === max && includeMax ? x : ((x - min) % d + d) % d + min;\r
120 }\r
121 \r
122 // @function falseFn(): Function\r
123 // Returns a function which always returns `false`.\r
124 function falseFn() { return false; }\r
125 \r
126 // @function formatNum(num: Number, digits?: Number): Number\r
127 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.\r
128 function formatNum(num, digits) {\r
129         var pow = Math.pow(10, digits || 5);\r
130         return Math.round(num * pow) / pow;\r
131 }\r
132 \r
133 // @function trim(str: String): String\r
134 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)\r
135 function trim(str) {\r
136         return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');\r
137 }\r
138 \r
139 // @function splitWords(str: String): String[]\r
140 // Trims and splits the string on whitespace and returns the array of parts.\r
141 function splitWords(str) {\r
142         return trim(str).split(/\s+/);\r
143 }\r
144 \r
145 // @function setOptions(obj: Object, options: Object): Object\r
146 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.\r
147 function setOptions(obj, options) {\r
148         if (!obj.hasOwnProperty('options')) {\r
149                 obj.options = obj.options ? create(obj.options) : {};\r
150         }\r
151         for (var i in options) {\r
152                 obj.options[i] = options[i];\r
153         }\r
154         return obj.options;\r
155 }\r
156 \r
157 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String\r
158 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`\r
159 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will\r
160 // be appended at the end. If `uppercase` is `true`, the parameter names will\r
161 // be uppercased (e.g. `'?A=foo&B=bar'`)\r
162 function getParamString(obj, existingUrl, uppercase) {\r
163         var params = [];\r
164         for (var i in obj) {\r
165                 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));\r
166         }\r
167         return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');\r
168 }\r
169 \r
170 var templateRe = /\{ *([\w_\-]+) *\}/g;\r
171 \r
172 // @function template(str: String, data: Object): String\r
173 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`\r
174 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string\r
175 // `('Hello foo, bar')`. You can also specify functions instead of strings for\r
176 // data values — they will be evaluated passing `data` as an argument.\r
177 function template(str, data) {\r
178         return str.replace(templateRe, function (str, key) {\r
179                 var value = data[key];\r
180 \r
181                 if (value === undefined) {\r
182                         throw new Error('No value provided for variable ' + str);\r
183 \r
184                 } else if (typeof value === 'function') {\r
185                         value = value(data);\r
186                 }\r
187                 return value;\r
188         });\r
189 }\r
190 \r
191 // @function isArray(obj): Boolean\r
192 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)\r
193 var isArray = Array.isArray || function (obj) {\r
194         return (Object.prototype.toString.call(obj) === '[object Array]');\r
195 };\r
196 \r
197 // @function indexOf(array: Array, el: Object): Number\r
198 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)\r
199 function indexOf(array, el) {\r
200         for (var i = 0; i < array.length; i++) {\r
201                 if (array[i] === el) { return i; }\r
202         }\r
203         return -1;\r
204 }\r
205 \r
206 // @property emptyImageUrl: String\r
207 // Data URI string containing a base64-encoded empty GIF image.\r
208 // Used as a hack to free memory from unused images on WebKit-powered\r
209 // mobile devices (by setting image `src` to this string).\r
210 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';\r
211 \r
212 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/\r
213 \r
214 function getPrefixed(name) {\r
215         return window['webkit' + name] || window['moz' + name] || window['ms' + name];\r
216 }\r
217 \r
218 var lastTime = 0;\r
219 \r
220 // fallback for IE 7-8\r
221 function timeoutDefer(fn) {\r
222         var time = +new Date(),\r
223             timeToCall = Math.max(0, 16 - (time - lastTime));\r
224 \r
225         lastTime = time + timeToCall;\r
226         return window.setTimeout(fn, timeToCall);\r
227 }\r
228 \r
229 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;\r
230 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||\r
231                 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };\r
232 \r
233 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number\r
234 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to\r
235 // `context` if given. When `immediate` is set, `fn` is called immediately if\r
236 // the browser doesn't have native support for\r
237 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),\r
238 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.\r
239 function requestAnimFrame(fn, context, immediate) {\r
240         if (immediate && requestFn === timeoutDefer) {\r
241                 fn.call(context);\r
242         } else {\r
243                 return requestFn.call(window, bind(fn, context));\r
244         }\r
245 }\r
246 \r
247 // @function cancelAnimFrame(id: Number): undefined\r
248 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).\r
249 function cancelAnimFrame(id) {\r
250         if (id) {\r
251                 cancelFn.call(window, id);\r
252         }\r
253 }\r
254
255
256 var Util = (Object.freeze || Object)({
257         freeze: freeze,
258         extend: extend,
259         create: create,
260         bind: bind,
261         lastId: lastId,
262         stamp: stamp,
263         throttle: throttle,
264         wrapNum: wrapNum,
265         falseFn: falseFn,
266         formatNum: formatNum,
267         trim: trim,
268         splitWords: splitWords,
269         setOptions: setOptions,
270         getParamString: getParamString,
271         template: template,
272         isArray: isArray,
273         indexOf: indexOf,
274         emptyImageUrl: emptyImageUrl,
275         requestFn: requestFn,
276         cancelFn: cancelFn,
277         requestAnimFrame: requestAnimFrame,
278         cancelAnimFrame: cancelAnimFrame
279 });
280
281 // @class Class\r
282 // @aka L.Class\r
283 \r
284 // @section\r
285 // @uninheritable\r
286 \r
287 // Thanks to John Resig and Dean Edwards for inspiration!\r
288 \r
289 function Class() {}\r
290 \r
291 Class.extend = function (props) {\r
292 \r
293         // @function extend(props: Object): Function\r
294         // [Extends the current class](#class-inheritance) given the properties to be included.\r
295         // Returns a Javascript function that is a class constructor (to be called with `new`).\r
296         var NewClass = function () {\r
297 \r
298                 // call the constructor\r
299                 if (this.initialize) {\r
300                         this.initialize.apply(this, arguments);\r
301                 }\r
302 \r
303                 // call all constructor hooks\r
304                 this.callInitHooks();\r
305         };\r
306 \r
307         var parentProto = NewClass.__super__ = this.prototype;\r
308 \r
309         var proto = create(parentProto);\r
310         proto.constructor = NewClass;\r
311 \r
312         NewClass.prototype = proto;\r
313 \r
314         // inherit parent's statics\r
315         for (var i in this) {\r
316                 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {\r
317                         NewClass[i] = this[i];\r
318                 }\r
319         }\r
320 \r
321         // mix static properties into the class\r
322         if (props.statics) {\r
323                 extend(NewClass, props.statics);\r
324                 delete props.statics;\r
325         }\r
326 \r
327         // mix includes into the prototype\r
328         if (props.includes) {\r
329                 checkDeprecatedMixinEvents(props.includes);\r
330                 extend.apply(null, [proto].concat(props.includes));\r
331                 delete props.includes;\r
332         }\r
333 \r
334         // merge options\r
335         if (proto.options) {\r
336                 props.options = extend(create(proto.options), props.options);\r
337         }\r
338 \r
339         // mix given properties into the prototype\r
340         extend(proto, props);\r
341 \r
342         proto._initHooks = [];\r
343 \r
344         // add method for calling all hooks\r
345         proto.callInitHooks = function () {\r
346 \r
347                 if (this._initHooksCalled) { return; }\r
348 \r
349                 if (parentProto.callInitHooks) {\r
350                         parentProto.callInitHooks.call(this);\r
351                 }\r
352 \r
353                 this._initHooksCalled = true;\r
354 \r
355                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {\r
356                         proto._initHooks[i].call(this);\r
357                 }\r
358         };\r
359 \r
360         return NewClass;\r
361 };\r
362 \r
363 \r
364 // @function include(properties: Object): this\r
365 // [Includes a mixin](#class-includes) into the current class.\r
366 Class.include = function (props) {\r
367         extend(this.prototype, props);\r
368         return this;\r
369 };\r
370 \r
371 // @function mergeOptions(options: Object): this\r
372 // [Merges `options`](#class-options) into the defaults of the class.\r
373 Class.mergeOptions = function (options) {\r
374         extend(this.prototype.options, options);\r
375         return this;\r
376 };\r
377 \r
378 // @function addInitHook(fn: Function): this\r
379 // Adds a [constructor hook](#class-constructor-hooks) to the class.\r
380 Class.addInitHook = function (fn) { // (Function) || (String, args...)\r
381         var args = Array.prototype.slice.call(arguments, 1);\r
382 \r
383         var init = typeof fn === 'function' ? fn : function () {\r
384                 this[fn].apply(this, args);\r
385         };\r
386 \r
387         this.prototype._initHooks = this.prototype._initHooks || [];\r
388         this.prototype._initHooks.push(init);\r
389         return this;\r
390 };\r
391 \r
392 function checkDeprecatedMixinEvents(includes) {\r
393         if (!L || !L.Mixin) { return; }\r
394 \r
395         includes = isArray(includes) ? includes : [includes];\r
396 \r
397         for (var i = 0; i < includes.length; i++) {\r
398                 if (includes[i] === L.Mixin.Events) {\r
399                         console.warn('Deprecated include of L.Mixin.Events: ' +\r
400                                 'this property will be removed in future releases, ' +\r
401                                 'please inherit from L.Evented instead.', new Error().stack);\r
402                 }\r
403         }\r
404 }
405
406 /*\r
407  * @class Evented\r
408  * @aka L.Evented\r
409  * @inherits Class\r
410  *\r
411  * 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
412  *\r
413  * @example\r
414  *\r
415  * ```js\r
416  * map.on('click', function(e) {\r
417  *      alert(e.latlng);\r
418  * } );\r
419  * ```\r
420  *\r
421  * 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
422  *\r
423  * ```js\r
424  * function onClick(e) { ... }\r
425  *\r
426  * map.on('click', onClick);\r
427  * map.off('click', onClick);\r
428  * ```\r
429  */\r
430 \r
431 var Events = {\r
432         /* @method on(type: String, fn: Function, context?: Object): this\r
433          * 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
434          *\r
435          * @alternative\r
436          * @method on(eventMap: Object): this\r
437          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`\r
438          */\r
439         on: function (types, fn, context) {\r
440 \r
441                 // types can be a map of types/handlers\r
442                 if (typeof types === 'object') {\r
443                         for (var type in types) {\r
444                                 // we don't process space-separated events here for performance;\r
445                                 // it's a hot path since Layer uses the on(obj) syntax\r
446                                 this._on(type, types[type], fn);\r
447                         }\r
448 \r
449                 } else {\r
450                         // types can be a string of space-separated words\r
451                         types = splitWords(types);\r
452 \r
453                         for (var i = 0, len = types.length; i < len; i++) {\r
454                                 this._on(types[i], fn, context);\r
455                         }\r
456                 }\r
457 \r
458                 return this;\r
459         },\r
460 \r
461         /* @method off(type: String, fn?: Function, context?: Object): this\r
462          * 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
463          *\r
464          * @alternative\r
465          * @method off(eventMap: Object): this\r
466          * Removes a set of type/listener pairs.\r
467          *\r
468          * @alternative\r
469          * @method off: this\r
470          * Removes all listeners to all events on the object.\r
471          */\r
472         off: function (types, fn, context) {\r
473 \r
474                 if (!types) {\r
475                         // clear all listeners if called without arguments\r
476                         delete this._events;\r
477 \r
478                 } else if (typeof types === 'object') {\r
479                         for (var type in types) {\r
480                                 this._off(type, types[type], fn);\r
481                         }\r
482 \r
483                 } else {\r
484                         types = splitWords(types);\r
485 \r
486                         for (var i = 0, len = types.length; i < len; i++) {\r
487                                 this._off(types[i], fn, context);\r
488                         }\r
489                 }\r
490 \r
491                 return this;\r
492         },\r
493 \r
494         // attach listener (without syntactic sugar now)\r
495         _on: function (type, fn, context) {\r
496                 this._events = this._events || {};\r
497 \r
498                 /* get/init listeners for type */\r
499                 var typeListeners = this._events[type];\r
500                 if (!typeListeners) {\r
501                         typeListeners = [];\r
502                         this._events[type] = typeListeners;\r
503                 }\r
504 \r
505                 if (context === this) {\r
506                         // Less memory footprint.\r
507                         context = undefined;\r
508                 }\r
509                 var newListener = {fn: fn, ctx: context},\r
510                     listeners = typeListeners;\r
511 \r
512                 // check if fn already there\r
513                 for (var i = 0, len = listeners.length; i < len; i++) {\r
514                         if (listeners[i].fn === fn && listeners[i].ctx === context) {\r
515                                 return;\r
516                         }\r
517                 }\r
518 \r
519                 listeners.push(newListener);\r
520         },\r
521 \r
522         _off: function (type, fn, context) {\r
523                 var listeners,\r
524                     i,\r
525                     len;\r
526 \r
527                 if (!this._events) { return; }\r
528 \r
529                 listeners = this._events[type];\r
530 \r
531                 if (!listeners) {\r
532                         return;\r
533                 }\r
534 \r
535                 if (!fn) {\r
536                         // Set all removed listeners to noop so they are not called if remove happens in fire\r
537                         for (i = 0, len = listeners.length; i < len; i++) {\r
538                                 listeners[i].fn = falseFn;\r
539                         }\r
540                         // clear all listeners for a type if function isn't specified\r
541                         delete this._events[type];\r
542                         return;\r
543                 }\r
544 \r
545                 if (context === this) {\r
546                         context = undefined;\r
547                 }\r
548 \r
549                 if (listeners) {\r
550 \r
551                         // find fn and remove it\r
552                         for (i = 0, len = listeners.length; i < len; i++) {\r
553                                 var l = listeners[i];\r
554                                 if (l.ctx !== context) { continue; }\r
555                                 if (l.fn === fn) {\r
556 \r
557                                         // set the removed listener to noop so that's not called if remove happens in fire\r
558                                         l.fn = falseFn;\r
559 \r
560                                         if (this._firingCount) {\r
561                                                 /* copy array in case events are being fired */\r
562                                                 this._events[type] = listeners = listeners.slice();\r
563                                         }\r
564                                         listeners.splice(i, 1);\r
565 \r
566                                         return;\r
567                                 }\r
568                         }\r
569                 }\r
570         },\r
571 \r
572         // @method fire(type: String, data?: Object, propagate?: Boolean): this\r
573         // Fires an event of the specified type. You can optionally provide an data\r
574         // object — the first argument of the listener function will contain its\r
575         // properties. The event can optionally be propagated to event parents.\r
576         fire: function (type, data, propagate) {\r
577                 if (!this.listens(type, propagate)) { return this; }\r
578 \r
579                 var event = extend({}, data, {type: type, target: this});\r
580 \r
581                 if (this._events) {\r
582                         var listeners = this._events[type];\r
583 \r
584                         if (listeners) {\r
585                                 this._firingCount = (this._firingCount + 1) || 1;\r
586                                 for (var i = 0, len = listeners.length; i < len; i++) {\r
587                                         var l = listeners[i];\r
588                                         l.fn.call(l.ctx || this, event);\r
589                                 }\r
590 \r
591                                 this._firingCount--;\r
592                         }\r
593                 }\r
594 \r
595                 if (propagate) {\r
596                         // propagate the event to parents (set with addEventParent)\r
597                         this._propagateEvent(event);\r
598                 }\r
599 \r
600                 return this;\r
601         },\r
602 \r
603         // @method listens(type: String): Boolean\r
604         // Returns `true` if a particular event type has any listeners attached to it.\r
605         listens: function (type, propagate) {\r
606                 var listeners = this._events && this._events[type];\r
607                 if (listeners && listeners.length) { return true; }\r
608 \r
609                 if (propagate) {\r
610                         // also check parents for listeners if event propagates\r
611                         for (var id in this._eventParents) {\r
612                                 if (this._eventParents[id].listens(type, propagate)) { return true; }\r
613                         }\r
614                 }\r
615                 return false;\r
616         },\r
617 \r
618         // @method once(…): this\r
619         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.\r
620         once: function (types, fn, context) {\r
621 \r
622                 if (typeof types === 'object') {\r
623                         for (var type in types) {\r
624                                 this.once(type, types[type], fn);\r
625                         }\r
626                         return this;\r
627                 }\r
628 \r
629                 var handler = bind(function () {\r
630                         this\r
631                             .off(types, fn, context)\r
632                             .off(types, handler, context);\r
633                 }, this);\r
634 \r
635                 // add a listener that's executed once and removed after that\r
636                 return this\r
637                     .on(types, fn, context)\r
638                     .on(types, handler, context);\r
639         },\r
640 \r
641         // @method addEventParent(obj: Evented): this\r
642         // Adds an event parent - an `Evented` that will receive propagated events\r
643         addEventParent: function (obj) {\r
644                 this._eventParents = this._eventParents || {};\r
645                 this._eventParents[stamp(obj)] = obj;\r
646                 return this;\r
647         },\r
648 \r
649         // @method removeEventParent(obj: Evented): this\r
650         // Removes an event parent, so it will stop receiving propagated events\r
651         removeEventParent: function (obj) {\r
652                 if (this._eventParents) {\r
653                         delete this._eventParents[stamp(obj)];\r
654                 }\r
655                 return this;\r
656         },\r
657 \r
658         _propagateEvent: function (e) {\r
659                 for (var id in this._eventParents) {\r
660                         this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);\r
661                 }\r
662         }\r
663 };\r
664 \r
665 // aliases; we should ditch those eventually\r
666 \r
667 // @method addEventListener(…): this\r
668 // Alias to [`on(…)`](#evented-on)\r
669 Events.addEventListener = Events.on;\r
670 \r
671 // @method removeEventListener(…): this\r
672 // Alias to [`off(…)`](#evented-off)\r
673 \r
674 // @method clearAllEventListeners(…): this\r
675 // Alias to [`off()`](#evented-off)\r
676 Events.removeEventListener = Events.clearAllEventListeners = Events.off;\r
677 \r
678 // @method addOneTimeEventListener(…): this\r
679 // Alias to [`once(…)`](#evented-once)\r
680 Events.addOneTimeEventListener = Events.once;\r
681 \r
682 // @method fireEvent(…): this\r
683 // Alias to [`fire(…)`](#evented-fire)\r
684 Events.fireEvent = Events.fire;\r
685 \r
686 // @method hasEventListeners(…): Boolean\r
687 // Alias to [`listens(…)`](#evented-listens)\r
688 Events.hasEventListeners = Events.listens;\r
689 \r
690 var Evented = Class.extend(Events);
691
692 /*\r
693  * @class Point\r
694  * @aka L.Point\r
695  *\r
696  * Represents a point with `x` and `y` coordinates in pixels.\r
697  *\r
698  * @example\r
699  *\r
700  * ```js\r
701  * var point = L.point(200, 300);\r
702  * ```\r
703  *\r
704  * 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
705  *\r
706  * ```js\r
707  * map.panBy([200, 300]);\r
708  * map.panBy(L.point(200, 300));\r
709  * ```\r
710  */\r
711 \r
712 function Point(x, y, round) {\r
713         // @property x: Number; The `x` coordinate of the point\r
714         this.x = (round ? Math.round(x) : x);\r
715         // @property y: Number; The `y` coordinate of the point\r
716         this.y = (round ? Math.round(y) : y);\r
717 }\r
718 \r
719 Point.prototype = {\r
720 \r
721         // @method clone(): Point\r
722         // Returns a copy of the current point.\r
723         clone: function () {\r
724                 return new Point(this.x, this.y);\r
725         },\r
726 \r
727         // @method add(otherPoint: Point): Point\r
728         // Returns the result of addition of the current and the given points.\r
729         add: function (point) {\r
730                 // non-destructive, returns a new point\r
731                 return this.clone()._add(toPoint(point));\r
732         },\r
733 \r
734         _add: function (point) {\r
735                 // destructive, used directly for performance in situations where it's safe to modify existing point\r
736                 this.x += point.x;\r
737                 this.y += point.y;\r
738                 return this;\r
739         },\r
740 \r
741         // @method subtract(otherPoint: Point): Point\r
742         // Returns the result of subtraction of the given point from the current.\r
743         subtract: function (point) {\r
744                 return this.clone()._subtract(toPoint(point));\r
745         },\r
746 \r
747         _subtract: function (point) {\r
748                 this.x -= point.x;\r
749                 this.y -= point.y;\r
750                 return this;\r
751         },\r
752 \r
753         // @method divideBy(num: Number): Point\r
754         // Returns the result of division of the current point by the given number.\r
755         divideBy: function (num) {\r
756                 return this.clone()._divideBy(num);\r
757         },\r
758 \r
759         _divideBy: function (num) {\r
760                 this.x /= num;\r
761                 this.y /= num;\r
762                 return this;\r
763         },\r
764 \r
765         // @method multiplyBy(num: Number): Point\r
766         // Returns the result of multiplication of the current point by the given number.\r
767         multiplyBy: function (num) {\r
768                 return this.clone()._multiplyBy(num);\r
769         },\r
770 \r
771         _multiplyBy: function (num) {\r
772                 this.x *= num;\r
773                 this.y *= num;\r
774                 return this;\r
775         },\r
776 \r
777         // @method scaleBy(scale: Point): Point\r
778         // Multiply each coordinate of the current point by each coordinate of\r
779         // `scale`. In linear algebra terms, multiply the point by the\r
780         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)\r
781         // defined by `scale`.\r
782         scaleBy: function (point) {\r
783                 return new Point(this.x * point.x, this.y * point.y);\r
784         },\r
785 \r
786         // @method unscaleBy(scale: Point): Point\r
787         // Inverse of `scaleBy`. Divide each coordinate of the current point by\r
788         // each coordinate of `scale`.\r
789         unscaleBy: function (point) {\r
790                 return new Point(this.x / point.x, this.y / point.y);\r
791         },\r
792 \r
793         // @method round(): Point\r
794         // Returns a copy of the current point with rounded coordinates.\r
795         round: function () {\r
796                 return this.clone()._round();\r
797         },\r
798 \r
799         _round: function () {\r
800                 this.x = Math.round(this.x);\r
801                 this.y = Math.round(this.y);\r
802                 return this;\r
803         },\r
804 \r
805         // @method floor(): Point\r
806         // Returns a copy of the current point with floored coordinates (rounded down).\r
807         floor: function () {\r
808                 return this.clone()._floor();\r
809         },\r
810 \r
811         _floor: function () {\r
812                 this.x = Math.floor(this.x);\r
813                 this.y = Math.floor(this.y);\r
814                 return this;\r
815         },\r
816 \r
817         // @method ceil(): Point\r
818         // Returns a copy of the current point with ceiled coordinates (rounded up).\r
819         ceil: function () {\r
820                 return this.clone()._ceil();\r
821         },\r
822 \r
823         _ceil: function () {\r
824                 this.x = Math.ceil(this.x);\r
825                 this.y = Math.ceil(this.y);\r
826                 return this;\r
827         },\r
828 \r
829         // @method distanceTo(otherPoint: Point): Number\r
830         // Returns the cartesian distance between the current and the given points.\r
831         distanceTo: function (point) {\r
832                 point = toPoint(point);\r
833 \r
834                 var x = point.x - this.x,\r
835                     y = point.y - this.y;\r
836 \r
837                 return Math.sqrt(x * x + y * y);\r
838         },\r
839 \r
840         // @method equals(otherPoint: Point): Boolean\r
841         // Returns `true` if the given point has the same coordinates.\r
842         equals: function (point) {\r
843                 point = toPoint(point);\r
844 \r
845                 return point.x === this.x &&\r
846                        point.y === this.y;\r
847         },\r
848 \r
849         // @method contains(otherPoint: Point): Boolean\r
850         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).\r
851         contains: function (point) {\r
852                 point = toPoint(point);\r
853 \r
854                 return Math.abs(point.x) <= Math.abs(this.x) &&\r
855                        Math.abs(point.y) <= Math.abs(this.y);\r
856         },\r
857 \r
858         // @method toString(): String\r
859         // Returns a string representation of the point for debugging purposes.\r
860         toString: function () {\r
861                 return 'Point(' +\r
862                         formatNum(this.x) + ', ' +\r
863                         formatNum(this.y) + ')';\r
864         }\r
865 };\r
866 \r
867 // @factory L.point(x: Number, y: Number, round?: Boolean)\r
868 // 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
869 \r
870 // @alternative\r
871 // @factory L.point(coords: Number[])\r
872 // Expects an array of the form `[x, y]` instead.\r
873 \r
874 // @alternative\r
875 // @factory L.point(coords: Object)\r
876 // Expects a plain object of the form `{x: Number, y: Number}` instead.\r
877 function toPoint(x, y, round) {\r
878         if (x instanceof Point) {\r
879                 return x;\r
880         }\r
881         if (isArray(x)) {\r
882                 return new Point(x[0], x[1]);\r
883         }\r
884         if (x === undefined || x === null) {\r
885                 return x;\r
886         }\r
887         if (typeof x === 'object' && 'x' in x && 'y' in x) {\r
888                 return new Point(x.x, x.y);\r
889         }\r
890         return new Point(x, y, round);\r
891 }
892
893 /*\r
894  * @class Bounds\r
895  * @aka L.Bounds\r
896  *\r
897  * Represents a rectangular area in pixel coordinates.\r
898  *\r
899  * @example\r
900  *\r
901  * ```js\r
902  * var p1 = L.point(10, 10),\r
903  * p2 = L.point(40, 60),\r
904  * bounds = L.bounds(p1, p2);\r
905  * ```\r
906  *\r
907  * 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
908  *\r
909  * ```js\r
910  * otherBounds.intersects([[10, 10], [40, 60]]);\r
911  * ```\r
912  */\r
913 \r
914 function Bounds(a, b) {\r
915         if (!a) { return; }\r
916 \r
917         var points = b ? [a, b] : a;\r
918 \r
919         for (var i = 0, len = points.length; i < len; i++) {\r
920                 this.extend(points[i]);\r
921         }\r
922 }\r
923 \r
924 Bounds.prototype = {\r
925         // @method extend(point: Point): this\r
926         // Extends the bounds to contain the given point.\r
927         extend: function (point) { // (Point)\r
928                 point = toPoint(point);\r
929 \r
930                 // @property min: Point\r
931                 // The top left corner of the rectangle.\r
932                 // @property max: Point\r
933                 // The bottom right corner of the rectangle.\r
934                 if (!this.min && !this.max) {\r
935                         this.min = point.clone();\r
936                         this.max = point.clone();\r
937                 } else {\r
938                         this.min.x = Math.min(point.x, this.min.x);\r
939                         this.max.x = Math.max(point.x, this.max.x);\r
940                         this.min.y = Math.min(point.y, this.min.y);\r
941                         this.max.y = Math.max(point.y, this.max.y);\r
942                 }\r
943                 return this;\r
944         },\r
945 \r
946         // @method getCenter(round?: Boolean): Point\r
947         // Returns the center point of the bounds.\r
948         getCenter: function (round) {\r
949                 return new Point(\r
950                         (this.min.x + this.max.x) / 2,\r
951                         (this.min.y + this.max.y) / 2, round);\r
952         },\r
953 \r
954         // @method getBottomLeft(): Point\r
955         // Returns the bottom-left point of the bounds.\r
956         getBottomLeft: function () {\r
957                 return new Point(this.min.x, this.max.y);\r
958         },\r
959 \r
960         // @method getTopRight(): Point\r
961         // Returns the top-right point of the bounds.\r
962         getTopRight: function () { // -> Point\r
963                 return new Point(this.max.x, this.min.y);\r
964         },\r
965 \r
966         // @method getTopLeft(): Point\r
967         // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).\r
968         getTopLeft: function () {\r
969                 return this.min; // left, top\r
970         },\r
971 \r
972         // @method getBottomRight(): Point\r
973         // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).\r
974         getBottomRight: function () {\r
975                 return this.max; // right, bottom\r
976         },\r
977 \r
978         // @method getSize(): Point\r
979         // Returns the size of the given bounds\r
980         getSize: function () {\r
981                 return this.max.subtract(this.min);\r
982         },\r
983 \r
984         // @method contains(otherBounds: Bounds): Boolean\r
985         // Returns `true` if the rectangle contains the given one.\r
986         // @alternative\r
987         // @method contains(point: Point): Boolean\r
988         // Returns `true` if the rectangle contains the given point.\r
989         contains: function (obj) {\r
990                 var min, max;\r
991 \r
992                 if (typeof obj[0] === 'number' || obj instanceof Point) {\r
993                         obj = toPoint(obj);\r
994                 } else {\r
995                         obj = toBounds(obj);\r
996                 }\r
997 \r
998                 if (obj instanceof Bounds) {\r
999                         min = obj.min;\r
1000                         max = obj.max;\r
1001                 } else {\r
1002                         min = max = obj;\r
1003                 }\r
1004 \r
1005                 return (min.x >= this.min.x) &&\r
1006                        (max.x <= this.max.x) &&\r
1007                        (min.y >= this.min.y) &&\r
1008                        (max.y <= this.max.y);\r
1009         },\r
1010 \r
1011         // @method intersects(otherBounds: Bounds): Boolean\r
1012         // Returns `true` if the rectangle intersects the given bounds. Two bounds\r
1013         // intersect if they have at least one point in common.\r
1014         intersects: function (bounds) { // (Bounds) -> Boolean\r
1015                 bounds = toBounds(bounds);\r
1016 \r
1017                 var min = this.min,\r
1018                     max = this.max,\r
1019                     min2 = bounds.min,\r
1020                     max2 = bounds.max,\r
1021                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),\r
1022                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);\r
1023 \r
1024                 return xIntersects && yIntersects;\r
1025         },\r
1026 \r
1027         // @method overlaps(otherBounds: Bounds): Boolean\r
1028         // Returns `true` if the rectangle overlaps the given bounds. Two bounds\r
1029         // overlap if their intersection is an area.\r
1030         overlaps: function (bounds) { // (Bounds) -> Boolean\r
1031                 bounds = toBounds(bounds);\r
1032 \r
1033                 var min = this.min,\r
1034                     max = this.max,\r
1035                     min2 = bounds.min,\r
1036                     max2 = bounds.max,\r
1037                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),\r
1038                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);\r
1039 \r
1040                 return xOverlaps && yOverlaps;\r
1041         },\r
1042 \r
1043         isValid: function () {\r
1044                 return !!(this.min && this.max);\r
1045         }\r
1046 };\r
1047 \r
1048 \r
1049 // @factory L.bounds(corner1: Point, corner2: Point)\r
1050 // Creates a Bounds object from two corners coordinate pairs.\r
1051 // @alternative\r
1052 // @factory L.bounds(points: Point[])\r
1053 // Creates a Bounds object from the given array of points.\r
1054 function toBounds(a, b) {\r
1055         if (!a || a instanceof Bounds) {\r
1056                 return a;\r
1057         }\r
1058         return new Bounds(a, b);\r
1059 }
1060
1061 /*\r
1062  * @class LatLngBounds\r
1063  * @aka L.LatLngBounds\r
1064  *\r
1065  * Represents a rectangular geographical area on a map.\r
1066  *\r
1067  * @example\r
1068  *\r
1069  * ```js\r
1070  * var corner1 = L.latLng(40.712, -74.227),\r
1071  * corner2 = L.latLng(40.774, -74.125),\r
1072  * bounds = L.latLngBounds(corner1, corner2);\r
1073  * ```\r
1074  *\r
1075  * 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
1076  *\r
1077  * ```js\r
1078  * map.fitBounds([\r
1079  *      [40.712, -74.227],\r
1080  *      [40.774, -74.125]\r
1081  * ]);\r
1082  * ```\r
1083  *\r
1084  * 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
1085  */\r
1086 \r
1087 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])\r
1088         if (!corner1) { return; }\r
1089 \r
1090         var latlngs = corner2 ? [corner1, corner2] : corner1;\r
1091 \r
1092         for (var i = 0, len = latlngs.length; i < len; i++) {\r
1093                 this.extend(latlngs[i]);\r
1094         }\r
1095 }\r
1096 \r
1097 LatLngBounds.prototype = {\r
1098 \r
1099         // @method extend(latlng: LatLng): this\r
1100         // Extend the bounds to contain the given point\r
1101 \r
1102         // @alternative\r
1103         // @method extend(otherBounds: LatLngBounds): this\r
1104         // Extend the bounds to contain the given bounds\r
1105         extend: function (obj) {\r
1106                 var sw = this._southWest,\r
1107                     ne = this._northEast,\r
1108                     sw2, ne2;\r
1109 \r
1110                 if (obj instanceof LatLng) {\r
1111                         sw2 = obj;\r
1112                         ne2 = obj;\r
1113 \r
1114                 } else if (obj instanceof LatLngBounds) {\r
1115                         sw2 = obj._southWest;\r
1116                         ne2 = obj._northEast;\r
1117 \r
1118                         if (!sw2 || !ne2) { return this; }\r
1119 \r
1120                 } else {\r
1121                         return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;\r
1122                 }\r
1123 \r
1124                 if (!sw && !ne) {\r
1125                         this._southWest = new LatLng(sw2.lat, sw2.lng);\r
1126                         this._northEast = new LatLng(ne2.lat, ne2.lng);\r
1127                 } else {\r
1128                         sw.lat = Math.min(sw2.lat, sw.lat);\r
1129                         sw.lng = Math.min(sw2.lng, sw.lng);\r
1130                         ne.lat = Math.max(ne2.lat, ne.lat);\r
1131                         ne.lng = Math.max(ne2.lng, ne.lng);\r
1132                 }\r
1133 \r
1134                 return this;\r
1135         },\r
1136 \r
1137         // @method pad(bufferRatio: Number): LatLngBounds\r
1138         // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.\r
1139         pad: function (bufferRatio) {\r
1140                 var sw = this._southWest,\r
1141                     ne = this._northEast,\r
1142                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,\r
1143                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;\r
1144 \r
1145                 return new LatLngBounds(\r
1146                         new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),\r
1147                         new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));\r
1148         },\r
1149 \r
1150         // @method getCenter(): LatLng\r
1151         // Returns the center point of the bounds.\r
1152         getCenter: function () {\r
1153                 return new LatLng(\r
1154                         (this._southWest.lat + this._northEast.lat) / 2,\r
1155                         (this._southWest.lng + this._northEast.lng) / 2);\r
1156         },\r
1157 \r
1158         // @method getSouthWest(): LatLng\r
1159         // Returns the south-west point of the bounds.\r
1160         getSouthWest: function () {\r
1161                 return this._southWest;\r
1162         },\r
1163 \r
1164         // @method getNorthEast(): LatLng\r
1165         // Returns the north-east point of the bounds.\r
1166         getNorthEast: function () {\r
1167                 return this._northEast;\r
1168         },\r
1169 \r
1170         // @method getNorthWest(): LatLng\r
1171         // Returns the north-west point of the bounds.\r
1172         getNorthWest: function () {\r
1173                 return new LatLng(this.getNorth(), this.getWest());\r
1174         },\r
1175 \r
1176         // @method getSouthEast(): LatLng\r
1177         // Returns the south-east point of the bounds.\r
1178         getSouthEast: function () {\r
1179                 return new LatLng(this.getSouth(), this.getEast());\r
1180         },\r
1181 \r
1182         // @method getWest(): Number\r
1183         // Returns the west longitude of the bounds\r
1184         getWest: function () {\r
1185                 return this._southWest.lng;\r
1186         },\r
1187 \r
1188         // @method getSouth(): Number\r
1189         // Returns the south latitude of the bounds\r
1190         getSouth: function () {\r
1191                 return this._southWest.lat;\r
1192         },\r
1193 \r
1194         // @method getEast(): Number\r
1195         // Returns the east longitude of the bounds\r
1196         getEast: function () {\r
1197                 return this._northEast.lng;\r
1198         },\r
1199 \r
1200         // @method getNorth(): Number\r
1201         // Returns the north latitude of the bounds\r
1202         getNorth: function () {\r
1203                 return this._northEast.lat;\r
1204         },\r
1205 \r
1206         // @method contains(otherBounds: LatLngBounds): Boolean\r
1207         // Returns `true` if the rectangle contains the given one.\r
1208 \r
1209         // @alternative\r
1210         // @method contains (latlng: LatLng): Boolean\r
1211         // Returns `true` if the rectangle contains the given point.\r
1212         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean\r
1213                 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {\r
1214                         obj = toLatLng(obj);\r
1215                 } else {\r
1216                         obj = toLatLngBounds(obj);\r
1217                 }\r
1218 \r
1219                 var sw = this._southWest,\r
1220                     ne = this._northEast,\r
1221                     sw2, ne2;\r
1222 \r
1223                 if (obj instanceof LatLngBounds) {\r
1224                         sw2 = obj.getSouthWest();\r
1225                         ne2 = obj.getNorthEast();\r
1226                 } else {\r
1227                         sw2 = ne2 = obj;\r
1228                 }\r
1229 \r
1230                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&\r
1231                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);\r
1232         },\r
1233 \r
1234         // @method intersects(otherBounds: LatLngBounds): Boolean\r
1235         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.\r
1236         intersects: function (bounds) {\r
1237                 bounds = toLatLngBounds(bounds);\r
1238 \r
1239                 var sw = this._southWest,\r
1240                     ne = this._northEast,\r
1241                     sw2 = bounds.getSouthWest(),\r
1242                     ne2 = bounds.getNorthEast(),\r
1243 \r
1244                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),\r
1245                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);\r
1246 \r
1247                 return latIntersects && lngIntersects;\r
1248         },\r
1249 \r
1250         // @method overlaps(otherBounds: Bounds): Boolean\r
1251         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.\r
1252         overlaps: function (bounds) {\r
1253                 bounds = toLatLngBounds(bounds);\r
1254 \r
1255                 var sw = this._southWest,\r
1256                     ne = this._northEast,\r
1257                     sw2 = bounds.getSouthWest(),\r
1258                     ne2 = bounds.getNorthEast(),\r
1259 \r
1260                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),\r
1261                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);\r
1262 \r
1263                 return latOverlaps && lngOverlaps;\r
1264         },\r
1265 \r
1266         // @method toBBoxString(): String\r
1267         // 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
1268         toBBoxString: function () {\r
1269                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');\r
1270         },\r
1271 \r
1272         // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean\r
1273         // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overriden by setting `maxMargin` to a small number.\r
1274         equals: function (bounds, maxMargin) {\r
1275                 if (!bounds) { return false; }\r
1276 \r
1277                 bounds = toLatLngBounds(bounds);\r
1278 \r
1279                 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&\r
1280                        this._northEast.equals(bounds.getNorthEast(), maxMargin);\r
1281         },\r
1282 \r
1283         // @method isValid(): Boolean\r
1284         // Returns `true` if the bounds are properly initialized.\r
1285         isValid: function () {\r
1286                 return !!(this._southWest && this._northEast);\r
1287         }\r
1288 };\r
1289 \r
1290 // TODO International date line?\r
1291 \r
1292 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)\r
1293 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.\r
1294 \r
1295 // @alternative\r
1296 // @factory L.latLngBounds(latlngs: LatLng[])\r
1297 // 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
1298 function toLatLngBounds(a, b) {\r
1299         if (a instanceof LatLngBounds) {\r
1300                 return a;\r
1301         }\r
1302         return new LatLngBounds(a, b);\r
1303 }
1304
1305 /* @class LatLng\r
1306  * @aka L.LatLng\r
1307  *\r
1308  * Represents a geographical point with a certain latitude and longitude.\r
1309  *\r
1310  * @example\r
1311  *\r
1312  * ```\r
1313  * var latlng = L.latLng(50.5, 30.5);\r
1314  * ```\r
1315  *\r
1316  * 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
1317  *\r
1318  * ```\r
1319  * map.panTo([50, 30]);\r
1320  * map.panTo({lon: 30, lat: 50});\r
1321  * map.panTo({lat: 50, lng: 30});\r
1322  * map.panTo(L.latLng(50, 30));\r
1323  * ```\r
1324  */\r
1325 \r
1326 function LatLng(lat, lng, alt) {\r
1327         if (isNaN(lat) || isNaN(lng)) {\r
1328                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');\r
1329         }\r
1330 \r
1331         // @property lat: Number\r
1332         // Latitude in degrees\r
1333         this.lat = +lat;\r
1334 \r
1335         // @property lng: Number\r
1336         // Longitude in degrees\r
1337         this.lng = +lng;\r
1338 \r
1339         // @property alt: Number\r
1340         // Altitude in meters (optional)\r
1341         if (alt !== undefined) {\r
1342                 this.alt = +alt;\r
1343         }\r
1344 }\r
1345 \r
1346 LatLng.prototype = {\r
1347         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean\r
1348         // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.\r
1349         equals: function (obj, maxMargin) {\r
1350                 if (!obj) { return false; }\r
1351 \r
1352                 obj = toLatLng(obj);\r
1353 \r
1354                 var margin = Math.max(\r
1355                         Math.abs(this.lat - obj.lat),\r
1356                         Math.abs(this.lng - obj.lng));\r
1357 \r
1358                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);\r
1359         },\r
1360 \r
1361         // @method toString(): String\r
1362         // Returns a string representation of the point (for debugging purposes).\r
1363         toString: function (precision) {\r
1364                 return 'LatLng(' +\r
1365                         formatNum(this.lat, precision) + ', ' +\r
1366                         formatNum(this.lng, precision) + ')';\r
1367         },\r
1368 \r
1369         // @method distanceTo(otherLatLng: LatLng): Number\r
1370         // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).\r
1371         distanceTo: function (other) {\r
1372                 return Earth.distance(this, toLatLng(other));\r
1373         },\r
1374 \r
1375         // @method wrap(): LatLng\r
1376         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.\r
1377         wrap: function () {\r
1378                 return Earth.wrapLatLng(this);\r
1379         },\r
1380 \r
1381         // @method toBounds(sizeInMeters: Number): LatLngBounds\r
1382         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.\r
1383         toBounds: function (sizeInMeters) {\r
1384                 var latAccuracy = 180 * sizeInMeters / 40075017,\r
1385                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);\r
1386 \r
1387                 return toLatLngBounds(\r
1388                         [this.lat - latAccuracy, this.lng - lngAccuracy],\r
1389                         [this.lat + latAccuracy, this.lng + lngAccuracy]);\r
1390         },\r
1391 \r
1392         clone: function () {\r
1393                 return new LatLng(this.lat, this.lng, this.alt);\r
1394         }\r
1395 };\r
1396 \r
1397 \r
1398 \r
1399 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng\r
1400 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).\r
1401 \r
1402 // @alternative\r
1403 // @factory L.latLng(coords: Array): LatLng\r
1404 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.\r
1405 \r
1406 // @alternative\r
1407 // @factory L.latLng(coords: Object): LatLng\r
1408 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.\r
1409 \r
1410 function toLatLng(a, b, c) {\r
1411         if (a instanceof LatLng) {\r
1412                 return a;\r
1413         }\r
1414         if (isArray(a) && typeof a[0] !== 'object') {\r
1415                 if (a.length === 3) {\r
1416                         return new LatLng(a[0], a[1], a[2]);\r
1417                 }\r
1418                 if (a.length === 2) {\r
1419                         return new LatLng(a[0], a[1]);\r
1420                 }\r
1421                 return null;\r
1422         }\r
1423         if (a === undefined || a === null) {\r
1424                 return a;\r
1425         }\r
1426         if (typeof a === 'object' && 'lat' in a) {\r
1427                 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);\r
1428         }\r
1429         if (b === undefined) {\r
1430                 return null;\r
1431         }\r
1432         return new LatLng(a, b, c);\r
1433 }
1434
1435 /*\r
1436  * @namespace CRS\r
1437  * @crs L.CRS.Base\r
1438  * Object that defines coordinate reference systems for projecting\r
1439  * geographical points into pixel (screen) coordinates and back (and to\r
1440  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See\r
1441  * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).\r
1442  *\r
1443  * Leaflet defines the most usual CRSs by default. If you want to use a\r
1444  * CRS not defined by default, take a look at the\r
1445  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.\r
1446  */\r
1447 \r
1448 var CRS = {\r
1449         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point\r
1450         // Projects geographical coordinates into pixel coordinates for a given zoom.\r
1451         latLngToPoint: function (latlng, zoom) {\r
1452                 var projectedPoint = this.projection.project(latlng),\r
1453                     scale = this.scale(zoom);\r
1454 \r
1455                 return this.transformation._transform(projectedPoint, scale);\r
1456         },\r
1457 \r
1458         // @method pointToLatLng(point: Point, zoom: Number): LatLng\r
1459         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given\r
1460         // zoom into geographical coordinates.\r
1461         pointToLatLng: function (point, zoom) {\r
1462                 var scale = this.scale(zoom),\r
1463                     untransformedPoint = this.transformation.untransform(point, scale);\r
1464 \r
1465                 return this.projection.unproject(untransformedPoint);\r
1466         },\r
1467 \r
1468         // @method project(latlng: LatLng): Point\r
1469         // Projects geographical coordinates into coordinates in units accepted for\r
1470         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).\r
1471         project: function (latlng) {\r
1472                 return this.projection.project(latlng);\r
1473         },\r
1474 \r
1475         // @method unproject(point: Point): LatLng\r
1476         // Given a projected coordinate returns the corresponding LatLng.\r
1477         // The inverse of `project`.\r
1478         unproject: function (point) {\r
1479                 return this.projection.unproject(point);\r
1480         },\r
1481 \r
1482         // @method scale(zoom: Number): Number\r
1483         // Returns the scale used when transforming projected coordinates into\r
1484         // pixel coordinates for a particular zoom. For example, it returns\r
1485         // `256 * 2^zoom` for Mercator-based CRS.\r
1486         scale: function (zoom) {\r
1487                 return 256 * Math.pow(2, zoom);\r
1488         },\r
1489 \r
1490         // @method zoom(scale: Number): Number\r
1491         // Inverse of `scale()`, returns the zoom level corresponding to a scale\r
1492         // factor of `scale`.\r
1493         zoom: function (scale) {\r
1494                 return Math.log(scale / 256) / Math.LN2;\r
1495         },\r
1496 \r
1497         // @method getProjectedBounds(zoom: Number): Bounds\r
1498         // Returns the projection's bounds scaled and transformed for the provided `zoom`.\r
1499         getProjectedBounds: function (zoom) {\r
1500                 if (this.infinite) { return null; }\r
1501 \r
1502                 var b = this.projection.bounds,\r
1503                     s = this.scale(zoom),\r
1504                     min = this.transformation.transform(b.min, s),\r
1505                     max = this.transformation.transform(b.max, s);\r
1506 \r
1507                 return new Bounds(min, max);\r
1508         },\r
1509 \r
1510         // @method distance(latlng1: LatLng, latlng2: LatLng): Number\r
1511         // Returns the distance between two geographical coordinates.\r
1512 \r
1513         // @property code: String\r
1514         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)\r
1515         //\r
1516         // @property wrapLng: Number[]\r
1517         // An array of two numbers defining whether the longitude (horizontal) coordinate\r
1518         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most\r
1519         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.\r
1520         //\r
1521         // @property wrapLat: Number[]\r
1522         // Like `wrapLng`, but for the latitude (vertical) axis.\r
1523 \r
1524         // wrapLng: [min, max],\r
1525         // wrapLat: [min, max],\r
1526 \r
1527         // @property infinite: Boolean\r
1528         // If true, the coordinate space will be unbounded (infinite in both axes)\r
1529         infinite: false,\r
1530 \r
1531         // @method wrapLatLng(latlng: LatLng): LatLng\r
1532         // Returns a `LatLng` where lat and lng has been wrapped according to the\r
1533         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.\r
1534         wrapLatLng: function (latlng) {\r
1535                 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,\r
1536                     lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,\r
1537                     alt = latlng.alt;\r
1538 \r
1539                 return new LatLng(lat, lng, alt);\r
1540         },\r
1541 \r
1542         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds\r
1543         // Returns a `LatLngBounds` with the same size as the given one, ensuring\r
1544         // that its center is within the CRS's bounds.\r
1545         // Only accepts actual `L.LatLngBounds` instances, not arrays.\r
1546         wrapLatLngBounds: function (bounds) {\r
1547                 var center = bounds.getCenter(),\r
1548                     newCenter = this.wrapLatLng(center),\r
1549                     latShift = center.lat - newCenter.lat,\r
1550                     lngShift = center.lng - newCenter.lng;\r
1551 \r
1552                 if (latShift === 0 && lngShift === 0) {\r
1553                         return bounds;\r
1554                 }\r
1555 \r
1556                 var sw = bounds.getSouthWest(),\r
1557                     ne = bounds.getNorthEast(),\r
1558                     newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),\r
1559                     newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);\r
1560 \r
1561                 return new LatLngBounds(newSw, newNe);\r
1562         }\r
1563 };
1564
1565 /*
1566  * @namespace CRS
1567  * @crs L.CRS.Earth
1568  *
1569  * Serves as the base for CRS that are global such that they cover the earth.
1570  * Can only be used as the base for other CRS and cannot be used directly,
1571  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1572  * meters.
1573  */
1574
1575 var Earth = extend({}, CRS, {
1576         wrapLng: [-180, 180],
1577
1578         // Mean Earth Radius, as recommended for use by
1579         // the International Union of Geodesy and Geophysics,
1580         // see http://rosettacode.org/wiki/Haversine_formula
1581         R: 6371000,
1582
1583         // distance between two geographical points using spherical law of cosines approximation
1584         distance: function (latlng1, latlng2) {
1585                 var rad = Math.PI / 180,
1586                     lat1 = latlng1.lat * rad,
1587                     lat2 = latlng2.lat * rad,
1588                     a = Math.sin(lat1) * Math.sin(lat2) +
1589                         Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
1590
1591                 return this.R * Math.acos(Math.min(a, 1));
1592         }
1593 });
1594
1595 /*\r
1596  * @namespace Projection\r
1597  * @projection L.Projection.SphericalMercator\r
1598  *\r
1599  * Spherical Mercator projection — the most common projection for online maps,\r
1600  * used by almost all free and commercial tile providers. Assumes that Earth is\r
1601  * a sphere. Used by the `EPSG:3857` CRS.\r
1602  */\r
1603 \r
1604 var SphericalMercator = {\r
1605 \r
1606         R: 6378137,\r
1607         MAX_LATITUDE: 85.0511287798,\r
1608 \r
1609         project: function (latlng) {\r
1610                 var d = Math.PI / 180,\r
1611                     max = this.MAX_LATITUDE,\r
1612                     lat = Math.max(Math.min(max, latlng.lat), -max),\r
1613                     sin = Math.sin(lat * d);\r
1614 \r
1615                 return new Point(\r
1616                                 this.R * latlng.lng * d,\r
1617                                 this.R * Math.log((1 + sin) / (1 - sin)) / 2);\r
1618         },\r
1619 \r
1620         unproject: function (point) {\r
1621                 var d = 180 / Math.PI;\r
1622 \r
1623                 return new LatLng(\r
1624                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,\r
1625                         point.x * d / this.R);\r
1626         },\r
1627 \r
1628         bounds: (function () {\r
1629                 var d = 6378137 * Math.PI;\r
1630                 return new Bounds([-d, -d], [d, d]);\r
1631         })()\r
1632 };
1633
1634 /*\r
1635  * @class Transformation\r
1636  * @aka L.Transformation\r
1637  *\r
1638  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`\r
1639  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing\r
1640  * the reverse. Used by Leaflet in its projections code.\r
1641  *\r
1642  * @example\r
1643  *\r
1644  * ```js\r
1645  * var transformation = L.transformation(2, 5, -1, 10),\r
1646  *      p = L.point(1, 2),\r
1647  *      p2 = transformation.transform(p), //  L.point(7, 8)\r
1648  *      p3 = transformation.untransform(p2); //  L.point(1, 2)\r
1649  * ```\r
1650  */\r
1651 \r
1652 \r
1653 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)\r
1654 // Creates a `Transformation` object with the given coefficients.\r
1655 function Transformation(a, b, c, d) {\r
1656         if (isArray(a)) {\r
1657                 // use array properties\r
1658                 this._a = a[0];\r
1659                 this._b = a[1];\r
1660                 this._c = a[2];\r
1661                 this._d = a[3];\r
1662                 return;\r
1663         }\r
1664         this._a = a;\r
1665         this._b = b;\r
1666         this._c = c;\r
1667         this._d = d;\r
1668 }\r
1669 \r
1670 Transformation.prototype = {\r
1671         // @method transform(point: Point, scale?: Number): Point\r
1672         // Returns a transformed point, optionally multiplied by the given scale.\r
1673         // Only accepts actual `L.Point` instances, not arrays.\r
1674         transform: function (point, scale) { // (Point, Number) -> Point\r
1675                 return this._transform(point.clone(), scale);\r
1676         },\r
1677 \r
1678         // destructive transform (faster)\r
1679         _transform: function (point, scale) {\r
1680                 scale = scale || 1;\r
1681                 point.x = scale * (this._a * point.x + this._b);\r
1682                 point.y = scale * (this._c * point.y + this._d);\r
1683                 return point;\r
1684         },\r
1685 \r
1686         // @method untransform(point: Point, scale?: Number): Point\r
1687         // Returns the reverse transformation of the given point, optionally divided\r
1688         // by the given scale. Only accepts actual `L.Point` instances, not arrays.\r
1689         untransform: function (point, scale) {\r
1690                 scale = scale || 1;\r
1691                 return new Point(\r
1692                         (point.x / scale - this._b) / this._a,\r
1693                         (point.y / scale - this._d) / this._c);\r
1694         }\r
1695 };\r
1696 \r
1697 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)\r
1698 \r
1699 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)\r
1700 // Instantiates a Transformation object with the given coefficients.\r
1701 \r
1702 // @alternative\r
1703 // @factory L.transformation(coefficients: Array): Transformation\r
1704 // Expects an coeficients array of the form\r
1705 // `[a: Number, b: Number, c: Number, d: Number]`.\r
1706 \r
1707 function toTransformation(a, b, c, d) {\r
1708         return new Transformation(a, b, c, d);\r
1709 }
1710
1711 /*\r
1712  * @namespace CRS\r
1713  * @crs L.CRS.EPSG3857\r
1714  *\r
1715  * The most common CRS for online maps, used by almost all free and commercial\r
1716  * tile providers. Uses Spherical Mercator projection. Set in by default in\r
1717  * Map's `crs` option.\r
1718  */\r
1719 \r
1720 var EPSG3857 = extend({}, Earth, {\r
1721         code: 'EPSG:3857',\r
1722         projection: SphericalMercator,\r
1723 \r
1724         transformation: (function () {\r
1725                 var scale = 0.5 / (Math.PI * SphericalMercator.R);\r
1726                 return toTransformation(scale, 0.5, -scale, 0.5);\r
1727         }())\r
1728 });\r
1729 \r
1730 var EPSG900913 = extend({}, EPSG3857, {\r
1731         code: 'EPSG:900913'\r
1732 });
1733
1734 // @namespace SVG; @section
1735 // There are several static functions which can be called without instantiating L.SVG:
1736
1737 // @function create(name: String): SVGElement
1738 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1739 // corresponding to the class name passed. For example, using 'line' will return
1740 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1741 function svgCreate(name) {
1742         return document.createElementNS('http://www.w3.org/2000/svg', name);
1743 }
1744
1745 // @function pointsToPath(rings: Point[], closed: Boolean): String
1746 // Generates a SVG path string for multiple rings, with each ring turning
1747 // into "M..L..L.." instructions
1748 function pointsToPath(rings, closed) {
1749         var str = '',
1750         i, j, len, len2, points, p;
1751
1752         for (i = 0, len = rings.length; i < len; i++) {
1753                 points = rings[i];
1754
1755                 for (j = 0, len2 = points.length; j < len2; j++) {
1756                         p = points[j];
1757                         str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1758                 }
1759
1760                 // closes the ring for polygons; "x" is VML syntax
1761                 str += closed ? (svg ? 'z' : 'x') : '';
1762         }
1763
1764         // SVG complains about empty path strings
1765         return str || 'M0 0';
1766 }
1767
1768 /*\r
1769  * @namespace Browser\r
1770  * @aka L.Browser\r
1771  *\r
1772  * A namespace with static properties for browser/feature detection used by Leaflet internally.\r
1773  *\r
1774  * @example\r
1775  *\r
1776  * ```js\r
1777  * if (L.Browser.ielt9) {\r
1778  *   alert('Upgrade your browser, dude!');\r
1779  * }\r
1780  * ```\r
1781  */\r
1782 \r
1783 var style$1 = document.documentElement.style;\r
1784 \r
1785 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).\r
1786 var ie = 'ActiveXObject' in window;\r
1787 \r
1788 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.\r
1789 var ielt9 = ie && !document.addEventListener;\r
1790 \r
1791 // @property edge: Boolean; `true` for the Edge web browser.\r
1792 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);\r
1793 \r
1794 // @property webkit: Boolean;\r
1795 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).\r
1796 var webkit = userAgentContains('webkit');\r
1797 \r
1798 // @property android: Boolean\r
1799 // `true` for any browser running on an Android platform.\r
1800 var android = userAgentContains('android');\r
1801 \r
1802 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.\r
1803 var android23 = userAgentContains('android 2') || userAgentContains('android 3');\r
1804 \r
1805 // @property opera: Boolean; `true` for the Opera browser\r
1806 var opera = !!window.opera;\r
1807 \r
1808 // @property chrome: Boolean; `true` for the Chrome browser.\r
1809 var chrome = userAgentContains('chrome');\r
1810 \r
1811 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.\r
1812 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;\r
1813 \r
1814 // @property safari: Boolean; `true` for the Safari browser.\r
1815 var safari = !chrome && userAgentContains('safari');\r
1816 \r
1817 var phantom = userAgentContains('phantom');\r
1818 \r
1819 // @property opera12: Boolean\r
1820 // `true` for the Opera browser supporting CSS transforms (version 12 or later).\r
1821 var opera12 = 'OTransition' in style$1;\r
1822 \r
1823 // @property win: Boolean; `true` when the browser is running in a Windows platform\r
1824 var win = navigator.platform.indexOf('Win') === 0;\r
1825 \r
1826 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.\r
1827 var ie3d = ie && ('transition' in style$1);\r
1828 \r
1829 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.\r
1830 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;\r
1831 \r
1832 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.\r
1833 var gecko3d = 'MozPerspective' in style$1;\r
1834 \r
1835 // @property any3d: Boolean\r
1836 // `true` for all browsers supporting CSS transforms.\r
1837 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;\r
1838 \r
1839 // @property mobile: Boolean; `true` for all browsers running in a mobile device.\r
1840 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');\r
1841 \r
1842 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.\r
1843 var mobileWebkit = mobile && webkit;\r
1844 \r
1845 // @property mobileWebkit3d: Boolean\r
1846 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.\r
1847 var mobileWebkit3d = mobile && webkit3d;\r
1848 \r
1849 // @property msPointer: Boolean\r
1850 // `true` for browsers implementing the Microsoft touch events model (notably IE10).\r
1851 var msPointer = !window.PointerEvent && window.MSPointerEvent;\r
1852 \r
1853 // @property pointer: Boolean\r
1854 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).\r
1855 var pointer = !!(window.PointerEvent || msPointer);\r
1856 \r
1857 // @property touch: Boolean\r
1858 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).\r
1859 // This does not necessarily mean that the browser is running in a computer with\r
1860 // a touchscreen, it only means that the browser is capable of understanding\r
1861 // touch events.\r
1862 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||\r
1863                 (window.DocumentTouch && document instanceof window.DocumentTouch));\r
1864 \r
1865 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.\r
1866 var mobileOpera = mobile && opera;\r
1867 \r
1868 // @property mobileGecko: Boolean\r
1869 // `true` for gecko-based browsers running in a mobile device.\r
1870 var mobileGecko = mobile && gecko;\r
1871 \r
1872 // @property retina: Boolean\r
1873 // `true` for browsers on a high-resolution "retina" screen.\r
1874 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;\r
1875 \r
1876 \r
1877 // @property canvas: Boolean\r
1878 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).\r
1879 var canvas = (function () {\r
1880         return !!document.createElement('canvas').getContext;\r
1881 }());\r
1882 \r
1883 // @property svg: Boolean\r
1884 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).\r
1885 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);\r
1886 \r
1887 // @property vml: Boolean\r
1888 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).\r
1889 var vml = !svg && (function () {\r
1890         try {\r
1891                 var div = document.createElement('div');\r
1892                 div.innerHTML = '<v:shape adj="1"/>';\r
1893 \r
1894                 var shape = div.firstChild;\r
1895                 shape.style.behavior = 'url(#default#VML)';\r
1896 \r
1897                 return shape && (typeof shape.adj === 'object');\r
1898 \r
1899         } catch (e) {\r
1900                 return false;\r
1901         }\r
1902 }());\r
1903 \r
1904 \r
1905 function userAgentContains(str) {\r
1906         return navigator.userAgent.toLowerCase().indexOf(str) >= 0;\r
1907 }\r
1908
1909
1910 var Browser = (Object.freeze || Object)({
1911         ie: ie,
1912         ielt9: ielt9,
1913         edge: edge,
1914         webkit: webkit,
1915         android: android,
1916         android23: android23,
1917         opera: opera,
1918         chrome: chrome,
1919         gecko: gecko,
1920         safari: safari,
1921         phantom: phantom,
1922         opera12: opera12,
1923         win: win,
1924         ie3d: ie3d,
1925         webkit3d: webkit3d,
1926         gecko3d: gecko3d,
1927         any3d: any3d,
1928         mobile: mobile,
1929         mobileWebkit: mobileWebkit,
1930         mobileWebkit3d: mobileWebkit3d,
1931         msPointer: msPointer,
1932         pointer: pointer,
1933         touch: touch,
1934         mobileOpera: mobileOpera,
1935         mobileGecko: mobileGecko,
1936         retina: retina,
1937         canvas: canvas,
1938         svg: svg,
1939         vml: vml
1940 });
1941
1942 /*
1943  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1944  */
1945
1946
1947 var POINTER_DOWN =   msPointer ? 'MSPointerDown'   : 'pointerdown';
1948 var POINTER_MOVE =   msPointer ? 'MSPointerMove'   : 'pointermove';
1949 var POINTER_UP =     msPointer ? 'MSPointerUp'     : 'pointerup';
1950 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
1951 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
1952 var _pointers = {};
1953 var _pointerDocListener = false;
1954
1955 // DomEvent.DoubleTap needs to know about this
1956 var _pointersCount = 0;
1957
1958 // Provides a touch events wrapper for (ms)pointer events.
1959 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
1960
1961 function addPointerListener(obj, type, handler, id) {
1962         if (type === 'touchstart') {
1963                 _addPointerStart(obj, handler, id);
1964
1965         } else if (type === 'touchmove') {
1966                 _addPointerMove(obj, handler, id);
1967
1968         } else if (type === 'touchend') {
1969                 _addPointerEnd(obj, handler, id);
1970         }
1971
1972         return this;
1973 }
1974
1975 function removePointerListener(obj, type, id) {
1976         var handler = obj['_leaflet_' + type + id];
1977
1978         if (type === 'touchstart') {
1979                 obj.removeEventListener(POINTER_DOWN, handler, false);
1980
1981         } else if (type === 'touchmove') {
1982                 obj.removeEventListener(POINTER_MOVE, handler, false);
1983
1984         } else if (type === 'touchend') {
1985                 obj.removeEventListener(POINTER_UP, handler, false);
1986                 obj.removeEventListener(POINTER_CANCEL, handler, false);
1987         }
1988
1989         return this;
1990 }
1991
1992 function _addPointerStart(obj, handler, id) {
1993         var onDown = bind(function (e) {
1994                 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
1995                         // In IE11, some touch events needs to fire for form controls, or
1996                         // the controls will stop working. We keep a whitelist of tag names that
1997                         // need these events. For other target tags, we prevent default on the event.
1998                         if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
1999                                 preventDefault(e);
2000                         } else {
2001                                 return;
2002                         }
2003                 }
2004
2005                 _handlePointer(e, handler);
2006         });
2007
2008         obj['_leaflet_touchstart' + id] = onDown;
2009         obj.addEventListener(POINTER_DOWN, onDown, false);
2010
2011         // need to keep track of what pointers and how many are active to provide e.touches emulation
2012         if (!_pointerDocListener) {
2013                 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2014                 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2015                 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2016                 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2017                 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2018
2019                 _pointerDocListener = true;
2020         }
2021 }
2022
2023 function _globalPointerDown(e) {
2024         _pointers[e.pointerId] = e;
2025         _pointersCount++;
2026 }
2027
2028 function _globalPointerMove(e) {
2029         if (_pointers[e.pointerId]) {
2030                 _pointers[e.pointerId] = e;
2031         }
2032 }
2033
2034 function _globalPointerUp(e) {
2035         delete _pointers[e.pointerId];
2036         _pointersCount--;
2037 }
2038
2039 function _handlePointer(e, handler) {
2040         e.touches = [];
2041         for (var i in _pointers) {
2042                 e.touches.push(_pointers[i]);
2043         }
2044         e.changedTouches = [e];
2045
2046         handler(e);
2047 }
2048
2049 function _addPointerMove(obj, handler, id) {
2050         var onMove = function (e) {
2051                 // don't fire touch moves when mouse isn't down
2052                 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2053
2054                 _handlePointer(e, handler);
2055         };
2056
2057         obj['_leaflet_touchmove' + id] = onMove;
2058         obj.addEventListener(POINTER_MOVE, onMove, false);
2059 }
2060
2061 function _addPointerEnd(obj, handler, id) {
2062         var onUp = function (e) {
2063                 _handlePointer(e, handler);
2064         };
2065
2066         obj['_leaflet_touchend' + id] = onUp;
2067         obj.addEventListener(POINTER_UP, onUp, false);
2068         obj.addEventListener(POINTER_CANCEL, onUp, false);
2069 }
2070
2071 /*\r
2072  * Extends the event handling code with double tap support for mobile browsers.\r
2073  */\r
2074 \r
2075 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2076 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2077 var _pre = '_leaflet_';\r
2078 \r
2079 // inspired by Zepto touch code by Thomas Fuchs\r
2080 function addDoubleTapListener(obj, handler, id) {\r
2081         var last, touch$$1,\r
2082             doubleTap = false,\r
2083             delay = 250;\r
2084 \r
2085         function onTouchStart(e) {\r
2086                 var count;\r
2087 \r
2088                 if (pointer) {\r
2089                         if ((!edge) || e.pointerType === 'mouse') { return; }\r
2090                         count = _pointersCount;\r
2091                 } else {\r
2092                         count = e.touches.length;\r
2093                 }\r
2094 \r
2095                 if (count > 1) { return; }\r
2096 \r
2097                 var now = Date.now(),\r
2098                     delta = now - (last || now);\r
2099 \r
2100                 touch$$1 = e.touches ? e.touches[0] : e;\r
2101                 doubleTap = (delta > 0 && delta <= delay);\r
2102                 last = now;\r
2103         }\r
2104 \r
2105         function onTouchEnd(e) {\r
2106                 if (doubleTap && !touch$$1.cancelBubble) {\r
2107                         if (pointer) {\r
2108                                 if ((!edge) || e.pointerType === 'mouse') { return; }\r
2109                                 // work around .type being readonly with MSPointer* events\r
2110                                 var newTouch = {},\r
2111                                     prop, i;\r
2112 \r
2113                                 for (i in touch$$1) {\r
2114                                         prop = touch$$1[i];\r
2115                                         newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;\r
2116                                 }\r
2117                                 touch$$1 = newTouch;\r
2118                         }\r
2119                         touch$$1.type = 'dblclick';\r
2120                         handler(touch$$1);\r
2121                         last = null;\r
2122                 }\r
2123         }\r
2124 \r
2125         obj[_pre + _touchstart + id] = onTouchStart;\r
2126         obj[_pre + _touchend + id] = onTouchEnd;\r
2127         obj[_pre + 'dblclick' + id] = handler;\r
2128 \r
2129         obj.addEventListener(_touchstart, onTouchStart, false);\r
2130         obj.addEventListener(_touchend, onTouchEnd, false);\r
2131 \r
2132         // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),\r
2133         // the browser doesn't fire touchend/pointerup events but does fire\r
2134         // native dblclicks. See #4127.\r
2135         // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.\r
2136         obj.addEventListener('dblclick', handler, false);\r
2137 \r
2138         return this;\r
2139 }\r
2140 \r
2141 function removeDoubleTapListener(obj, id) {\r
2142         var touchstart = obj[_pre + _touchstart + id],\r
2143             touchend = obj[_pre + _touchend + id],\r
2144             dblclick = obj[_pre + 'dblclick' + id];\r
2145 \r
2146         obj.removeEventListener(_touchstart, touchstart, false);\r
2147         obj.removeEventListener(_touchend, touchend, false);\r
2148         if (!edge) {\r
2149                 obj.removeEventListener('dblclick', dblclick, false);\r
2150         }\r
2151 \r
2152         return this;\r
2153 }
2154
2155 /*\r
2156  * @namespace DomEvent\r
2157  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.\r
2158  */\r
2159 \r
2160 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.\r
2161 \r
2162 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this\r
2163 // Adds a listener function (`fn`) to a particular DOM event type of the\r
2164 // element `el`. You can optionally specify the context of the listener\r
2165 // (object the `this` keyword will point to). You can also pass several\r
2166 // space-separated types (e.g. `'click dblclick'`).\r
2167 \r
2168 // @alternative\r
2169 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this\r
2170 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`\r
2171 function on(obj, types, fn, context) {\r
2172 \r
2173         if (typeof types === 'object') {\r
2174                 for (var type in types) {\r
2175                         addOne(obj, type, types[type], fn);\r
2176                 }\r
2177         } else {\r
2178                 types = splitWords(types);\r
2179 \r
2180                 for (var i = 0, len = types.length; i < len; i++) {\r
2181                         addOne(obj, types[i], fn, context);\r
2182                 }\r
2183         }\r
2184 \r
2185         return this;\r
2186 }\r
2187 \r
2188 var eventsKey = '_leaflet_events';\r
2189 \r
2190 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this\r
2191 // Removes a previously added listener function. If no function is specified,\r
2192 // it will remove all the listeners of that particular DOM event from the element.\r
2193 // Note that if you passed a custom context to on, you must pass the same\r
2194 // context to `off` in order to remove the listener.\r
2195 \r
2196 // @alternative\r
2197 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this\r
2198 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`\r
2199 \r
2200 // @alternative\r
2201 // @function off(el: HTMLElement): this\r
2202 // Removes all known event listeners\r
2203 function off(obj, types, fn, context) {\r
2204 \r
2205         if (typeof types === 'object') {\r
2206                 for (var type in types) {\r
2207                         removeOne(obj, type, types[type], fn);\r
2208                 }\r
2209         } else if (types) {\r
2210                 types = splitWords(types);\r
2211 \r
2212                 for (var i = 0, len = types.length; i < len; i++) {\r
2213                         removeOne(obj, types[i], fn, context);\r
2214                 }\r
2215         } else {\r
2216                 for (var j in obj[eventsKey]) {\r
2217                         removeOne(obj, j, obj[eventsKey][j]);\r
2218                 }\r
2219                 delete obj[eventsKey];\r
2220         }\r
2221 \r
2222         return this;\r
2223 }\r
2224 \r
2225 function addOne(obj, type, fn, context) {\r
2226         var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');\r
2227 \r
2228         if (obj[eventsKey] && obj[eventsKey][id]) { return this; }\r
2229 \r
2230         var handler = function (e) {\r
2231                 return fn.call(context || obj, e || window.event);\r
2232         };\r
2233 \r
2234         var originalHandler = handler;\r
2235 \r
2236         if (pointer && type.indexOf('touch') === 0) {\r
2237                 // Needs DomEvent.Pointer.js\r
2238                 addPointerListener(obj, type, handler, id);\r
2239 \r
2240         } else if (touch && (type === 'dblclick') && addDoubleTapListener &&\r
2241                    !(pointer && chrome)) {\r
2242                 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener\r
2243                 // See #5180\r
2244                 addDoubleTapListener(obj, handler, id);\r
2245 \r
2246         } else if ('addEventListener' in obj) {\r
2247 \r
2248                 if (type === 'mousewheel') {\r
2249                         obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);\r
2250 \r
2251                 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {\r
2252                         handler = function (e) {\r
2253                                 e = e || window.event;\r
2254                                 if (isExternalTarget(obj, e)) {\r
2255                                         originalHandler(e);\r
2256                                 }\r
2257                         };\r
2258                         obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);\r
2259 \r
2260                 } else {\r
2261                         if (type === 'click' && android) {\r
2262                                 handler = function (e) {\r
2263                                         filterClick(e, originalHandler);\r
2264                                 };\r
2265                         }\r
2266                         obj.addEventListener(type, handler, false);\r
2267                 }\r
2268 \r
2269         } else if ('attachEvent' in obj) {\r
2270                 obj.attachEvent('on' + type, handler);\r
2271         }\r
2272 \r
2273         obj[eventsKey] = obj[eventsKey] || {};\r
2274         obj[eventsKey][id] = handler;\r
2275 }\r
2276 \r
2277 function removeOne(obj, type, fn, context) {\r
2278 \r
2279         var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),\r
2280             handler = obj[eventsKey] && obj[eventsKey][id];\r
2281 \r
2282         if (!handler) { return this; }\r
2283 \r
2284         if (pointer && type.indexOf('touch') === 0) {\r
2285                 removePointerListener(obj, type, id);\r
2286 \r
2287         } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {\r
2288                 removeDoubleTapListener(obj, id);\r
2289 \r
2290         } else if ('removeEventListener' in obj) {\r
2291 \r
2292                 if (type === 'mousewheel') {\r
2293                         obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);\r
2294 \r
2295                 } else {\r
2296                         obj.removeEventListener(\r
2297                                 type === 'mouseenter' ? 'mouseover' :\r
2298                                 type === 'mouseleave' ? 'mouseout' : type, handler, false);\r
2299                 }\r
2300 \r
2301         } else if ('detachEvent' in obj) {\r
2302                 obj.detachEvent('on' + type, handler);\r
2303         }\r
2304 \r
2305         obj[eventsKey][id] = null;\r
2306 }\r
2307 \r
2308 // @function stopPropagation(ev: DOMEvent): this\r
2309 // Stop the given event from propagation to parent elements. Used inside the listener functions:\r
2310 // ```js\r
2311 // L.DomEvent.on(div, 'click', function (ev) {\r
2312 //      L.DomEvent.stopPropagation(ev);\r
2313 // });\r
2314 // ```\r
2315 function stopPropagation(e) {\r
2316 \r
2317         if (e.stopPropagation) {\r
2318                 e.stopPropagation();\r
2319         } else if (e.originalEvent) {  // In case of Leaflet event.\r
2320                 e.originalEvent._stopped = true;\r
2321         } else {\r
2322                 e.cancelBubble = true;\r
2323         }\r
2324         skipped(e);\r
2325 \r
2326         return this;\r
2327 }\r
2328 \r
2329 // @function disableScrollPropagation(el: HTMLElement): this\r
2330 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).\r
2331 function disableScrollPropagation(el) {\r
2332         addOne(el, 'mousewheel', stopPropagation);\r
2333         return this;\r
2334 }\r
2335 \r
2336 // @function disableClickPropagation(el: HTMLElement): this\r
2337 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,\r
2338 // `'mousedown'` and `'touchstart'` events (plus browser variants).\r
2339 function disableClickPropagation(el) {\r
2340         on(el, 'mousedown touchstart dblclick', stopPropagation);\r
2341         addOne(el, 'click', fakeStop);\r
2342         return this;\r
2343 }\r
2344 \r
2345 // @function preventDefault(ev: DOMEvent): this\r
2346 // Prevents the default action of the DOM Event `ev` from happening (such as\r
2347 // following a link in the href of the a element, or doing a POST request\r
2348 // with page reload when a `<form>` is submitted).\r
2349 // Use it inside listener functions.\r
2350 function preventDefault(e) {\r
2351         if (e.preventDefault) {\r
2352                 e.preventDefault();\r
2353         } else {\r
2354                 e.returnValue = false;\r
2355         }\r
2356         return this;\r
2357 }\r
2358 \r
2359 // @function stop(ev): this\r
2360 // Does `stopPropagation` and `preventDefault` at the same time.\r
2361 function stop(e) {\r
2362         preventDefault(e);\r
2363         stopPropagation(e);\r
2364         return this;\r
2365 }\r
2366 \r
2367 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point\r
2368 // Gets normalized mouse position from a DOM event relative to the\r
2369 // `container` or to the whole page if not specified.\r
2370 function getMousePosition(e, container) {\r
2371         if (!container) {\r
2372                 return new Point(e.clientX, e.clientY);\r
2373         }\r
2374 \r
2375         var rect = container.getBoundingClientRect();\r
2376 \r
2377         return new Point(\r
2378                 e.clientX - rect.left - container.clientLeft,\r
2379                 e.clientY - rect.top - container.clientTop);\r
2380 }\r
2381 \r
2382 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),\r
2383 // and Firefox scrolls device pixels, not CSS pixels\r
2384 var wheelPxFactor =\r
2385         (win && chrome) ? 2 * window.devicePixelRatio :\r
2386         gecko ? window.devicePixelRatio : 1;\r
2387 \r
2388 // @function getWheelDelta(ev: DOMEvent): Number\r
2389 // Gets normalized wheel delta from a mousewheel DOM event, in vertical\r
2390 // pixels scrolled (negative if scrolling down).\r
2391 // Events from pointing devices without precise scrolling are mapped to\r
2392 // a best guess of 60 pixels.\r
2393 function getWheelDelta(e) {\r
2394         return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta\r
2395                (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels\r
2396                (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines\r
2397                (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages\r
2398                (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events\r
2399                e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels\r
2400                (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines\r
2401                e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages\r
2402                0;\r
2403 }\r
2404 \r
2405 var skipEvents = {};\r
2406 \r
2407 function fakeStop(e) {\r
2408         // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)\r
2409         skipEvents[e.type] = true;\r
2410 }\r
2411 \r
2412 function skipped(e) {\r
2413         var events = skipEvents[e.type];\r
2414         // reset when checking, as it's only used in map container and propagates outside of the map\r
2415         skipEvents[e.type] = false;\r
2416         return events;\r
2417 }\r
2418 \r
2419 // check if element really left/entered the event target (for mouseenter/mouseleave)\r
2420 function isExternalTarget(el, e) {\r
2421 \r
2422         var related = e.relatedTarget;\r
2423 \r
2424         if (!related) { return true; }\r
2425 \r
2426         try {\r
2427                 while (related && (related !== el)) {\r
2428                         related = related.parentNode;\r
2429                 }\r
2430         } catch (err) {\r
2431                 return false;\r
2432         }\r
2433         return (related !== el);\r
2434 }\r
2435 \r
2436 var lastClick;\r
2437 \r
2438 // this is a horrible workaround for a bug in Android where a single touch triggers two click events\r
2439 function filterClick(e, handler) {\r
2440         var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),\r
2441             elapsed = lastClick && (timeStamp - lastClick);\r
2442 \r
2443         // are they closer together than 500ms yet more than 100ms?\r
2444         // Android typically triggers them ~300ms apart while multiple listeners\r
2445         // on the same event should be triggered far faster;\r
2446         // or check if click is simulated on the element, and if it is, reject any non-simulated events\r
2447 \r
2448         if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {\r
2449                 stop(e);\r
2450                 return;\r
2451         }\r
2452         lastClick = timeStamp;\r
2453 \r
2454         handler(e);\r
2455 }\r
2456 \r
2457 \r
2458
2459
2460 var DomEvent = (Object.freeze || Object)({
2461         on: on,
2462         off: off,
2463         stopPropagation: stopPropagation,
2464         disableScrollPropagation: disableScrollPropagation,
2465         disableClickPropagation: disableClickPropagation,
2466         preventDefault: preventDefault,
2467         stop: stop,
2468         getMousePosition: getMousePosition,
2469         getWheelDelta: getWheelDelta,
2470         fakeStop: fakeStop,
2471         skipped: skipped,
2472         isExternalTarget: isExternalTarget,
2473         addListener: on,
2474         removeListener: off
2475 });
2476
2477 /*\r
2478  * @namespace DomUtil\r
2479  *\r
2480  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)\r
2481  * tree, used by Leaflet internally.\r
2482  *\r
2483  * Most functions expecting or returning a `HTMLElement` also work for\r
2484  * SVG elements. The only difference is that classes refer to CSS classes\r
2485  * in HTML and SVG classes in SVG.\r
2486  */\r
2487 \r
2488 \r
2489 // @property TRANSFORM: String\r
2490 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).\r
2491 var TRANSFORM = testProp(\r
2492         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);\r
2493 \r
2494 // webkitTransition comes first because some browser versions that drop vendor prefix don't do\r
2495 // the same for the transitionend event, in particular the Android 4.1 stock browser\r
2496 \r
2497 // @property TRANSITION: String\r
2498 // Vendor-prefixed transition style name.\r
2499 var TRANSITION = testProp(\r
2500         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);\r
2501 \r
2502 // @property TRANSITION_END: String\r
2503 // Vendor-prefixed transitionend event name.\r
2504 var TRANSITION_END =\r
2505         TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';\r
2506 \r
2507 \r
2508 // @function get(id: String|HTMLElement): HTMLElement\r
2509 // Returns an element given its DOM id, or returns the element itself\r
2510 // if it was passed directly.\r
2511 function get(id) {\r
2512         return typeof id === 'string' ? document.getElementById(id) : id;\r
2513 }\r
2514 \r
2515 // @function getStyle(el: HTMLElement, styleAttrib: String): String\r
2516 // Returns the value for a certain style attribute on an element,\r
2517 // including computed values or values set through CSS.\r
2518 function getStyle(el, style) {\r
2519         var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);\r
2520 \r
2521         if ((!value || value === 'auto') && document.defaultView) {\r
2522                 var css = document.defaultView.getComputedStyle(el, null);\r
2523                 value = css ? css[style] : null;\r
2524         }\r
2525         return value === 'auto' ? null : value;\r
2526 }\r
2527 \r
2528 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement\r
2529 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.\r
2530 function create$1(tagName, className, container) {\r
2531         var el = document.createElement(tagName);\r
2532         el.className = className || '';\r
2533 \r
2534         if (container) {\r
2535                 container.appendChild(el);\r
2536         }\r
2537         return el;\r
2538 }\r
2539 \r
2540 // @function remove(el: HTMLElement)\r
2541 // Removes `el` from its parent element\r
2542 function remove(el) {\r
2543         var parent = el.parentNode;\r
2544         if (parent) {\r
2545                 parent.removeChild(el);\r
2546         }\r
2547 }\r
2548 \r
2549 // @function empty(el: HTMLElement)\r
2550 // Removes all of `el`'s children elements from `el`\r
2551 function empty(el) {\r
2552         while (el.firstChild) {\r
2553                 el.removeChild(el.firstChild);\r
2554         }\r
2555 }\r
2556 \r
2557 // @function toFront(el: HTMLElement)\r
2558 // Makes `el` the last child of its parent, so it renders in front of the other children.\r
2559 function toFront(el) {\r
2560         var parent = el.parentNode;\r
2561         if (parent.lastChild !== el) {\r
2562                 parent.appendChild(el);\r
2563         }\r
2564 }\r
2565 \r
2566 // @function toBack(el: HTMLElement)\r
2567 // Makes `el` the first child of its parent, so it renders behind the other children.\r
2568 function toBack(el) {\r
2569         var parent = el.parentNode;\r
2570         if (parent.firstChild !== el) {\r
2571                 parent.insertBefore(el, parent.firstChild);\r
2572         }\r
2573 }\r
2574 \r
2575 // @function hasClass(el: HTMLElement, name: String): Boolean\r
2576 // Returns `true` if the element's class attribute contains `name`.\r
2577 function hasClass(el, name) {\r
2578         if (el.classList !== undefined) {\r
2579                 return el.classList.contains(name);\r
2580         }\r
2581         var className = getClass(el);\r
2582         return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);\r
2583 }\r
2584 \r
2585 // @function addClass(el: HTMLElement, name: String)\r
2586 // Adds `name` to the element's class attribute.\r
2587 function addClass(el, name) {\r
2588         if (el.classList !== undefined) {\r
2589                 var classes = splitWords(name);\r
2590                 for (var i = 0, len = classes.length; i < len; i++) {\r
2591                         el.classList.add(classes[i]);\r
2592                 }\r
2593         } else if (!hasClass(el, name)) {\r
2594                 var className = getClass(el);\r
2595                 setClass(el, (className ? className + ' ' : '') + name);\r
2596         }\r
2597 }\r
2598 \r
2599 // @function removeClass(el: HTMLElement, name: String)\r
2600 // Removes `name` from the element's class attribute.\r
2601 function removeClass(el, name) {\r
2602         if (el.classList !== undefined) {\r
2603                 el.classList.remove(name);\r
2604         } else {\r
2605                 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));\r
2606         }\r
2607 }\r
2608 \r
2609 // @function setClass(el: HTMLElement, name: String)\r
2610 // Sets the element's class.\r
2611 function setClass(el, name) {\r
2612         if (el.className.baseVal === undefined) {\r
2613                 el.className = name;\r
2614         } else {\r
2615                 // in case of SVG element\r
2616                 el.className.baseVal = name;\r
2617         }\r
2618 }\r
2619 \r
2620 // @function getClass(el: HTMLElement): String\r
2621 // Returns the element's class.\r
2622 function getClass(el) {\r
2623         return el.className.baseVal === undefined ? el.className : el.className.baseVal;\r
2624 }\r
2625 \r
2626 // @function setOpacity(el: HTMLElement, opacity: Number)\r
2627 // Set the opacity of an element (including old IE support).\r
2628 // `opacity` must be a number from `0` to `1`.\r
2629 function setOpacity(el, value) {\r
2630         if ('opacity' in el.style) {\r
2631                 el.style.opacity = value;\r
2632         } else if ('filter' in el.style) {\r
2633                 _setOpacityIE(el, value);\r
2634         }\r
2635 }\r
2636 \r
2637 function _setOpacityIE(el, value) {\r
2638         var filter = false,\r
2639             filterName = 'DXImageTransform.Microsoft.Alpha';\r
2640 \r
2641         // filters collection throws an error if we try to retrieve a filter that doesn't exist\r
2642         try {\r
2643                 filter = el.filters.item(filterName);\r
2644         } catch (e) {\r
2645                 // don't set opacity to 1 if we haven't already set an opacity,\r
2646                 // it isn't needed and breaks transparent pngs.\r
2647                 if (value === 1) { return; }\r
2648         }\r
2649 \r
2650         value = Math.round(value * 100);\r
2651 \r
2652         if (filter) {\r
2653                 filter.Enabled = (value !== 100);\r
2654                 filter.Opacity = value;\r
2655         } else {\r
2656                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';\r
2657         }\r
2658 }\r
2659 \r
2660 // @function testProp(props: String[]): String|false\r
2661 // Goes through the array of style names and returns the first name\r
2662 // that is a valid style name for an element. If no such name is found,\r
2663 // it returns false. Useful for vendor-prefixed styles like `transform`.\r
2664 function testProp(props) {\r
2665         var style = document.documentElement.style;\r
2666 \r
2667         for (var i = 0; i < props.length; i++) {\r
2668                 if (props[i] in style) {\r
2669                         return props[i];\r
2670                 }\r
2671         }\r
2672         return false;\r
2673 }\r
2674 \r
2675 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)\r
2676 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels\r
2677 // and optionally scaled by `scale`. Does not have an effect if the\r
2678 // browser doesn't support 3D CSS transforms.\r
2679 function setTransform(el, offset, scale) {\r
2680         var pos = offset || new Point(0, 0);\r
2681 \r
2682         el.style[TRANSFORM] =\r
2683                 (ie3d ?\r
2684                         'translate(' + pos.x + 'px,' + pos.y + 'px)' :\r
2685                         'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +\r
2686                 (scale ? ' scale(' + scale + ')' : '');\r
2687 }\r
2688 \r
2689 // @function setPosition(el: HTMLElement, position: Point)\r
2690 // Sets the position of `el` to coordinates specified by `position`,\r
2691 // using CSS translate or top/left positioning depending on the browser\r
2692 // (used by Leaflet internally to position its layers).\r
2693 function setPosition(el, point) {\r
2694 \r
2695         /*eslint-disable */\r
2696         el._leaflet_pos = point;\r
2697         /*eslint-enable */\r
2698 \r
2699         if (any3d) {\r
2700                 setTransform(el, point);\r
2701         } else {\r
2702                 el.style.left = point.x + 'px';\r
2703                 el.style.top = point.y + 'px';\r
2704         }\r
2705 }\r
2706 \r
2707 // @function getPosition(el: HTMLElement): Point\r
2708 // Returns the coordinates of an element previously positioned with setPosition.\r
2709 function getPosition(el) {\r
2710         // this method is only used for elements previously positioned using setPosition,\r
2711         // so it's safe to cache the position for performance\r
2712 \r
2713         return el._leaflet_pos || new Point(0, 0);\r
2714 }\r
2715 \r
2716 // @function disableTextSelection()\r
2717 // Prevents the user from generating `selectstart` DOM events, usually generated\r
2718 // when the user drags the mouse through a page with text. Used internally\r
2719 // by Leaflet to override the behaviour of any click-and-drag interaction on\r
2720 // the map. Affects drag interactions on the whole document.\r
2721 \r
2722 // @function enableTextSelection()\r
2723 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).\r
2724 var disableTextSelection;\r
2725 var enableTextSelection;\r
2726 var _userSelect;\r
2727 if ('onselectstart' in document) {\r
2728         disableTextSelection = function () {\r
2729                 on(window, 'selectstart', preventDefault);\r
2730         };\r
2731         enableTextSelection = function () {\r
2732                 off(window, 'selectstart', preventDefault);\r
2733         };\r
2734 } else {\r
2735         var userSelectProperty = testProp(\r
2736                 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);\r
2737 \r
2738         disableTextSelection = function () {\r
2739                 if (userSelectProperty) {\r
2740                         var style = document.documentElement.style;\r
2741                         _userSelect = style[userSelectProperty];\r
2742                         style[userSelectProperty] = 'none';\r
2743                 }\r
2744         };\r
2745         enableTextSelection = function () {\r
2746                 if (userSelectProperty) {\r
2747                         document.documentElement.style[userSelectProperty] = _userSelect;\r
2748                         _userSelect = undefined;\r
2749                 }\r
2750         };\r
2751 }\r
2752 \r
2753 // @function disableImageDrag()\r
2754 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but\r
2755 // for `dragstart` DOM events, usually generated when the user drags an image.\r
2756 function disableImageDrag() {\r
2757         on(window, 'dragstart', preventDefault);\r
2758 }\r
2759 \r
2760 // @function enableImageDrag()\r
2761 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).\r
2762 function enableImageDrag() {\r
2763         off(window, 'dragstart', preventDefault);\r
2764 }\r
2765 \r
2766 var _outlineElement;
2767 var _outlineStyle;\r
2768 // @function preventOutline(el: HTMLElement)\r
2769 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)\r
2770 // of the element `el` invisible. Used internally by Leaflet to prevent\r
2771 // focusable elements from displaying an outline when the user performs a\r
2772 // drag interaction on them.\r
2773 function preventOutline(element) {\r
2774         while (element.tabIndex === -1) {\r
2775                 element = element.parentNode;\r
2776         }\r
2777         if (!element.style) { return; }\r
2778         restoreOutline();\r
2779         _outlineElement = element;\r
2780         _outlineStyle = element.style.outline;\r
2781         element.style.outline = 'none';\r
2782         on(window, 'keydown', restoreOutline);\r
2783 }\r
2784 \r
2785 // @function restoreOutline()\r
2786 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().\r
2787 function restoreOutline() {\r
2788         if (!_outlineElement) { return; }\r
2789         _outlineElement.style.outline = _outlineStyle;\r
2790         _outlineElement = undefined;\r
2791         _outlineStyle = undefined;\r
2792         off(window, 'keydown', restoreOutline);\r
2793 }\r
2794
2795
2796 var DomUtil = (Object.freeze || Object)({
2797         TRANSFORM: TRANSFORM,
2798         TRANSITION: TRANSITION,
2799         TRANSITION_END: TRANSITION_END,
2800         get: get,
2801         getStyle: getStyle,
2802         create: create$1,
2803         remove: remove,
2804         empty: empty,
2805         toFront: toFront,
2806         toBack: toBack,
2807         hasClass: hasClass,
2808         addClass: addClass,
2809         removeClass: removeClass,
2810         setClass: setClass,
2811         getClass: getClass,
2812         setOpacity: setOpacity,
2813         testProp: testProp,
2814         setTransform: setTransform,
2815         setPosition: setPosition,
2816         getPosition: getPosition,
2817         disableTextSelection: disableTextSelection,
2818         enableTextSelection: enableTextSelection,
2819         disableImageDrag: disableImageDrag,
2820         enableImageDrag: enableImageDrag,
2821         preventOutline: preventOutline,
2822         restoreOutline: restoreOutline
2823 });
2824
2825 /*
2826  * @class PosAnimation
2827  * @aka L.PosAnimation
2828  * @inherits Evented
2829  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2830  *
2831  * @example
2832  * ```js
2833  * var fx = new L.PosAnimation();
2834  * fx.run(el, [300, 500], 0.5);
2835  * ```
2836  *
2837  * @constructor L.PosAnimation()
2838  * Creates a `PosAnimation` object.
2839  *
2840  */
2841
2842 var PosAnimation = Evented.extend({
2843
2844         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2845         // Run an animation of a given element to a new position, optionally setting
2846         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2847         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2848         // `0.5` by default).
2849         run: function (el, newPos, duration, easeLinearity) {
2850                 this.stop();
2851
2852                 this._el = el;
2853                 this._inProgress = true;
2854                 this._duration = duration || 0.25;
2855                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2856
2857                 this._startPos = getPosition(el);
2858                 this._offset = newPos.subtract(this._startPos);
2859                 this._startTime = +new Date();
2860
2861                 // @event start: Event
2862                 // Fired when the animation starts
2863                 this.fire('start');
2864
2865                 this._animate();
2866         },
2867
2868         // @method stop()
2869         // Stops the animation (if currently running).
2870         stop: function () {
2871                 if (!this._inProgress) { return; }
2872
2873                 this._step(true);
2874                 this._complete();
2875         },
2876
2877         _animate: function () {
2878                 // animation loop
2879                 this._animId = requestAnimFrame(this._animate, this);
2880                 this._step();
2881         },
2882
2883         _step: function (round) {
2884                 var elapsed = (+new Date()) - this._startTime,
2885                     duration = this._duration * 1000;
2886
2887                 if (elapsed < duration) {
2888                         this._runFrame(this._easeOut(elapsed / duration), round);
2889                 } else {
2890                         this._runFrame(1);
2891                         this._complete();
2892                 }
2893         },
2894
2895         _runFrame: function (progress, round) {
2896                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2897                 if (round) {
2898                         pos._round();
2899                 }
2900                 setPosition(this._el, pos);
2901
2902                 // @event step: Event
2903                 // Fired continuously during the animation.
2904                 this.fire('step');
2905         },
2906
2907         _complete: function () {
2908                 cancelAnimFrame(this._animId);
2909
2910                 this._inProgress = false;
2911                 // @event end: Event
2912                 // Fired when the animation ends.
2913                 this.fire('end');
2914         },
2915
2916         _easeOut: function (t) {
2917                 return 1 - Math.pow(1 - t, this._easeOutPower);
2918         }
2919 });
2920
2921 /*\r
2922  * @class Map\r
2923  * @aka L.Map\r
2924  * @inherits Evented\r
2925  *\r
2926  * The central class of the API — it is used to create a map on a page and manipulate it.\r
2927  *\r
2928  * @example\r
2929  *\r
2930  * ```js\r
2931  * // initialize the map on the "map" div with a given center and zoom\r
2932  * var map = L.map('map', {\r
2933  *      center: [51.505, -0.09],\r
2934  *      zoom: 13\r
2935  * });\r
2936  * ```\r
2937  *\r
2938  */\r
2939 \r
2940 var Map = Evented.extend({\r
2941 \r
2942         options: {\r
2943                 // @section Map State Options\r
2944                 // @option crs: CRS = L.CRS.EPSG3857\r
2945                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not\r
2946                 // sure what it means.\r
2947                 crs: EPSG3857,\r
2948 \r
2949                 // @option center: LatLng = undefined\r
2950                 // Initial geographic center of the map\r
2951                 center: undefined,\r
2952 \r
2953                 // @option zoom: Number = undefined\r
2954                 // Initial map zoom level\r
2955                 zoom: undefined,\r
2956 \r
2957                 // @option minZoom: Number = *\r
2958                 // Minimum zoom level of the map.\r
2959                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,\r
2960                 // the lowest of their `minZoom` options will be used instead.\r
2961                 minZoom: undefined,\r
2962 \r
2963                 // @option maxZoom: Number = *\r
2964                 // Maximum zoom level of the map.\r
2965                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,\r
2966                 // the highest of their `maxZoom` options will be used instead.\r
2967                 maxZoom: undefined,\r
2968 \r
2969                 // @option layers: Layer[] = []\r
2970                 // Array of layers that will be added to the map initially\r
2971                 layers: [],\r
2972 \r
2973                 // @option maxBounds: LatLngBounds = null\r
2974                 // When this option is set, the map restricts the view to the given\r
2975                 // geographical bounds, bouncing the user back if the user tries to pan\r
2976                 // outside the view. To set the restriction dynamically, use\r
2977                 // [`setMaxBounds`](#map-setmaxbounds) method.\r
2978                 maxBounds: undefined,\r
2979 \r
2980                 // @option renderer: Renderer = *\r
2981                 // The default method for drawing vector layers on the map. `L.SVG`\r
2982                 // or `L.Canvas` by default depending on browser support.\r
2983                 renderer: undefined,\r
2984 \r
2985 \r
2986                 // @section Animation Options\r
2987                 // @option zoomAnimation: Boolean = true\r
2988                 // Whether the map zoom animation is enabled. By default it's enabled\r
2989                 // in all browsers that support CSS3 Transitions except Android.\r
2990                 zoomAnimation: true,\r
2991 \r
2992                 // @option zoomAnimationThreshold: Number = 4\r
2993                 // Won't animate zoom if the zoom difference exceeds this value.\r
2994                 zoomAnimationThreshold: 4,\r
2995 \r
2996                 // @option fadeAnimation: Boolean = true\r
2997                 // Whether the tile fade animation is enabled. By default it's enabled\r
2998                 // in all browsers that support CSS3 Transitions except Android.\r
2999                 fadeAnimation: true,\r
3000 \r
3001                 // @option markerZoomAnimation: Boolean = true\r
3002                 // Whether markers animate their zoom with the zoom animation, if disabled\r
3003                 // they will disappear for the length of the animation. By default it's\r
3004                 // enabled in all browsers that support CSS3 Transitions except Android.\r
3005                 markerZoomAnimation: true,\r
3006 \r
3007                 // @option transform3DLimit: Number = 2^23\r
3008                 // Defines the maximum size of a CSS translation transform. The default\r
3009                 // value should not be changed unless a web browser positions layers in\r
3010                 // the wrong place after doing a large `panBy`.\r
3011                 transform3DLimit: 8388608, // Precision limit of a 32-bit float\r
3012 \r
3013                 // @section Interaction Options\r
3014                 // @option zoomSnap: Number = 1\r
3015                 // Forces the map's zoom level to always be a multiple of this, particularly\r
3016                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.\r
3017                 // By default, the zoom level snaps to the nearest integer; lower values\r
3018                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`\r
3019                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.\r
3020                 zoomSnap: 1,\r
3021 \r
3022                 // @option zoomDelta: Number = 1\r
3023                 // Controls how much the map's zoom level will change after a\r
3024                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`\r
3025                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).\r
3026                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.\r
3027                 zoomDelta: 1,\r
3028 \r
3029                 // @option trackResize: Boolean = true\r
3030                 // Whether the map automatically handles browser window resize to update itself.\r
3031                 trackResize: true\r
3032         },\r
3033 \r
3034         initialize: function (id, options) { // (HTMLElement or String, Object)\r
3035                 options = setOptions(this, options);\r
3036 \r
3037                 this._initContainer(id);\r
3038                 this._initLayout();\r
3039 \r
3040                 // hack for https://github.com/Leaflet/Leaflet/issues/1980\r
3041                 this._onResize = bind(this._onResize, this);\r
3042 \r
3043                 this._initEvents();\r
3044 \r
3045                 if (options.maxBounds) {\r
3046                         this.setMaxBounds(options.maxBounds);\r
3047                 }\r
3048 \r
3049                 if (options.zoom !== undefined) {\r
3050                         this._zoom = this._limitZoom(options.zoom);\r
3051                 }\r
3052 \r
3053                 if (options.center && options.zoom !== undefined) {\r
3054                         this.setView(toLatLng(options.center), options.zoom, {reset: true});\r
3055                 }\r
3056 \r
3057                 this._handlers = [];\r
3058                 this._layers = {};\r
3059                 this._zoomBoundLayers = {};\r
3060                 this._sizeChanged = true;\r
3061 \r
3062                 this.callInitHooks();\r
3063 \r
3064                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera\r
3065                 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&\r
3066                                 this.options.zoomAnimation;\r
3067 \r
3068                 // zoom transitions run with the same duration for all layers, so if one of transitionend events\r
3069                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally\r
3070                 if (this._zoomAnimated) {\r
3071                         this._createAnimProxy();\r
3072                         on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);\r
3073                 }\r
3074 \r
3075                 this._addLayers(this.options.layers);\r
3076         },\r
3077 \r
3078 \r
3079         // @section Methods for modifying map state\r
3080 \r
3081         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this\r
3082         // Sets the view of the map (geographical center and zoom) with the given\r
3083         // animation options.\r
3084         setView: function (center, zoom, options) {\r
3085 \r
3086                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);\r
3087                 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);\r
3088                 options = options || {};\r
3089 \r
3090                 this._stop();\r
3091 \r
3092                 if (this._loaded && !options.reset && options !== true) {\r
3093 \r
3094                         if (options.animate !== undefined) {\r
3095                                 options.zoom = extend({animate: options.animate}, options.zoom);\r
3096                                 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);\r
3097                         }\r
3098 \r
3099                         // try animating pan or zoom\r
3100                         var moved = (this._zoom !== zoom) ?\r
3101                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :\r
3102                                 this._tryAnimatedPan(center, options.pan);\r
3103 \r
3104                         if (moved) {\r
3105                                 // prevent resize handler call, the view will refresh after animation anyway\r
3106                                 clearTimeout(this._sizeTimer);\r
3107                                 return this;\r
3108                         }\r
3109                 }\r
3110 \r
3111                 // animation didn't start, just reset the map view\r
3112                 this._resetView(center, zoom);\r
3113 \r
3114                 return this;\r
3115         },\r
3116 \r
3117         // @method setZoom(zoom: Number, options?: Zoom/pan options): this\r
3118         // Sets the zoom of the map.\r
3119         setZoom: function (zoom, options) {\r
3120                 if (!this._loaded) {\r
3121                         this._zoom = zoom;\r
3122                         return this;\r
3123                 }\r
3124                 return this.setView(this.getCenter(), zoom, {zoom: options});\r
3125         },\r
3126 \r
3127         // @method zoomIn(delta?: Number, options?: Zoom options): this\r
3128         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).\r
3129         zoomIn: function (delta, options) {\r
3130                 delta = delta || (any3d ? this.options.zoomDelta : 1);\r
3131                 return this.setZoom(this._zoom + delta, options);\r
3132         },\r
3133 \r
3134         // @method zoomOut(delta?: Number, options?: Zoom options): this\r
3135         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).\r
3136         zoomOut: function (delta, options) {\r
3137                 delta = delta || (any3d ? this.options.zoomDelta : 1);\r
3138                 return this.setZoom(this._zoom - delta, options);\r
3139         },\r
3140 \r
3141         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this\r
3142         // Zooms the map while keeping a specified geographical point on the map\r
3143         // stationary (e.g. used internally for scroll zoom and double-click zoom).\r
3144         // @alternative\r
3145         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this\r
3146         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.\r
3147         setZoomAround: function (latlng, zoom, options) {\r
3148                 var scale = this.getZoomScale(zoom),\r
3149                     viewHalf = this.getSize().divideBy(2),\r
3150                     containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),\r
3151 \r
3152                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),\r
3153                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));\r
3154 \r
3155                 return this.setView(newCenter, zoom, {zoom: options});\r
3156         },\r
3157 \r
3158         _getBoundsCenterZoom: function (bounds, options) {\r
3159 \r
3160                 options = options || {};\r
3161                 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);\r
3162 \r
3163                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),\r
3164                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),\r
3165 \r
3166                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));\r
3167 \r
3168                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;\r
3169 \r
3170                 if (zoom === Infinity) {\r
3171                         return {\r
3172                                 center: bounds.getCenter(),\r
3173                                 zoom: zoom\r
3174                         };\r
3175                 }\r
3176 \r
3177                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),\r
3178 \r
3179                     swPoint = this.project(bounds.getSouthWest(), zoom),\r
3180                     nePoint = this.project(bounds.getNorthEast(), zoom),\r
3181                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);\r
3182 \r
3183                 return {\r
3184                         center: center,\r
3185                         zoom: zoom\r
3186                 };\r
3187         },\r
3188 \r
3189         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this\r
3190         // Sets a map view that contains the given geographical bounds with the\r
3191         // maximum zoom level possible.\r
3192         fitBounds: function (bounds, options) {\r
3193 \r
3194                 bounds = toLatLngBounds(bounds);\r
3195 \r
3196                 if (!bounds.isValid()) {\r
3197                         throw new Error('Bounds are not valid.');\r
3198                 }\r
3199 \r
3200                 var target = this._getBoundsCenterZoom(bounds, options);\r
3201                 return this.setView(target.center, target.zoom, options);\r
3202         },\r
3203 \r
3204         // @method fitWorld(options?: fitBounds options): this\r
3205         // Sets a map view that mostly contains the whole world with the maximum\r
3206         // zoom level possible.\r
3207         fitWorld: function (options) {\r
3208                 return this.fitBounds([[-90, -180], [90, 180]], options);\r
3209         },\r
3210 \r
3211         // @method panTo(latlng: LatLng, options?: Pan options): this\r
3212         // Pans the map to a given center.\r
3213         panTo: function (center, options) { // (LatLng)\r
3214                 return this.setView(center, this._zoom, {pan: options});\r
3215         },\r
3216 \r
3217         // @method panBy(offset: Point, options?: Pan options): this\r
3218         // Pans the map by a given number of pixels (animated).\r
3219         panBy: function (offset, options) {\r
3220                 offset = toPoint(offset).round();\r
3221                 options = options || {};\r
3222 \r
3223                 if (!offset.x && !offset.y) {\r
3224                         return this.fire('moveend');\r
3225                 }\r
3226                 // If we pan too far, Chrome gets issues with tiles\r
3227                 // and makes them disappear or appear in the wrong place (slightly offset) #2602\r
3228                 if (options.animate !== true && !this.getSize().contains(offset)) {\r
3229                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());\r
3230                         return this;\r
3231                 }\r
3232 \r
3233                 if (!this._panAnim) {\r
3234                         this._panAnim = new PosAnimation();\r
3235 \r
3236                         this._panAnim.on({\r
3237                                 'step': this._onPanTransitionStep,\r
3238                                 'end': this._onPanTransitionEnd\r
3239                         }, this);\r
3240                 }\r
3241 \r
3242                 // don't fire movestart if animating inertia\r
3243                 if (!options.noMoveStart) {\r
3244                         this.fire('movestart');\r
3245                 }\r
3246 \r
3247                 // animate pan unless animate: false specified\r
3248                 if (options.animate !== false) {\r
3249                         addClass(this._mapPane, 'leaflet-pan-anim');\r
3250 \r
3251                         var newPos = this._getMapPanePos().subtract(offset).round();\r
3252                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);\r
3253                 } else {\r
3254                         this._rawPanBy(offset);\r
3255                         this.fire('move').fire('moveend');\r
3256                 }\r
3257 \r
3258                 return this;\r
3259         },\r
3260 \r
3261         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this\r
3262         // Sets the view of the map (geographical center and zoom) performing a smooth\r
3263         // pan-zoom animation.\r
3264         flyTo: function (targetCenter, targetZoom, options) {\r
3265 \r
3266                 options = options || {};\r
3267                 if (options.animate === false || !any3d) {\r
3268                         return this.setView(targetCenter, targetZoom, options);\r
3269                 }\r
3270 \r
3271                 this._stop();\r
3272 \r
3273                 var from = this.project(this.getCenter()),\r
3274                     to = this.project(targetCenter),\r
3275                     size = this.getSize(),\r
3276                     startZoom = this._zoom;\r
3277 \r
3278                 targetCenter = toLatLng(targetCenter);\r
3279                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;\r
3280 \r
3281                 var w0 = Math.max(size.x, size.y),\r
3282                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),\r
3283                     u1 = (to.distanceTo(from)) || 1,\r
3284                     rho = 1.42,\r
3285                     rho2 = rho * rho;\r
3286 \r
3287                 function r(i) {\r
3288                         var s1 = i ? -1 : 1,\r
3289                             s2 = i ? w1 : w0,\r
3290                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,\r
3291                             b1 = 2 * s2 * rho2 * u1,\r
3292                             b = t1 / b1,\r
3293                             sq = Math.sqrt(b * b + 1) - b;\r
3294 \r
3295                             // workaround for floating point precision bug when sq = 0, log = -Infinite,\r
3296                             // thus triggering an infinite loop in flyTo\r
3297                             var log = sq < 0.000000001 ? -18 : Math.log(sq);\r
3298 \r
3299                         return log;\r
3300                 }\r
3301 \r
3302                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }\r
3303                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }\r
3304                 function tanh(n) { return sinh(n) / cosh(n); }\r
3305 \r
3306                 var r0 = r(0);\r
3307 \r
3308                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }\r
3309                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }\r
3310 \r
3311                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }\r
3312 \r
3313                 var start = Date.now(),\r
3314                     S = (r(1) - r0) / rho,\r
3315                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;\r
3316 \r
3317                 function frame() {\r
3318                         var t = (Date.now() - start) / duration,\r
3319                             s = easeOut(t) * S;\r
3320 \r
3321                         if (t <= 1) {\r
3322                                 this._flyToFrame = requestAnimFrame(frame, this);\r
3323 \r
3324                                 this._move(\r
3325                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),\r
3326                                         this.getScaleZoom(w0 / w(s), startZoom),\r
3327                                         {flyTo: true});\r
3328 \r
3329                         } else {\r
3330                                 this\r
3331                                         ._move(targetCenter, targetZoom)\r
3332                                         ._moveEnd(true);\r
3333                         }\r
3334                 }\r
3335 \r
3336                 this._moveStart(true);\r
3337 \r
3338                 frame.call(this);\r
3339                 return this;\r
3340         },\r
3341 \r
3342         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this\r
3343         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),\r
3344         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).\r
3345         flyToBounds: function (bounds, options) {\r
3346                 var target = this._getBoundsCenterZoom(bounds, options);\r
3347                 return this.flyTo(target.center, target.zoom, options);\r
3348         },\r
3349 \r
3350         // @method setMaxBounds(bounds: Bounds): this\r
3351         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).\r
3352         setMaxBounds: function (bounds) {\r
3353                 bounds = toLatLngBounds(bounds);\r
3354 \r
3355                 if (!bounds.isValid()) {\r
3356                         this.options.maxBounds = null;\r
3357                         return this.off('moveend', this._panInsideMaxBounds);\r
3358                 } else if (this.options.maxBounds) {\r
3359                         this.off('moveend', this._panInsideMaxBounds);\r
3360                 }\r
3361 \r
3362                 this.options.maxBounds = bounds;\r
3363 \r
3364                 if (this._loaded) {\r
3365                         this._panInsideMaxBounds();\r
3366                 }\r
3367 \r
3368                 return this.on('moveend', this._panInsideMaxBounds);\r
3369         },\r
3370 \r
3371         // @method setMinZoom(zoom: Number): this\r
3372         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).\r
3373         setMinZoom: function (zoom) {\r
3374                 this.options.minZoom = zoom;\r
3375 \r
3376                 if (this._loaded && this.getZoom() < this.options.minZoom) {\r
3377                         return this.setZoom(zoom);\r
3378                 }\r
3379 \r
3380                 return this;\r
3381         },\r
3382 \r
3383         // @method setMaxZoom(zoom: Number): this\r
3384         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).\r
3385         setMaxZoom: function (zoom) {\r
3386                 this.options.maxZoom = zoom;\r
3387 \r
3388                 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {\r
3389                         return this.setZoom(zoom);\r
3390                 }\r
3391 \r
3392                 return this;\r
3393         },\r
3394 \r
3395         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this\r
3396         // 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
3397         panInsideBounds: function (bounds, options) {\r
3398                 this._enforcingBounds = true;\r
3399                 var center = this.getCenter(),\r
3400                     newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));\r
3401 \r
3402                 if (!center.equals(newCenter)) {\r
3403                         this.panTo(newCenter, options);\r
3404                 }\r
3405 \r
3406                 this._enforcingBounds = false;\r
3407                 return this;\r
3408         },\r
3409 \r
3410         // @method invalidateSize(options: Zoom/Pan options): this\r
3411         // Checks if the map container size changed and updates the map if so —\r
3412         // call it after you've changed the map size dynamically, also animating\r
3413         // pan by default. If `options.pan` is `false`, panning will not occur.\r
3414         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so\r
3415         // that it doesn't happen often even if the method is called many\r
3416         // times in a row.\r
3417 \r
3418         // @alternative\r
3419         // @method invalidateSize(animate: Boolean): this\r
3420         // Checks if the map container size changed and updates the map if so —\r
3421         // call it after you've changed the map size dynamically, also animating\r
3422         // pan by default.\r
3423         invalidateSize: function (options) {\r
3424                 if (!this._loaded) { return this; }\r
3425 \r
3426                 options = extend({\r
3427                         animate: false,\r
3428                         pan: true\r
3429                 }, options === true ? {animate: true} : options);\r
3430 \r
3431                 var oldSize = this.getSize();\r
3432                 this._sizeChanged = true;\r
3433                 this._lastCenter = null;\r
3434 \r
3435                 var newSize = this.getSize(),\r
3436                     oldCenter = oldSize.divideBy(2).round(),\r
3437                     newCenter = newSize.divideBy(2).round(),\r
3438                     offset = oldCenter.subtract(newCenter);\r
3439 \r
3440                 if (!offset.x && !offset.y) { return this; }\r
3441 \r
3442                 if (options.animate && options.pan) {\r
3443                         this.panBy(offset);\r
3444 \r
3445                 } else {\r
3446                         if (options.pan) {\r
3447                                 this._rawPanBy(offset);\r
3448                         }\r
3449 \r
3450                         this.fire('move');\r
3451 \r
3452                         if (options.debounceMoveend) {\r
3453                                 clearTimeout(this._sizeTimer);\r
3454                                 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);\r
3455                         } else {\r
3456                                 this.fire('moveend');\r
3457                         }\r
3458                 }\r
3459 \r
3460                 // @section Map state change events\r
3461                 // @event resize: ResizeEvent\r
3462                 // Fired when the map is resized.\r
3463                 return this.fire('resize', {\r
3464                         oldSize: oldSize,\r
3465                         newSize: newSize\r
3466                 });\r
3467         },\r
3468 \r
3469         // @section Methods for modifying map state\r
3470         // @method stop(): this\r
3471         // Stops the currently running `panTo` or `flyTo` animation, if any.\r
3472         stop: function () {\r
3473                 this.setZoom(this._limitZoom(this._zoom));\r
3474                 if (!this.options.zoomSnap) {\r
3475                         this.fire('viewreset');\r
3476                 }\r
3477                 return this._stop();\r
3478         },\r
3479 \r
3480         // @section Geolocation methods\r
3481         // @method locate(options?: Locate options): this\r
3482         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)\r
3483         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,\r
3484         // and optionally sets the map view to the user's location with respect to\r
3485         // detection accuracy (or to the world view if geolocation failed).\r
3486         // Note that, if your page doesn't use HTTPS, this method will fail in\r
3487         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))\r
3488         // See `Locate options` for more details.\r
3489         locate: function (options) {\r
3490 \r
3491                 options = this._locateOptions = extend({\r
3492                         timeout: 10000,\r
3493                         watch: false\r
3494                         // setView: false\r
3495                         // maxZoom: <Number>\r
3496                         // maximumAge: 0\r
3497                         // enableHighAccuracy: false\r
3498                 }, options);\r
3499 \r
3500                 if (!('geolocation' in navigator)) {\r
3501                         this._handleGeolocationError({\r
3502                                 code: 0,\r
3503                                 message: 'Geolocation not supported.'\r
3504                         });\r
3505                         return this;\r
3506                 }\r
3507 \r
3508                 var onResponse = bind(this._handleGeolocationResponse, this),\r
3509                     onError = bind(this._handleGeolocationError, this);\r
3510 \r
3511                 if (options.watch) {\r
3512                         this._locationWatchId =\r
3513                                 navigator.geolocation.watchPosition(onResponse, onError, options);\r
3514                 } else {\r
3515                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);\r
3516                 }\r
3517                 return this;\r
3518         },\r
3519 \r
3520         // @method stopLocate(): this\r
3521         // Stops watching location previously initiated by `map.locate({watch: true})`\r
3522         // and aborts resetting the map view if map.locate was called with\r
3523         // `{setView: true}`.\r
3524         stopLocate: function () {\r
3525                 if (navigator.geolocation && navigator.geolocation.clearWatch) {\r
3526                         navigator.geolocation.clearWatch(this._locationWatchId);\r
3527                 }\r
3528                 if (this._locateOptions) {\r
3529                         this._locateOptions.setView = false;\r
3530                 }\r
3531                 return this;\r
3532         },\r
3533 \r
3534         _handleGeolocationError: function (error) {\r
3535                 var c = error.code,\r
3536                     message = error.message ||\r
3537                             (c === 1 ? 'permission denied' :\r
3538                             (c === 2 ? 'position unavailable' : 'timeout'));\r
3539 \r
3540                 if (this._locateOptions.setView && !this._loaded) {\r
3541                         this.fitWorld();\r
3542                 }\r
3543 \r
3544                 // @section Location events\r
3545                 // @event locationerror: ErrorEvent\r
3546                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.\r
3547                 this.fire('locationerror', {\r
3548                         code: c,\r
3549                         message: 'Geolocation error: ' + message + '.'\r
3550                 });\r
3551         },\r
3552 \r
3553         _handleGeolocationResponse: function (pos) {\r
3554                 var lat = pos.coords.latitude,\r
3555                     lng = pos.coords.longitude,\r
3556                     latlng = new LatLng(lat, lng),\r
3557                     bounds = latlng.toBounds(pos.coords.accuracy),\r
3558                     options = this._locateOptions;\r
3559 \r
3560                 if (options.setView) {\r
3561                         var zoom = this.getBoundsZoom(bounds);\r
3562                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);\r
3563                 }\r
3564 \r
3565                 var data = {\r
3566                         latlng: latlng,\r
3567                         bounds: bounds,\r
3568                         timestamp: pos.timestamp\r
3569                 };\r
3570 \r
3571                 for (var i in pos.coords) {\r
3572                         if (typeof pos.coords[i] === 'number') {\r
3573                                 data[i] = pos.coords[i];\r
3574                         }\r
3575                 }\r
3576 \r
3577                 // @event locationfound: LocationEvent\r
3578                 // Fired when geolocation (using the [`locate`](#map-locate) method)\r
3579                 // went successfully.\r
3580                 this.fire('locationfound', data);\r
3581         },\r
3582 \r
3583         // TODO handler.addTo\r
3584         // TODO Appropiate docs section?\r
3585         // @section Other Methods\r
3586         // @method addHandler(name: String, HandlerClass: Function): this\r
3587         // Adds a new `Handler` to the map, given its name and constructor function.\r
3588         addHandler: function (name, HandlerClass) {\r
3589                 if (!HandlerClass) { return this; }\r
3590 \r
3591                 var handler = this[name] = new HandlerClass(this);\r
3592 \r
3593                 this._handlers.push(handler);\r
3594 \r
3595                 if (this.options[name]) {\r
3596                         handler.enable();\r
3597                 }\r
3598 \r
3599                 return this;\r
3600         },\r
3601 \r
3602         // @method remove(): this\r
3603         // Destroys the map and clears all related event listeners.\r
3604         remove: function () {\r
3605 \r
3606                 this._initEvents(true);\r
3607 \r
3608                 if (this._containerId !== this._container._leaflet_id) {\r
3609                         throw new Error('Map container is being reused by another instance');\r
3610                 }\r
3611 \r
3612                 try {\r
3613                         // throws error in IE6-8\r
3614                         delete this._container._leaflet_id;\r
3615                         delete this._containerId;\r
3616                 } catch (e) {\r
3617                         /*eslint-disable */\r
3618                         this._container._leaflet_id = undefined;\r
3619                         /*eslint-enable */\r
3620                         this._containerId = undefined;\r
3621                 }\r
3622 \r
3623                 remove(this._mapPane);\r
3624 \r
3625                 if (this._clearControlPos) {\r
3626                         this._clearControlPos();\r
3627                 }\r
3628 \r
3629                 this._clearHandlers();\r
3630 \r
3631                 if (this._loaded) {\r
3632                         // @section Map state change events\r
3633                         // @event unload: Event\r
3634                         // Fired when the map is destroyed with [remove](#map-remove) method.\r
3635                         this.fire('unload');\r
3636                 }\r
3637 \r
3638                 var i;\r
3639                 for (i in this._layers) {\r
3640                         this._layers[i].remove();\r
3641                 }\r
3642                 for (i in this._panes) {\r
3643                         remove(this._panes[i]);\r
3644                 }\r
3645 \r
3646                 this._layers = [];\r
3647                 this._panes = [];\r
3648                 delete this._mapPane;\r
3649                 delete this._renderer;\r
3650 \r
3651                 return this;\r
3652         },\r
3653 \r
3654         // @section Other Methods\r
3655         // @method createPane(name: String, container?: HTMLElement): HTMLElement\r
3656         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,\r
3657         // then returns it. The pane is created as a child of `container`, or\r
3658         // as a child of the main map pane if not set.\r
3659         createPane: function (name, container) {\r
3660                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),\r
3661                     pane = create$1('div', className, container || this._mapPane);\r
3662 \r
3663                 if (name) {\r
3664                         this._panes[name] = pane;\r
3665                 }\r
3666                 return pane;\r
3667         },\r
3668 \r
3669         // @section Methods for Getting Map State\r
3670 \r
3671         // @method getCenter(): LatLng\r
3672         // Returns the geographical center of the map view\r
3673         getCenter: function () {\r
3674                 this._checkIfLoaded();\r
3675 \r
3676                 if (this._lastCenter && !this._moved()) {\r
3677                         return this._lastCenter;\r
3678                 }\r
3679                 return this.layerPointToLatLng(this._getCenterLayerPoint());\r
3680         },\r
3681 \r
3682         // @method getZoom(): Number\r
3683         // Returns the current zoom level of the map view\r
3684         getZoom: function () {\r
3685                 return this._zoom;\r
3686         },\r
3687 \r
3688         // @method getBounds(): LatLngBounds\r
3689         // Returns the geographical bounds visible in the current map view\r
3690         getBounds: function () {\r
3691                 var bounds = this.getPixelBounds(),\r
3692                     sw = this.unproject(bounds.getBottomLeft()),\r
3693                     ne = this.unproject(bounds.getTopRight());\r
3694 \r
3695                 return new LatLngBounds(sw, ne);\r
3696         },\r
3697 \r
3698         // @method getMinZoom(): Number\r
3699         // 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
3700         getMinZoom: function () {\r
3701                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;\r
3702         },\r
3703 \r
3704         // @method getMaxZoom(): Number\r
3705         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).\r
3706         getMaxZoom: function () {\r
3707                 return this.options.maxZoom === undefined ?\r
3708                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :\r
3709                         this.options.maxZoom;\r
3710         },\r
3711 \r
3712         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number\r
3713         // Returns the maximum zoom level on which the given bounds fit to the map\r
3714         // view in its entirety. If `inside` (optional) is set to `true`, the method\r
3715         // instead returns the minimum zoom level on which the map view fits into\r
3716         // the given bounds in its entirety.\r
3717         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number\r
3718                 bounds = toLatLngBounds(bounds);\r
3719                 padding = toPoint(padding || [0, 0]);\r
3720 \r
3721                 var zoom = this.getZoom() || 0,\r
3722                     min = this.getMinZoom(),\r
3723                     max = this.getMaxZoom(),\r
3724                     nw = bounds.getNorthWest(),\r
3725                     se = bounds.getSouthEast(),\r
3726                     size = this.getSize().subtract(padding),\r
3727                     boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),\r
3728                     snap = any3d ? this.options.zoomSnap : 1,\r
3729                     scalex = size.x / boundsSize.x,\r
3730                     scaley = size.y / boundsSize.y,\r
3731                     scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);\r
3732 \r
3733                 zoom = this.getScaleZoom(scale, zoom);\r
3734 \r
3735                 if (snap) {\r
3736                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level\r
3737                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;\r
3738                 }\r
3739 \r
3740                 return Math.max(min, Math.min(max, zoom));\r
3741         },\r
3742 \r
3743         // @method getSize(): Point\r
3744         // Returns the current size of the map container (in pixels).\r
3745         getSize: function () {\r
3746                 if (!this._size || this._sizeChanged) {\r
3747                         this._size = new Point(\r
3748                                 this._container.clientWidth || 0,\r
3749                                 this._container.clientHeight || 0);\r
3750 \r
3751                         this._sizeChanged = false;\r
3752                 }\r
3753                 return this._size.clone();\r
3754         },\r
3755 \r
3756         // @method getPixelBounds(): Bounds\r
3757         // Returns the bounds of the current map view in projected pixel\r
3758         // coordinates (sometimes useful in layer and overlay implementations).\r
3759         getPixelBounds: function (center, zoom) {\r
3760                 var topLeftPoint = this._getTopLeftPoint(center, zoom);\r
3761                 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));\r
3762         },\r
3763 \r
3764         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to\r
3765         // the map pane? "left point of the map layer" can be confusing, specially\r
3766         // since there can be negative offsets.\r
3767         // @method getPixelOrigin(): Point\r
3768         // Returns the projected pixel coordinates of the top left point of\r
3769         // the map layer (useful in custom layer and overlay implementations).\r
3770         getPixelOrigin: function () {\r
3771                 this._checkIfLoaded();\r
3772                 return this._pixelOrigin;\r
3773         },\r
3774 \r
3775         // @method getPixelWorldBounds(zoom?: Number): Bounds\r
3776         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.\r
3777         // If `zoom` is omitted, the map's current zoom level is used.\r
3778         getPixelWorldBounds: function (zoom) {\r
3779                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);\r
3780         },\r
3781 \r
3782         // @section Other Methods\r
3783 \r
3784         // @method getPane(pane: String|HTMLElement): HTMLElement\r
3785         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).\r
3786         getPane: function (pane) {\r
3787                 return typeof pane === 'string' ? this._panes[pane] : pane;\r
3788         },\r
3789 \r
3790         // @method getPanes(): Object\r
3791         // Returns a plain object containing the names of all [panes](#map-pane) as keys and\r
3792         // the panes as values.\r
3793         getPanes: function () {\r
3794                 return this._panes;\r
3795         },\r
3796 \r
3797         // @method getContainer: HTMLElement\r
3798         // Returns the HTML element that contains the map.\r
3799         getContainer: function () {\r
3800                 return this._container;\r
3801         },\r
3802 \r
3803 \r
3804         // @section Conversion Methods\r
3805 \r
3806         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number\r
3807         // Returns the scale factor to be applied to a map transition from zoom level\r
3808         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.\r
3809         getZoomScale: function (toZoom, fromZoom) {\r
3810                 // TODO replace with universal implementation after refactoring projections\r
3811                 var crs = this.options.crs;\r
3812                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;\r
3813                 return crs.scale(toZoom) / crs.scale(fromZoom);\r
3814         },\r
3815 \r
3816         // @method getScaleZoom(scale: Number, fromZoom: Number): Number\r
3817         // Returns the zoom level that the map would end up at, if it is at `fromZoom`\r
3818         // level and everything is scaled by a factor of `scale`. Inverse of\r
3819         // [`getZoomScale`](#map-getZoomScale).\r
3820         getScaleZoom: function (scale, fromZoom) {\r
3821                 var crs = this.options.crs;\r
3822                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;\r
3823                 var zoom = crs.zoom(scale * crs.scale(fromZoom));\r
3824                 return isNaN(zoom) ? Infinity : zoom;\r
3825         },\r
3826 \r
3827         // @method project(latlng: LatLng, zoom: Number): Point\r
3828         // Projects a geographical coordinate `LatLng` according to the projection\r
3829         // of the map's CRS, then scales it according to `zoom` and the CRS's\r
3830         // `Transformation`. The result is pixel coordinate relative to\r
3831         // the CRS origin.\r
3832         project: function (latlng, zoom) {\r
3833                 zoom = zoom === undefined ? this._zoom : zoom;\r
3834                 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);\r
3835         },\r
3836 \r
3837         // @method unproject(point: Point, zoom: Number): LatLng\r
3838         // Inverse of [`project`](#map-project).\r
3839         unproject: function (point, zoom) {\r
3840                 zoom = zoom === undefined ? this._zoom : zoom;\r
3841                 return this.options.crs.pointToLatLng(toPoint(point), zoom);\r
3842         },\r
3843 \r
3844         // @method layerPointToLatLng(point: Point): LatLng\r
3845         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),\r
3846         // returns the corresponding geographical coordinate (for the current zoom level).\r
3847         layerPointToLatLng: function (point) {\r
3848                 var projectedPoint = toPoint(point).add(this.getPixelOrigin());\r
3849                 return this.unproject(projectedPoint);\r
3850         },\r
3851 \r
3852         // @method latLngToLayerPoint(latlng: LatLng): Point\r
3853         // Given a geographical coordinate, returns the corresponding pixel coordinate\r
3854         // relative to the [origin pixel](#map-getpixelorigin).\r
3855         latLngToLayerPoint: function (latlng) {\r
3856                 var projectedPoint = this.project(toLatLng(latlng))._round();\r
3857                 return projectedPoint._subtract(this.getPixelOrigin());\r
3858         },\r
3859 \r
3860         // @method wrapLatLng(latlng: LatLng): LatLng\r
3861         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the\r
3862         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the\r
3863         // CRS's bounds.\r
3864         // By default this means longitude is wrapped around the dateline so its\r
3865         // value is between -180 and +180 degrees.\r
3866         wrapLatLng: function (latlng) {\r
3867                 return this.options.crs.wrapLatLng(toLatLng(latlng));\r
3868         },\r
3869 \r
3870         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds\r
3871         // Returns a `LatLngBounds` with the same size as the given one, ensuring that\r
3872         // its center is within the CRS's bounds.\r
3873         // By default this means the center longitude is wrapped around the dateline so its\r
3874         // value is between -180 and +180 degrees, and the majority of the bounds\r
3875         // overlaps the CRS's bounds.\r
3876         wrapLatLngBounds: function (latlng) {\r
3877                 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));\r
3878         },\r
3879 \r
3880         // @method distance(latlng1: LatLng, latlng2: LatLng): Number\r
3881         // Returns the distance between two geographical coordinates according to\r
3882         // the map's CRS. By default this measures distance in meters.\r
3883         distance: function (latlng1, latlng2) {\r
3884                 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));\r
3885         },\r
3886 \r
3887         // @method containerPointToLayerPoint(point: Point): Point\r
3888         // Given a pixel coordinate relative to the map container, returns the corresponding\r
3889         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).\r
3890         containerPointToLayerPoint: function (point) { // (Point)\r
3891                 return toPoint(point).subtract(this._getMapPanePos());\r
3892         },\r
3893 \r
3894         // @method layerPointToContainerPoint(point: Point): Point\r
3895         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),\r
3896         // returns the corresponding pixel coordinate relative to the map container.\r
3897         layerPointToContainerPoint: function (point) { // (Point)\r
3898                 return toPoint(point).add(this._getMapPanePos());\r
3899         },\r
3900 \r
3901         // @method containerPointToLatLng(point: Point): LatLng\r
3902         // Given a pixel coordinate relative to the map container, returns\r
3903         // the corresponding geographical coordinate (for the current zoom level).\r
3904         containerPointToLatLng: function (point) {\r
3905                 var layerPoint = this.containerPointToLayerPoint(toPoint(point));\r
3906                 return this.layerPointToLatLng(layerPoint);\r
3907         },\r
3908 \r
3909         // @method latLngToContainerPoint(latlng: LatLng): Point\r
3910         // Given a geographical coordinate, returns the corresponding pixel coordinate\r
3911         // relative to the map container.\r
3912         latLngToContainerPoint: function (latlng) {\r
3913                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));\r
3914         },\r
3915 \r
3916         // @method mouseEventToContainerPoint(ev: MouseEvent): Point\r
3917         // Given a MouseEvent object, returns the pixel coordinate relative to the\r
3918         // map container where the event took place.\r
3919         mouseEventToContainerPoint: function (e) {\r
3920                 return getMousePosition(e, this._container);\r
3921         },\r
3922 \r
3923         // @method mouseEventToLayerPoint(ev: MouseEvent): Point\r
3924         // Given a MouseEvent object, returns the pixel coordinate relative to\r
3925         // the [origin pixel](#map-getpixelorigin) where the event took place.\r
3926         mouseEventToLayerPoint: function (e) {\r
3927                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));\r
3928         },\r
3929 \r
3930         // @method mouseEventToLatLng(ev: MouseEvent): LatLng\r
3931         // Given a MouseEvent object, returns geographical coordinate where the\r
3932         // event took place.\r
3933         mouseEventToLatLng: function (e) { // (MouseEvent)\r
3934                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));\r
3935         },\r
3936 \r
3937 \r
3938         // map initialization methods\r
3939 \r
3940         _initContainer: function (id) {\r
3941                 var container = this._container = get(id);\r
3942 \r
3943                 if (!container) {\r
3944                         throw new Error('Map container not found.');\r
3945                 } else if (container._leaflet_id) {\r
3946                         throw new Error('Map container is already initialized.');\r
3947                 }\r
3948 \r
3949                 on(container, 'scroll', this._onScroll, this);\r
3950                 this._containerId = stamp(container);\r
3951         },\r
3952 \r
3953         _initLayout: function () {\r
3954                 var container = this._container;\r
3955 \r
3956                 this._fadeAnimated = this.options.fadeAnimation && any3d;\r
3957 \r
3958                 addClass(container, 'leaflet-container' +\r
3959                         (touch ? ' leaflet-touch' : '') +\r
3960                         (retina ? ' leaflet-retina' : '') +\r
3961                         (ielt9 ? ' leaflet-oldie' : '') +\r
3962                         (safari ? ' leaflet-safari' : '') +\r
3963                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));\r
3964 \r
3965                 var position = getStyle(container, 'position');\r
3966 \r
3967                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {\r
3968                         container.style.position = 'relative';\r
3969                 }\r
3970 \r
3971                 this._initPanes();\r
3972 \r
3973                 if (this._initControlPos) {\r
3974                         this._initControlPos();\r
3975                 }\r
3976         },\r
3977 \r
3978         _initPanes: function () {\r
3979                 var panes = this._panes = {};\r
3980                 this._paneRenderers = {};\r
3981 \r
3982                 // @section\r
3983                 //\r
3984                 // Panes are DOM elements used to control the ordering of layers on the map. You\r
3985                 // can access panes with [`map.getPane`](#map-getpane) or\r
3986                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the\r
3987                 // [`map.createPane`](#map-createpane) method.\r
3988                 //\r
3989                 // Every map has the following default panes that differ only in zIndex.\r
3990                 //\r
3991                 // @pane mapPane: HTMLElement = 'auto'\r
3992                 // Pane that contains all other map panes\r
3993 \r
3994                 this._mapPane = this.createPane('mapPane', this._container);\r
3995                 setPosition(this._mapPane, new Point(0, 0));\r
3996 \r
3997                 // @pane tilePane: HTMLElement = 200\r
3998                 // Pane for `GridLayer`s and `TileLayer`s\r
3999                 this.createPane('tilePane');\r
4000                 // @pane overlayPane: HTMLElement = 400\r
4001                 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s\r
4002                 this.createPane('shadowPane');\r
4003                 // @pane shadowPane: HTMLElement = 500\r
4004                 // Pane for overlay shadows (e.g. `Marker` shadows)\r
4005                 this.createPane('overlayPane');\r
4006                 // @pane markerPane: HTMLElement = 600\r
4007                 // Pane for `Icon`s of `Marker`s\r
4008                 this.createPane('markerPane');\r
4009                 // @pane tooltipPane: HTMLElement = 650\r
4010                 // Pane for tooltip.\r
4011                 this.createPane('tooltipPane');\r
4012                 // @pane popupPane: HTMLElement = 700\r
4013                 // Pane for `Popup`s.\r
4014                 this.createPane('popupPane');\r
4015 \r
4016                 if (!this.options.markerZoomAnimation) {\r
4017                         addClass(panes.markerPane, 'leaflet-zoom-hide');\r
4018                         addClass(panes.shadowPane, 'leaflet-zoom-hide');\r
4019                 }\r
4020         },\r
4021 \r
4022 \r
4023         // private methods that modify map state\r
4024 \r
4025         // @section Map state change events\r
4026         _resetView: function (center, zoom) {\r
4027                 setPosition(this._mapPane, new Point(0, 0));\r
4028 \r
4029                 var loading = !this._loaded;\r
4030                 this._loaded = true;\r
4031                 zoom = this._limitZoom(zoom);\r
4032 \r
4033                 this.fire('viewprereset');\r
4034 \r
4035                 var zoomChanged = this._zoom !== zoom;\r
4036                 this\r
4037                         ._moveStart(zoomChanged)\r
4038                         ._move(center, zoom)\r
4039                         ._moveEnd(zoomChanged);\r
4040 \r
4041                 // @event viewreset: Event\r
4042                 // Fired when the map needs to redraw its content (this usually happens\r
4043                 // on map zoom or load). Very useful for creating custom overlays.\r
4044                 this.fire('viewreset');\r
4045 \r
4046                 // @event load: Event\r
4047                 // Fired when the map is initialized (when its center and zoom are set\r
4048                 // for the first time).\r
4049                 if (loading) {\r
4050                         this.fire('load');\r
4051                 }\r
4052         },\r
4053 \r
4054         _moveStart: function (zoomChanged) {\r
4055                 // @event zoomstart: Event\r
4056                 // Fired when the map zoom is about to change (e.g. before zoom animation).\r
4057                 // @event movestart: Event\r
4058                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).\r
4059                 if (zoomChanged) {\r
4060                         this.fire('zoomstart');\r
4061                 }\r
4062                 return this.fire('movestart');\r
4063         },\r
4064 \r
4065         _move: function (center, zoom, data) {\r
4066                 if (zoom === undefined) {\r
4067                         zoom = this._zoom;\r
4068                 }\r
4069                 var zoomChanged = this._zoom !== zoom;\r
4070 \r
4071                 this._zoom = zoom;\r
4072                 this._lastCenter = center;\r
4073                 this._pixelOrigin = this._getNewPixelOrigin(center);\r
4074 \r
4075                 // @event zoom: Event\r
4076                 // Fired repeatedly during any change in zoom level, including zoom\r
4077                 // and fly animations.\r
4078                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530\r
4079                         this.fire('zoom', data);\r
4080                 }\r
4081 \r
4082                 // @event move: Event\r
4083                 // Fired repeatedly during any movement of the map, including pan and\r
4084                 // fly animations.\r
4085                 return this.fire('move', data);\r
4086         },\r
4087 \r
4088         _moveEnd: function (zoomChanged) {\r
4089                 // @event zoomend: Event\r
4090                 // Fired when the map has changed, after any animations.\r
4091                 if (zoomChanged) {\r
4092                         this.fire('zoomend');\r
4093                 }\r
4094 \r
4095                 // @event moveend: Event\r
4096                 // Fired when the center of the map stops changing (e.g. user stopped\r
4097                 // dragging the map).\r
4098                 return this.fire('moveend');\r
4099         },\r
4100 \r
4101         _stop: function () {\r
4102                 cancelAnimFrame(this._flyToFrame);\r
4103                 if (this._panAnim) {\r
4104                         this._panAnim.stop();\r
4105                 }\r
4106                 return this;\r
4107         },\r
4108 \r
4109         _rawPanBy: function (offset) {\r
4110                 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));\r
4111         },\r
4112 \r
4113         _getZoomSpan: function () {\r
4114                 return this.getMaxZoom() - this.getMinZoom();\r
4115         },\r
4116 \r
4117         _panInsideMaxBounds: function () {\r
4118                 if (!this._enforcingBounds) {\r
4119                         this.panInsideBounds(this.options.maxBounds);\r
4120                 }\r
4121         },\r
4122 \r
4123         _checkIfLoaded: function () {\r
4124                 if (!this._loaded) {\r
4125                         throw new Error('Set map center and zoom first.');\r
4126                 }\r
4127         },\r
4128 \r
4129         // DOM event handling\r
4130 \r
4131         // @section Interaction events\r
4132         _initEvents: function (remove$$1) {\r
4133                 this._targets = {};\r
4134                 this._targets[stamp(this._container)] = this;\r
4135 \r
4136                 var onOff = remove$$1 ? off : on;\r
4137 \r
4138                 // @event click: MouseEvent\r
4139                 // Fired when the user clicks (or taps) the map.\r
4140                 // @event dblclick: MouseEvent\r
4141                 // Fired when the user double-clicks (or double-taps) the map.\r
4142                 // @event mousedown: MouseEvent\r
4143                 // Fired when the user pushes the mouse button on the map.\r
4144                 // @event mouseup: MouseEvent\r
4145                 // Fired when the user releases the mouse button on the map.\r
4146                 // @event mouseover: MouseEvent\r
4147                 // Fired when the mouse enters the map.\r
4148                 // @event mouseout: MouseEvent\r
4149                 // Fired when the mouse leaves the map.\r
4150                 // @event mousemove: MouseEvent\r
4151                 // Fired while the mouse moves over the map.\r
4152                 // @event contextmenu: MouseEvent\r
4153                 // Fired when the user pushes the right mouse button on the map, prevents\r
4154                 // default browser context menu from showing if there are listeners on\r
4155                 // this event. Also fired on mobile when the user holds a single touch\r
4156                 // for a second (also called long press).\r
4157                 // @event keypress: KeyboardEvent\r
4158                 // Fired when the user presses a key from the keyboard while the map is focused.\r
4159                 onOff(this._container, 'click dblclick mousedown mouseup ' +\r
4160                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);\r
4161 \r
4162                 if (this.options.trackResize) {\r
4163                         onOff(window, 'resize', this._onResize, this);\r
4164                 }\r
4165 \r
4166                 if (any3d && this.options.transform3DLimit) {\r
4167                         (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);\r
4168                 }\r
4169         },\r
4170 \r
4171         _onResize: function () {\r
4172                 cancelAnimFrame(this._resizeRequest);\r
4173                 this._resizeRequest = requestAnimFrame(\r
4174                         function () { this.invalidateSize({debounceMoveend: true}); }, this);\r
4175         },\r
4176 \r
4177         _onScroll: function () {\r
4178                 this._container.scrollTop  = 0;\r
4179                 this._container.scrollLeft = 0;\r
4180         },\r
4181 \r
4182         _onMoveEnd: function () {\r
4183                 var pos = this._getMapPanePos();\r
4184                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {\r
4185                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have\r
4186                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/\r
4187                         this._resetView(this.getCenter(), this.getZoom());\r
4188                 }\r
4189         },\r
4190 \r
4191         _findEventTargets: function (e, type) {\r
4192                 var targets = [],\r
4193                     target,\r
4194                     isHover = type === 'mouseout' || type === 'mouseover',\r
4195                     src = e.target || e.srcElement,\r
4196                     dragging = false;\r
4197 \r
4198                 while (src) {\r
4199                         target = this._targets[stamp(src)];\r
4200                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {\r
4201                                 // Prevent firing click after you just dragged an object.\r
4202                                 dragging = true;\r
4203                                 break;\r
4204                         }\r
4205                         if (target && target.listens(type, true)) {\r
4206                                 if (isHover && !isExternalTarget(src, e)) { break; }\r
4207                                 targets.push(target);\r
4208                                 if (isHover) { break; }\r
4209                         }\r
4210                         if (src === this._container) { break; }\r
4211                         src = src.parentNode;\r
4212                 }\r
4213                 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {\r
4214                         targets = [this];\r
4215                 }\r
4216                 return targets;\r
4217         },\r
4218 \r
4219         _handleDOMEvent: function (e) {\r
4220                 if (!this._loaded || skipped(e)) { return; }\r
4221 \r
4222                 var type = e.type;\r
4223 \r
4224                 if (type === 'mousedown' || type === 'keypress') {\r
4225                         // prevents outline when clicking on keyboard-focusable element\r
4226                         preventOutline(e.target || e.srcElement);\r
4227                 }\r
4228 \r
4229                 this._fireDOMEvent(e, type);\r
4230         },\r
4231 \r
4232         _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],\r
4233 \r
4234         _fireDOMEvent: function (e, type, targets) {\r
4235 \r
4236                 if (e.type === 'click') {\r
4237                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).\r
4238                         // @event preclick: MouseEvent\r
4239                         // Fired before mouse click on the map (sometimes useful when you\r
4240                         // want something to happen on click before any existing click\r
4241                         // handlers start running).\r
4242                         var synth = extend({}, e);\r
4243                         synth.type = 'preclick';\r
4244                         this._fireDOMEvent(synth, synth.type, targets);\r
4245                 }\r
4246 \r
4247                 if (e._stopped) { return; }\r
4248 \r
4249                 // Find the layer the event is propagating from and its parents.\r
4250                 targets = (targets || []).concat(this._findEventTargets(e, type));\r
4251 \r
4252                 if (!targets.length) { return; }\r
4253 \r
4254                 var target = targets[0];\r
4255                 if (type === 'contextmenu' && target.listens(type, true)) {\r
4256                         preventDefault(e);\r
4257                 }\r
4258 \r
4259                 var data = {\r
4260                         originalEvent: e\r
4261                 };\r
4262 \r
4263                 if (e.type !== 'keypress') {\r
4264                         var isMarker = (target.options && 'icon' in target.options);\r
4265                         data.containerPoint = isMarker ?\r
4266                                         this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);\r
4267                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);\r
4268                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);\r
4269                 }\r
4270 \r
4271                 for (var i = 0; i < targets.length; i++) {\r
4272                         targets[i].fire(type, data, true);\r
4273                         if (data.originalEvent._stopped ||\r
4274                                 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }\r
4275                 }\r
4276         },\r
4277 \r
4278         _draggableMoved: function (obj) {\r
4279                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;\r
4280                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());\r
4281         },\r
4282 \r
4283         _clearHandlers: function () {\r
4284                 for (var i = 0, len = this._handlers.length; i < len; i++) {\r
4285                         this._handlers[i].disable();\r
4286                 }\r
4287         },\r
4288 \r
4289         // @section Other Methods\r
4290 \r
4291         // @method whenReady(fn: Function, context?: Object): this\r
4292         // Runs the given function `fn` when the map gets initialized with\r
4293         // a view (center and zoom) and at least one layer, or immediately\r
4294         // if it's already initialized, optionally passing a function context.\r
4295         whenReady: function (callback, context) {\r
4296                 if (this._loaded) {\r
4297                         callback.call(context || this, {target: this});\r
4298                 } else {\r
4299                         this.on('load', callback, context);\r
4300                 }\r
4301                 return this;\r
4302         },\r
4303 \r
4304 \r
4305         // private methods for getting map state\r
4306 \r
4307         _getMapPanePos: function () {\r
4308                 return getPosition(this._mapPane) || new Point(0, 0);\r
4309         },\r
4310 \r
4311         _moved: function () {\r
4312                 var pos = this._getMapPanePos();\r
4313                 return pos && !pos.equals([0, 0]);\r
4314         },\r
4315 \r
4316         _getTopLeftPoint: function (center, zoom) {\r
4317                 var pixelOrigin = center && zoom !== undefined ?\r
4318                         this._getNewPixelOrigin(center, zoom) :\r
4319                         this.getPixelOrigin();\r
4320                 return pixelOrigin.subtract(this._getMapPanePos());\r
4321         },\r
4322 \r
4323         _getNewPixelOrigin: function (center, zoom) {\r
4324                 var viewHalf = this.getSize()._divideBy(2);\r
4325                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();\r
4326         },\r
4327 \r
4328         _latLngToNewLayerPoint: function (latlng, zoom, center) {\r
4329                 var topLeft = this._getNewPixelOrigin(center, zoom);\r
4330                 return this.project(latlng, zoom)._subtract(topLeft);\r
4331         },\r
4332 \r
4333         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {\r
4334                 var topLeft = this._getNewPixelOrigin(center, zoom);\r
4335                 return toBounds([\r
4336                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),\r
4337                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),\r
4338                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),\r
4339                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)\r
4340                 ]);\r
4341         },\r
4342 \r
4343         // layer point of the current center\r
4344         _getCenterLayerPoint: function () {\r
4345                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));\r
4346         },\r
4347 \r
4348         // offset of the specified place to the current center in pixels\r
4349         _getCenterOffset: function (latlng) {\r
4350                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());\r
4351         },\r
4352 \r
4353         // adjust center for view to get inside bounds\r
4354         _limitCenter: function (center, zoom, bounds) {\r
4355 \r
4356                 if (!bounds) { return center; }\r
4357 \r
4358                 var centerPoint = this.project(center, zoom),\r
4359                     viewHalf = this.getSize().divideBy(2),\r
4360                     viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),\r
4361                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);\r
4362 \r
4363                 // If offset is less than a pixel, ignore.\r
4364                 // This prevents unstable projections from getting into\r
4365                 // an infinite loop of tiny offsets.\r
4366                 if (offset.round().equals([0, 0])) {\r
4367                         return center;\r
4368                 }\r
4369 \r
4370                 return this.unproject(centerPoint.add(offset), zoom);\r
4371         },\r
4372 \r
4373         // adjust offset for view to get inside bounds\r
4374         _limitOffset: function (offset, bounds) {\r
4375                 if (!bounds) { return offset; }\r
4376 \r
4377                 var viewBounds = this.getPixelBounds(),\r
4378                     newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));\r
4379 \r
4380                 return offset.add(this._getBoundsOffset(newBounds, bounds));\r
4381         },\r
4382 \r
4383         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom\r
4384         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {\r
4385                 var projectedMaxBounds = toBounds(\r
4386                         this.project(maxBounds.getNorthEast(), zoom),\r
4387                         this.project(maxBounds.getSouthWest(), zoom)\r
4388                     ),\r
4389                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),\r
4390                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),\r
4391 \r
4392                     dx = this._rebound(minOffset.x, -maxOffset.x),\r
4393                     dy = this._rebound(minOffset.y, -maxOffset.y);\r
4394 \r
4395                 return new Point(dx, dy);\r
4396         },\r
4397 \r
4398         _rebound: function (left, right) {\r
4399                 return left + right > 0 ?\r
4400                         Math.round(left - right) / 2 :\r
4401                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));\r
4402         },\r
4403 \r
4404         _limitZoom: function (zoom) {\r
4405                 var min = this.getMinZoom(),\r
4406                     max = this.getMaxZoom(),\r
4407                     snap = any3d ? this.options.zoomSnap : 1;\r
4408                 if (snap) {\r
4409                         zoom = Math.round(zoom / snap) * snap;\r
4410                 }\r
4411                 return Math.max(min, Math.min(max, zoom));\r
4412         },\r
4413 \r
4414         _onPanTransitionStep: function () {\r
4415                 this.fire('move');\r
4416         },\r
4417 \r
4418         _onPanTransitionEnd: function () {\r
4419                 removeClass(this._mapPane, 'leaflet-pan-anim');\r
4420                 this.fire('moveend');\r
4421         },\r
4422 \r
4423         _tryAnimatedPan: function (center, options) {\r
4424                 // difference between the new and current centers in pixels\r
4425                 var offset = this._getCenterOffset(center)._floor();\r
4426 \r
4427                 // don't animate too far unless animate: true specified in options\r
4428                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }\r
4429 \r
4430                 this.panBy(offset, options);\r
4431 \r
4432                 return true;\r
4433         },\r
4434 \r
4435         _createAnimProxy: function () {\r
4436 \r
4437                 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');\r
4438                 this._panes.mapPane.appendChild(proxy);\r
4439 \r
4440                 this.on('zoomanim', function (e) {\r
4441                         var prop = TRANSFORM,\r
4442                             transform = this._proxy.style[prop];\r
4443 \r
4444                         setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));\r
4445 \r
4446                         // workaround for case when transform is the same and so transitionend event is not fired\r
4447                         if (transform === this._proxy.style[prop] && this._animatingZoom) {\r
4448                                 this._onZoomTransitionEnd();\r
4449                         }\r
4450                 }, this);\r
4451 \r
4452                 this.on('load moveend', function () {\r
4453                         var c = this.getCenter(),\r
4454                             z = this.getZoom();\r
4455                         setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));\r
4456                 }, this);\r
4457 \r
4458                 this._on('unload', this._destroyAnimProxy, this);\r
4459         },\r
4460 \r
4461         _destroyAnimProxy: function () {\r
4462                 remove(this._proxy);\r
4463                 delete this._proxy;\r
4464         },\r
4465 \r
4466         _catchTransitionEnd: function (e) {\r
4467                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {\r
4468                         this._onZoomTransitionEnd();\r
4469                 }\r
4470         },\r
4471 \r
4472         _nothingToAnimate: function () {\r
4473                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;\r
4474         },\r
4475 \r
4476         _tryAnimatedZoom: function (center, zoom, options) {\r
4477 \r
4478                 if (this._animatingZoom) { return true; }\r
4479 \r
4480                 options = options || {};\r
4481 \r
4482                 // don't animate if disabled, not supported or zoom difference is too large\r
4483                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||\r
4484                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }\r
4485 \r
4486                 // offset is the pixel coords of the zoom origin relative to the current center\r
4487                 var scale = this.getZoomScale(zoom),\r
4488                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);\r
4489 \r
4490                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced\r
4491                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }\r
4492 \r
4493                 requestAnimFrame(function () {\r
4494                         this\r
4495                             ._moveStart(true)\r
4496                             ._animateZoom(center, zoom, true);\r
4497                 }, this);\r
4498 \r
4499                 return true;\r
4500         },\r
4501 \r
4502         _animateZoom: function (center, zoom, startAnim, noUpdate) {\r
4503                 if (startAnim) {\r
4504                         this._animatingZoom = true;\r
4505 \r
4506                         // remember what center/zoom to set after animation\r
4507                         this._animateToCenter = center;\r
4508                         this._animateToZoom = zoom;\r
4509 \r
4510                         addClass(this._mapPane, 'leaflet-zoom-anim');\r
4511                 }\r
4512 \r
4513                 // @event zoomanim: ZoomAnimEvent\r
4514                 // Fired on every frame of a zoom animation\r
4515                 this.fire('zoomanim', {\r
4516                         center: center,\r
4517                         zoom: zoom,\r
4518                         noUpdate: noUpdate\r
4519                 });\r
4520 \r
4521                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693\r
4522                 setTimeout(bind(this._onZoomTransitionEnd, this), 250);\r
4523         },\r
4524 \r
4525         _onZoomTransitionEnd: function () {\r
4526                 if (!this._animatingZoom) { return; }\r
4527 \r
4528                 removeClass(this._mapPane, 'leaflet-zoom-anim');\r
4529 \r
4530                 this._animatingZoom = false;\r
4531 \r
4532                 this._move(this._animateToCenter, this._animateToZoom);\r
4533 \r
4534                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.\r
4535                 requestAnimFrame(function () {\r
4536                         this._moveEnd(true);\r
4537                 }, this);\r
4538         }\r
4539 });\r
4540 \r
4541 // @section\r
4542 \r
4543 // @factory L.map(id: String, options?: Map options)\r
4544 // Instantiates a map object given the DOM ID of a `<div>` element\r
4545 // and optionally an object literal with `Map options`.\r
4546 //\r
4547 // @alternative\r
4548 // @factory L.map(el: HTMLElement, options?: Map options)\r
4549 // Instantiates a map object given an instance of a `<div>` HTML element\r
4550 // and optionally an object literal with `Map options`.\r
4551 function createMap(id, options) {\r
4552         return new Map(id, options);\r
4553 }
4554
4555 /*\r
4556  * @class Control\r
4557  * @aka L.Control\r
4558  * @inherits Class\r
4559  *\r
4560  * L.Control is a base class for implementing map controls. Handles positioning.\r
4561  * All other controls extend from this class.\r
4562  */\r
4563 \r
4564 var Control = Class.extend({\r
4565         // @section\r
4566         // @aka Control options\r
4567         options: {\r
4568                 // @option position: String = 'topright'\r
4569                 // The position of the control (one of the map corners). Possible values are `'topleft'`,\r
4570                 // `'topright'`, `'bottomleft'` or `'bottomright'`\r
4571                 position: 'topright'\r
4572         },\r
4573 \r
4574         initialize: function (options) {\r
4575                 setOptions(this, options);\r
4576         },\r
4577 \r
4578         /* @section\r
4579          * Classes extending L.Control will inherit the following methods:\r
4580          *\r
4581          * @method getPosition: string\r
4582          * Returns the position of the control.\r
4583          */\r
4584         getPosition: function () {\r
4585                 return this.options.position;\r
4586         },\r
4587 \r
4588         // @method setPosition(position: string): this\r
4589         // Sets the position of the control.\r
4590         setPosition: function (position) {\r
4591                 var map = this._map;\r
4592 \r
4593                 if (map) {\r
4594                         map.removeControl(this);\r
4595                 }\r
4596 \r
4597                 this.options.position = position;\r
4598 \r
4599                 if (map) {\r
4600                         map.addControl(this);\r
4601                 }\r
4602 \r
4603                 return this;\r
4604         },\r
4605 \r
4606         // @method getContainer: HTMLElement\r
4607         // Returns the HTMLElement that contains the control.\r
4608         getContainer: function () {\r
4609                 return this._container;\r
4610         },\r
4611 \r
4612         // @method addTo(map: Map): this\r
4613         // Adds the control to the given map.\r
4614         addTo: function (map) {\r
4615                 this.remove();\r
4616                 this._map = map;\r
4617 \r
4618                 var container = this._container = this.onAdd(map),\r
4619                     pos = this.getPosition(),\r
4620                     corner = map._controlCorners[pos];\r
4621 \r
4622                 addClass(container, 'leaflet-control');\r
4623 \r
4624                 if (pos.indexOf('bottom') !== -1) {\r
4625                         corner.insertBefore(container, corner.firstChild);\r
4626                 } else {\r
4627                         corner.appendChild(container);\r
4628                 }\r
4629 \r
4630                 return this;\r
4631         },\r
4632 \r
4633         // @method remove: this\r
4634         // Removes the control from the map it is currently active on.\r
4635         remove: function () {\r
4636                 if (!this._map) {\r
4637                         return this;\r
4638                 }\r
4639 \r
4640                 remove(this._container);\r
4641 \r
4642                 if (this.onRemove) {\r
4643                         this.onRemove(this._map);\r
4644                 }\r
4645 \r
4646                 this._map = null;\r
4647 \r
4648                 return this;\r
4649         },\r
4650 \r
4651         _refocusOnMap: function (e) {\r
4652                 // if map exists and event is not a keyboard event\r
4653                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {\r
4654                         this._map.getContainer().focus();\r
4655                 }\r
4656         }\r
4657 });\r
4658 \r
4659 var control = function (options) {\r
4660         return new Control(options);\r
4661 };\r
4662 \r
4663 /* @section Extension methods\r
4664  * @uninheritable\r
4665  *\r
4666  * Every control should extend from `L.Control` and (re-)implement the following methods.\r
4667  *\r
4668  * @method onAdd(map: Map): HTMLElement\r
4669  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).\r
4670  *\r
4671  * @method onRemove(map: Map)\r
4672  * 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
4673  */\r
4674 \r
4675 /* @namespace Map\r
4676  * @section Methods for Layers and Controls\r
4677  */\r
4678 Map.include({\r
4679         // @method addControl(control: Control): this\r
4680         // Adds the given control to the map\r
4681         addControl: function (control) {\r
4682                 control.addTo(this);\r
4683                 return this;\r
4684         },\r
4685 \r
4686         // @method removeControl(control: Control): this\r
4687         // Removes the given control from the map\r
4688         removeControl: function (control) {\r
4689                 control.remove();\r
4690                 return this;\r
4691         },\r
4692 \r
4693         _initControlPos: function () {\r
4694                 var corners = this._controlCorners = {},\r
4695                     l = 'leaflet-',\r
4696                     container = this._controlContainer =\r
4697                             create$1('div', l + 'control-container', this._container);\r
4698 \r
4699                 function createCorner(vSide, hSide) {\r
4700                         var className = l + vSide + ' ' + l + hSide;\r
4701 \r
4702                         corners[vSide + hSide] = create$1('div', className, container);\r
4703                 }\r
4704 \r
4705                 createCorner('top', 'left');\r
4706                 createCorner('top', 'right');\r
4707                 createCorner('bottom', 'left');\r
4708                 createCorner('bottom', 'right');\r
4709         },\r
4710 \r
4711         _clearControlPos: function () {\r
4712                 for (var i in this._controlCorners) {\r
4713                         remove(this._controlCorners[i]);\r
4714                 }\r
4715                 remove(this._controlContainer);\r
4716                 delete this._controlCorners;\r
4717                 delete this._controlContainer;\r
4718         }\r
4719 });
4720
4721 /*\r
4722  * @class Control.Layers\r
4723  * @aka L.Control.Layers\r
4724  * @inherits Control\r
4725  *\r
4726  * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`.\r
4727  *\r
4728  * @example\r
4729  *\r
4730  * ```js\r
4731  * var baseLayers = {\r
4732  *      "Mapbox": mapbox,\r
4733  *      "OpenStreetMap": osm\r
4734  * };\r
4735  *\r
4736  * var overlays = {\r
4737  *      "Marker": marker,\r
4738  *      "Roads": roadsLayer\r
4739  * };\r
4740  *\r
4741  * L.control.layers(baseLayers, overlays).addTo(map);\r
4742  * ```\r
4743  *\r
4744  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:\r
4745  *\r
4746  * ```js\r
4747  * {\r
4748  *     "<someName1>": layer1,\r
4749  *     "<someName2>": layer2\r
4750  * }\r
4751  * ```\r
4752  *\r
4753  * The layer names can contain HTML, which allows you to add additional styling to the items:\r
4754  *\r
4755  * ```js\r
4756  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}\r
4757  * ```\r
4758  */\r
4759 \r
4760 var Layers = Control.extend({\r
4761         // @section\r
4762         // @aka Control.Layers options\r
4763         options: {\r
4764                 // @option collapsed: Boolean = true\r
4765                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.\r
4766                 collapsed: true,\r
4767                 position: 'topright',\r
4768 \r
4769                 // @option autoZIndex: Boolean = true\r
4770                 // 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
4771                 autoZIndex: true,\r
4772 \r
4773                 // @option hideSingleBase: Boolean = false\r
4774                 // If `true`, the base layers in the control will be hidden when there is only one.\r
4775                 hideSingleBase: false,\r
4776 \r
4777                 // @option sortLayers: Boolean = false\r
4778                 // Whether to sort the layers. When `false`, layers will keep the order\r
4779                 // in which they were added to the control.\r
4780                 sortLayers: false,\r
4781 \r
4782                 // @option sortFunction: Function = *\r
4783                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)\r
4784                 // that will be used for sorting the layers, when `sortLayers` is `true`.\r
4785                 // The function receives both the `L.Layer` instances and their names, as in\r
4786                 // `sortFunction(layerA, layerB, nameA, nameB)`.\r
4787                 // By default, it sorts layers alphabetically by their name.\r
4788                 sortFunction: function (layerA, layerB, nameA, nameB) {\r
4789                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);\r
4790                 }\r
4791         },\r
4792 \r
4793         initialize: function (baseLayers, overlays, options) {\r
4794                 setOptions(this, options);\r
4795 \r
4796                 this._layerControlInputs = [];\r
4797                 this._layers = [];\r
4798                 this._lastZIndex = 0;\r
4799                 this._handlingClick = false;\r
4800 \r
4801                 for (var i in baseLayers) {\r
4802                         this._addLayer(baseLayers[i], i);\r
4803                 }\r
4804 \r
4805                 for (i in overlays) {\r
4806                         this._addLayer(overlays[i], i, true);\r
4807                 }\r
4808         },\r
4809 \r
4810         onAdd: function (map) {\r
4811                 this._initLayout();\r
4812                 this._update();\r
4813 \r
4814                 this._map = map;\r
4815                 map.on('zoomend', this._checkDisabledLayers, this);\r
4816 \r
4817                 for (var i = 0; i < this._layers.length; i++) {\r
4818                         this._layers[i].layer.on('add remove', this._onLayerChange, this);\r
4819                 }\r
4820 \r
4821                 return this._container;\r
4822         },\r
4823 \r
4824         addTo: function (map) {\r
4825                 Control.prototype.addTo.call(this, map);\r
4826                 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.\r
4827                 return this._expandIfNotCollapsed();\r
4828         },\r
4829 \r
4830         onRemove: function () {\r
4831                 this._map.off('zoomend', this._checkDisabledLayers, this);\r
4832 \r
4833                 for (var i = 0; i < this._layers.length; i++) {\r
4834                         this._layers[i].layer.off('add remove', this._onLayerChange, this);\r
4835                 }\r
4836         },\r
4837 \r
4838         // @method addBaseLayer(layer: Layer, name: String): this\r
4839         // Adds a base layer (radio button entry) with the given name to the control.\r
4840         addBaseLayer: function (layer, name) {\r
4841                 this._addLayer(layer, name);\r
4842                 return (this._map) ? this._update() : this;\r
4843         },\r
4844 \r
4845         // @method addOverlay(layer: Layer, name: String): this\r
4846         // Adds an overlay (checkbox entry) with the given name to the control.\r
4847         addOverlay: function (layer, name) {\r
4848                 this._addLayer(layer, name, true);\r
4849                 return (this._map) ? this._update() : this;\r
4850         },\r
4851 \r
4852         // @method removeLayer(layer: Layer): this\r
4853         // Remove the given layer from the control.\r
4854         removeLayer: function (layer) {\r
4855                 layer.off('add remove', this._onLayerChange, this);\r
4856 \r
4857                 var obj = this._getLayer(stamp(layer));\r
4858                 if (obj) {\r
4859                         this._layers.splice(this._layers.indexOf(obj), 1);\r
4860                 }\r
4861                 return (this._map) ? this._update() : this;\r
4862         },\r
4863 \r
4864         // @method expand(): this\r
4865         // Expand the control container if collapsed.\r
4866         expand: function () {\r
4867                 addClass(this._container, 'leaflet-control-layers-expanded');\r
4868                 this._form.style.height = null;\r
4869                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);\r
4870                 if (acceptableHeight < this._form.clientHeight) {\r
4871                         addClass(this._form, 'leaflet-control-layers-scrollbar');\r
4872                         this._form.style.height = acceptableHeight + 'px';\r
4873                 } else {\r
4874                         removeClass(this._form, 'leaflet-control-layers-scrollbar');\r
4875                 }\r
4876                 this._checkDisabledLayers();\r
4877                 return this;\r
4878         },\r
4879 \r
4880         // @method collapse(): this\r
4881         // Collapse the control container if expanded.\r
4882         collapse: function () {\r
4883                 removeClass(this._container, 'leaflet-control-layers-expanded');\r
4884                 return this;\r
4885         },\r
4886 \r
4887         _initLayout: function () {\r
4888                 var className = 'leaflet-control-layers',\r
4889                     container = this._container = create$1('div', className),\r
4890                     collapsed = this.options.collapsed;\r
4891 \r
4892                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released\r
4893                 container.setAttribute('aria-haspopup', true);\r
4894 \r
4895                 disableClickPropagation(container);\r
4896                 disableScrollPropagation(container);\r
4897 \r
4898                 var form = this._form = create$1('form', className + '-list');\r
4899 \r
4900                 if (collapsed) {\r
4901                         this._map.on('click', this.collapse, this);\r
4902 \r
4903                         if (!android) {\r
4904                                 on(container, {\r
4905                                         mouseenter: this.expand,\r
4906                                         mouseleave: this.collapse\r
4907                                 }, this);\r
4908                         }\r
4909                 }\r
4910 \r
4911                 var link = this._layersLink = create$1('a', className + '-toggle', container);\r
4912                 link.href = '#';\r
4913                 link.title = 'Layers';\r
4914 \r
4915                 if (touch) {\r
4916                         on(link, 'click', stop);\r
4917                         on(link, 'click', this.expand, this);\r
4918                 } else {\r
4919                         on(link, 'focus', this.expand, this);\r
4920                 }\r
4921 \r
4922                 if (!collapsed) {\r
4923                         this.expand();\r
4924                 }\r
4925 \r
4926                 this._baseLayersList = create$1('div', className + '-base', form);\r
4927                 this._separator = create$1('div', className + '-separator', form);\r
4928                 this._overlaysList = create$1('div', className + '-overlays', form);\r
4929 \r
4930                 container.appendChild(form);\r
4931         },\r
4932 \r
4933         _getLayer: function (id) {\r
4934                 for (var i = 0; i < this._layers.length; i++) {\r
4935 \r
4936                         if (this._layers[i] && stamp(this._layers[i].layer) === id) {\r
4937                                 return this._layers[i];\r
4938                         }\r
4939                 }\r
4940         },\r
4941 \r
4942         _addLayer: function (layer, name, overlay) {\r
4943                 if (this._map) {\r
4944                         layer.on('add remove', this._onLayerChange, this);\r
4945                 }\r
4946 \r
4947                 this._layers.push({\r
4948                         layer: layer,\r
4949                         name: name,\r
4950                         overlay: overlay\r
4951                 });\r
4952 \r
4953                 if (this.options.sortLayers) {\r
4954                         this._layers.sort(bind(function (a, b) {\r
4955                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);\r
4956                         }, this));\r
4957                 }\r
4958 \r
4959                 if (this.options.autoZIndex && layer.setZIndex) {\r
4960                         this._lastZIndex++;\r
4961                         layer.setZIndex(this._lastZIndex);\r
4962                 }\r
4963 \r
4964                 this._expandIfNotCollapsed();\r
4965         },\r
4966 \r
4967         _update: function () {\r
4968                 if (!this._container) { return this; }\r
4969 \r
4970                 empty(this._baseLayersList);\r
4971                 empty(this._overlaysList);\r
4972 \r
4973                 this._layerControlInputs = [];\r
4974                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;\r
4975 \r
4976                 for (i = 0; i < this._layers.length; i++) {\r
4977                         obj = this._layers[i];\r
4978                         this._addItem(obj);\r
4979                         overlaysPresent = overlaysPresent || obj.overlay;\r
4980                         baseLayersPresent = baseLayersPresent || !obj.overlay;\r
4981                         baseLayersCount += !obj.overlay ? 1 : 0;\r
4982                 }\r
4983 \r
4984                 // Hide base layers section if there's only one layer.\r
4985                 if (this.options.hideSingleBase) {\r
4986                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;\r
4987                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';\r
4988                 }\r
4989 \r
4990                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';\r
4991 \r
4992                 return this;\r
4993         },\r
4994 \r
4995         _onLayerChange: function (e) {\r
4996                 if (!this._handlingClick) {\r
4997                         this._update();\r
4998                 }\r
4999 \r
5000                 var obj = this._getLayer(stamp(e.target));\r
5001 \r
5002                 // @namespace Map\r
5003                 // @section Layer events\r
5004                 // @event baselayerchange: LayersControlEvent\r
5005                 // Fired when the base layer is changed through the [layer control](#control-layers).\r
5006                 // @event overlayadd: LayersControlEvent\r
5007                 // Fired when an overlay is selected through the [layer control](#control-layers).\r
5008                 // @event overlayremove: LayersControlEvent\r
5009                 // Fired when an overlay is deselected through the [layer control](#control-layers).\r
5010                 // @namespace Control.Layers\r
5011                 var type = obj.overlay ?\r
5012                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :\r
5013                         (e.type === 'add' ? 'baselayerchange' : null);\r
5014 \r
5015                 if (type) {\r
5016                         this._map.fire(type, obj);\r
5017                 }\r
5018         },\r
5019 \r
5020         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)\r
5021         _createRadioElement: function (name, checked) {\r
5022 \r
5023                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +\r
5024                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';\r
5025 \r
5026                 var radioFragment = document.createElement('div');\r
5027                 radioFragment.innerHTML = radioHtml;\r
5028 \r
5029                 return radioFragment.firstChild;\r
5030         },\r
5031 \r
5032         _addItem: function (obj) {\r
5033                 var label = document.createElement('label'),\r
5034                     checked = this._map.hasLayer(obj.layer),\r
5035                     input;\r
5036 \r
5037                 if (obj.overlay) {\r
5038                         input = document.createElement('input');\r
5039                         input.type = 'checkbox';\r
5040                         input.className = 'leaflet-control-layers-selector';\r
5041                         input.defaultChecked = checked;\r
5042                 } else {\r
5043                         input = this._createRadioElement('leaflet-base-layers', checked);\r
5044                 }\r
5045 \r
5046                 this._layerControlInputs.push(input);\r
5047                 input.layerId = stamp(obj.layer);\r
5048 \r
5049                 on(input, 'click', this._onInputClick, this);\r
5050 \r
5051                 var name = document.createElement('span');\r
5052                 name.innerHTML = ' ' + obj.name;\r
5053 \r
5054                 // Helps from preventing layer control flicker when checkboxes are disabled\r
5055                 // https://github.com/Leaflet/Leaflet/issues/2771\r
5056                 var holder = document.createElement('div');\r
5057 \r
5058                 label.appendChild(holder);\r
5059                 holder.appendChild(input);\r
5060                 holder.appendChild(name);\r
5061 \r
5062                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;\r
5063                 container.appendChild(label);\r
5064 \r
5065                 this._checkDisabledLayers();\r
5066                 return label;\r
5067         },\r
5068 \r
5069         _onInputClick: function () {\r
5070                 var inputs = this._layerControlInputs,\r
5071                     input, layer;\r
5072                 var addedLayers = [],\r
5073                     removedLayers = [];\r
5074 \r
5075                 this._handlingClick = true;\r
5076 \r
5077                 for (var i = inputs.length - 1; i >= 0; i--) {\r
5078                         input = inputs[i];\r
5079                         layer = this._getLayer(input.layerId).layer;\r
5080 \r
5081                         if (input.checked) {\r
5082                                 addedLayers.push(layer);\r
5083                         } else if (!input.checked) {\r
5084                                 removedLayers.push(layer);\r
5085                         }\r
5086                 }\r
5087 \r
5088                 // Bugfix issue 2318: Should remove all old layers before readding new ones\r
5089                 for (i = 0; i < removedLayers.length; i++) {\r
5090                         if (this._map.hasLayer(removedLayers[i])) {\r
5091                                 this._map.removeLayer(removedLayers[i]);\r
5092                         }\r
5093                 }\r
5094                 for (i = 0; i < addedLayers.length; i++) {\r
5095                         if (!this._map.hasLayer(addedLayers[i])) {\r
5096                                 this._map.addLayer(addedLayers[i]);\r
5097                         }\r
5098                 }\r
5099 \r
5100                 this._handlingClick = false;\r
5101 \r
5102                 this._refocusOnMap();\r
5103         },\r
5104 \r
5105         _checkDisabledLayers: function () {\r
5106                 var inputs = this._layerControlInputs,\r
5107                     input,\r
5108                     layer,\r
5109                     zoom = this._map.getZoom();\r
5110 \r
5111                 for (var i = inputs.length - 1; i >= 0; i--) {\r
5112                         input = inputs[i];\r
5113                         layer = this._getLayer(input.layerId).layer;\r
5114                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||\r
5115                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);\r
5116 \r
5117                 }\r
5118         },\r
5119 \r
5120         _expandIfNotCollapsed: function () {\r
5121                 if (this._map && !this.options.collapsed) {\r
5122                         this.expand();\r
5123                 }\r
5124                 return this;\r
5125         },\r
5126 \r
5127         _expand: function () {\r
5128                 // Backward compatibility, remove me in 1.1.\r
5129                 return this.expand();\r
5130         },\r
5131 \r
5132         _collapse: function () {\r
5133                 // Backward compatibility, remove me in 1.1.\r
5134                 return this.collapse();\r
5135         }\r
5136 \r
5137 });\r
5138 \r
5139 \r
5140 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)\r
5141 // Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.\r
5142 var layers = function (baseLayers, overlays, options) {\r
5143         return new Layers(baseLayers, overlays, options);\r
5144 };
5145
5146 /*\r
5147  * @class Control.Zoom\r
5148  * @aka L.Control.Zoom\r
5149  * @inherits Control\r
5150  *\r
5151  * 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
5152  */\r
5153 \r
5154 var Zoom = Control.extend({\r
5155         // @section\r
5156         // @aka Control.Zoom options\r
5157         options: {\r
5158                 position: 'topleft',\r
5159 \r
5160                 // @option zoomInText: String = '+'\r
5161                 // The text set on the 'zoom in' button.\r
5162                 zoomInText: '+',\r
5163 \r
5164                 // @option zoomInTitle: String = 'Zoom in'\r
5165                 // The title set on the 'zoom in' button.\r
5166                 zoomInTitle: 'Zoom in',\r
5167 \r
5168                 // @option zoomOutText: String = '&#x2212;'\r
5169                 // The text set on the 'zoom out' button.\r
5170                 zoomOutText: '&#x2212;',\r
5171 \r
5172                 // @option zoomOutTitle: String = 'Zoom out'\r
5173                 // The title set on the 'zoom out' button.\r
5174                 zoomOutTitle: 'Zoom out'\r
5175         },\r
5176 \r
5177         onAdd: function (map) {\r
5178                 var zoomName = 'leaflet-control-zoom',\r
5179                     container = create$1('div', zoomName + ' leaflet-bar'),\r
5180                     options = this.options;\r
5181 \r
5182                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,\r
5183                         zoomName + '-in',  container, this._zoomIn);\r
5184                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,\r
5185                         zoomName + '-out', container, this._zoomOut);\r
5186 \r
5187                 this._updateDisabled();\r
5188                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);\r
5189 \r
5190                 return container;\r
5191         },\r
5192 \r
5193         onRemove: function (map) {\r
5194                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);\r
5195         },\r
5196 \r
5197         disable: function () {\r
5198                 this._disabled = true;\r
5199                 this._updateDisabled();\r
5200                 return this;\r
5201         },\r
5202 \r
5203         enable: function () {\r
5204                 this._disabled = false;\r
5205                 this._updateDisabled();\r
5206                 return this;\r
5207         },\r
5208 \r
5209         _zoomIn: function (e) {\r
5210                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {\r
5211                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));\r
5212                 }\r
5213         },\r
5214 \r
5215         _zoomOut: function (e) {\r
5216                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {\r
5217                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));\r
5218                 }\r
5219         },\r
5220 \r
5221         _createButton: function (html, title, className, container, fn) {\r
5222                 var link = create$1('a', className, container);\r
5223                 link.innerHTML = html;\r
5224                 link.href = '#';\r
5225                 link.title = title;\r
5226 \r
5227                 /*\r
5228                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"\r
5229                  */\r
5230                 link.setAttribute('role', 'button');\r
5231                 link.setAttribute('aria-label', title);\r
5232 \r
5233                 disableClickPropagation(link);\r
5234                 on(link, 'click', stop);\r
5235                 on(link, 'click', fn, this);\r
5236                 on(link, 'click', this._refocusOnMap, this);\r
5237 \r
5238                 return link;\r
5239         },\r
5240 \r
5241         _updateDisabled: function () {\r
5242                 var map = this._map,\r
5243                     className = 'leaflet-disabled';\r
5244 \r
5245                 removeClass(this._zoomInButton, className);\r
5246                 removeClass(this._zoomOutButton, className);\r
5247 \r
5248                 if (this._disabled || map._zoom === map.getMinZoom()) {\r
5249                         addClass(this._zoomOutButton, className);\r
5250                 }\r
5251                 if (this._disabled || map._zoom === map.getMaxZoom()) {\r
5252                         addClass(this._zoomInButton, className);\r
5253                 }\r
5254         }\r
5255 });\r
5256 \r
5257 // @namespace Map\r
5258 // @section Control options\r
5259 // @option zoomControl: Boolean = true\r
5260 // Whether a [zoom control](#control-zoom) is added to the map by default.\r
5261 Map.mergeOptions({\r
5262         zoomControl: true\r
5263 });\r
5264 \r
5265 Map.addInitHook(function () {\r
5266         if (this.options.zoomControl) {\r
5267                 this.zoomControl = new Zoom();\r
5268                 this.addControl(this.zoomControl);\r
5269         }\r
5270 });\r
5271 \r
5272 // @namespace Control.Zoom\r
5273 // @factory L.control.zoom(options: Control.Zoom options)\r
5274 // Creates a zoom control\r
5275 var zoom = function (options) {\r
5276         return new Zoom(options);\r
5277 };
5278
5279 /*
5280  * @class Control.Scale
5281  * @aka L.Control.Scale
5282  * @inherits Control
5283  *
5284  * 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`.
5285  *
5286  * @example
5287  *
5288  * ```js
5289  * L.control.scale().addTo(map);
5290  * ```
5291  */
5292
5293 var Scale = Control.extend({
5294         // @section
5295         // @aka Control.Scale options
5296         options: {
5297                 position: 'bottomleft',
5298
5299                 // @option maxWidth: Number = 100
5300                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5301                 maxWidth: 100,
5302
5303                 // @option metric: Boolean = True
5304                 // Whether to show the metric scale line (m/km).
5305                 metric: true,
5306
5307                 // @option imperial: Boolean = True
5308                 // Whether to show the imperial scale line (mi/ft).
5309                 imperial: true
5310
5311                 // @option updateWhenIdle: Boolean = false
5312                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5313         },
5314
5315         onAdd: function (map) {
5316                 var className = 'leaflet-control-scale',
5317                     container = create$1('div', className),
5318                     options = this.options;
5319
5320                 this._addScales(options, className + '-line', container);
5321
5322                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5323                 map.whenReady(this._update, this);
5324
5325                 return container;
5326         },
5327
5328         onRemove: function (map) {
5329                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5330         },
5331
5332         _addScales: function (options, className, container) {
5333                 if (options.metric) {
5334                         this._mScale = create$1('div', className, container);
5335                 }
5336                 if (options.imperial) {
5337                         this._iScale = create$1('div', className, container);
5338                 }
5339         },
5340
5341         _update: function () {
5342                 var map = this._map,
5343                     y = map.getSize().y / 2;
5344
5345                 var maxMeters = map.distance(
5346                                 map.containerPointToLatLng([0, y]),
5347                                 map.containerPointToLatLng([this.options.maxWidth, y]));
5348
5349                 this._updateScales(maxMeters);
5350         },
5351
5352         _updateScales: function (maxMeters) {
5353                 if (this.options.metric && maxMeters) {
5354                         this._updateMetric(maxMeters);
5355                 }
5356                 if (this.options.imperial && maxMeters) {
5357                         this._updateImperial(maxMeters);
5358                 }
5359         },
5360
5361         _updateMetric: function (maxMeters) {
5362                 var meters = this._getRoundNum(maxMeters),
5363                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5364
5365                 this._updateScale(this._mScale, label, meters / maxMeters);
5366         },
5367
5368         _updateImperial: function (maxMeters) {
5369                 var maxFeet = maxMeters * 3.2808399,
5370                     maxMiles, miles, feet;
5371
5372                 if (maxFeet > 5280) {
5373                         maxMiles = maxFeet / 5280;
5374                         miles = this._getRoundNum(maxMiles);
5375                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5376
5377                 } else {
5378                         feet = this._getRoundNum(maxFeet);
5379                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5380                 }
5381         },
5382
5383         _updateScale: function (scale, text, ratio) {
5384                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5385                 scale.innerHTML = text;
5386         },
5387
5388         _getRoundNum: function (num) {
5389                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5390                     d = num / pow10;
5391
5392                 d = d >= 10 ? 10 :
5393                     d >= 5 ? 5 :
5394                     d >= 3 ? 3 :
5395                     d >= 2 ? 2 : 1;
5396
5397                 return pow10 * d;
5398         }
5399 });
5400
5401
5402 // @factory L.control.scale(options?: Control.Scale options)
5403 // Creates an scale control with the given options.
5404 var scale = function (options) {
5405         return new Scale(options);
5406 };
5407
5408 /*\r
5409  * @class Control.Attribution\r
5410  * @aka L.Control.Attribution\r
5411  * @inherits Control\r
5412  *\r
5413  * 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
5414  */\r
5415 \r
5416 var Attribution = Control.extend({\r
5417         // @section\r
5418         // @aka Control.Attribution options\r
5419         options: {\r
5420                 position: 'bottomright',\r
5421 \r
5422                 // @option prefix: String = 'Leaflet'\r
5423                 // The HTML text shown before the attributions. Pass `false` to disable.\r
5424                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'\r
5425         },\r
5426 \r
5427         initialize: function (options) {\r
5428                 setOptions(this, options);\r
5429 \r
5430                 this._attributions = {};\r
5431         },\r
5432 \r
5433         onAdd: function (map) {\r
5434                 map.attributionControl = this;\r
5435                 this._container = create$1('div', 'leaflet-control-attribution');\r
5436                 disableClickPropagation(this._container);\r
5437 \r
5438                 // TODO ugly, refactor\r
5439                 for (var i in map._layers) {\r
5440                         if (map._layers[i].getAttribution) {\r
5441                                 this.addAttribution(map._layers[i].getAttribution());\r
5442                         }\r
5443                 }\r
5444 \r
5445                 this._update();\r
5446 \r
5447                 return this._container;\r
5448         },\r
5449 \r
5450         // @method setPrefix(prefix: String): this\r
5451         // Sets the text before the attributions.\r
5452         setPrefix: function (prefix) {\r
5453                 this.options.prefix = prefix;\r
5454                 this._update();\r
5455                 return this;\r
5456         },\r
5457 \r
5458         // @method addAttribution(text: String): this\r
5459         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).\r
5460         addAttribution: function (text) {\r
5461                 if (!text) { return this; }\r
5462 \r
5463                 if (!this._attributions[text]) {\r
5464                         this._attributions[text] = 0;\r
5465                 }\r
5466                 this._attributions[text]++;\r
5467 \r
5468                 this._update();\r
5469 \r
5470                 return this;\r
5471         },\r
5472 \r
5473         // @method removeAttribution(text: String): this\r
5474         // Removes an attribution text.\r
5475         removeAttribution: function (text) {\r
5476                 if (!text) { return this; }\r
5477 \r
5478                 if (this._attributions[text]) {\r
5479                         this._attributions[text]--;\r
5480                         this._update();\r
5481                 }\r
5482 \r
5483                 return this;\r
5484         },\r
5485 \r
5486         _update: function () {\r
5487                 if (!this._map) { return; }\r
5488 \r
5489                 var attribs = [];\r
5490 \r
5491                 for (var i in this._attributions) {\r
5492                         if (this._attributions[i]) {\r
5493                                 attribs.push(i);\r
5494                         }\r
5495                 }\r
5496 \r
5497                 var prefixAndAttribs = [];\r
5498 \r
5499                 if (this.options.prefix) {\r
5500                         prefixAndAttribs.push(this.options.prefix);\r
5501                 }\r
5502                 if (attribs.length) {\r
5503                         prefixAndAttribs.push(attribs.join(', '));\r
5504                 }\r
5505 \r
5506                 this._container.innerHTML = prefixAndAttribs.join(' | ');\r
5507         }\r
5508 });\r
5509 \r
5510 // @namespace Map\r
5511 // @section Control options\r
5512 // @option attributionControl: Boolean = true\r
5513 // Whether a [attribution control](#control-attribution) is added to the map by default.\r
5514 Map.mergeOptions({\r
5515         attributionControl: true\r
5516 });\r
5517 \r
5518 Map.addInitHook(function () {\r
5519         if (this.options.attributionControl) {\r
5520                 new Attribution().addTo(this);\r
5521         }\r
5522 });\r
5523 \r
5524 // @namespace Control.Attribution\r
5525 // @factory L.control.attribution(options: Control.Attribution options)\r
5526 // Creates an attribution control.\r
5527 var attribution = function (options) {\r
5528         return new Attribution(options);\r
5529 };
5530
5531 Control.Layers = Layers;
5532 Control.Zoom = Zoom;
5533 Control.Scale = Scale;
5534 Control.Attribution = Attribution;
5535
5536 control.layers = layers;
5537 control.zoom = zoom;
5538 control.scale = scale;
5539 control.attribution = attribution;
5540
5541 /*
5542         L.Handler is a base class for handler classes that are used internally to inject
5543         interaction features like dragging to classes like Map and Marker.
5544 */
5545
5546 // @class Handler
5547 // @aka L.Handler
5548 // Abstract class for map interaction handlers
5549
5550 var Handler = Class.extend({
5551         initialize: function (map) {
5552                 this._map = map;
5553         },
5554
5555         // @method enable(): this
5556         // Enables the handler
5557         enable: function () {
5558                 if (this._enabled) { return this; }
5559
5560                 this._enabled = true;
5561                 this.addHooks();
5562                 return this;
5563         },
5564
5565         // @method disable(): this
5566         // Disables the handler
5567         disable: function () {
5568                 if (!this._enabled) { return this; }
5569
5570                 this._enabled = false;
5571                 this.removeHooks();
5572                 return this;
5573         },
5574
5575         // @method enabled(): Boolean
5576         // Returns `true` if the handler is enabled
5577         enabled: function () {
5578                 return !!this._enabled;
5579         }
5580
5581         // @section Extension methods
5582         // Classes inheriting from `Handler` must implement the two following methods:
5583         // @method addHooks()
5584         // Called when the handler is enabled, should add event hooks.
5585         // @method removeHooks()
5586         // Called when the handler is disabled, should remove the event hooks added previously.
5587 });
5588
5589 var Mixin = {Events: Events};
5590
5591 /*\r
5592  * @class Draggable\r
5593  * @aka L.Draggable\r
5594  * @inherits Evented\r
5595  *\r
5596  * A class for making DOM elements draggable (including touch support).\r
5597  * Used internally for map and marker dragging. Only works for elements\r
5598  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).\r
5599  *\r
5600  * @example\r
5601  * ```js\r
5602  * var draggable = new L.Draggable(elementToDrag);\r
5603  * draggable.enable();\r
5604  * ```\r
5605  */\r
5606 \r
5607 var START = touch ? 'touchstart mousedown' : 'mousedown';\r
5608 var END = {\r
5609         mousedown: 'mouseup',\r
5610         touchstart: 'touchend',\r
5611         pointerdown: 'touchend',\r
5612         MSPointerDown: 'touchend'\r
5613 };\r
5614 var MOVE = {\r
5615         mousedown: 'mousemove',\r
5616         touchstart: 'touchmove',\r
5617         pointerdown: 'touchmove',\r
5618         MSPointerDown: 'touchmove'\r
5619 };\r
5620 \r
5621 \r
5622 var Draggable = Evented.extend({\r
5623 \r
5624         options: {\r
5625                 // @section\r
5626                 // @aka Draggable options\r
5627                 // @option clickTolerance: Number = 3\r
5628                 // The max number of pixels a user can shift the mouse pointer during a click\r
5629                 // for it to be considered a valid click (as opposed to a mouse drag).\r
5630                 clickTolerance: 3\r
5631         },\r
5632 \r
5633         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)\r
5634         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).\r
5635         initialize: function (element, dragStartTarget, preventOutline$$1, options) {\r
5636                 setOptions(this, options);\r
5637 \r
5638                 this._element = element;\r
5639                 this._dragStartTarget = dragStartTarget || element;\r
5640                 this._preventOutline = preventOutline$$1;\r
5641         },\r
5642 \r
5643         // @method enable()\r
5644         // Enables the dragging ability\r
5645         enable: function () {\r
5646                 if (this._enabled) { return; }\r
5647 \r
5648                 on(this._dragStartTarget, START, this._onDown, this);\r
5649 \r
5650                 this._enabled = true;\r
5651         },\r
5652 \r
5653         // @method disable()\r
5654         // Disables the dragging ability\r
5655         disable: function () {\r
5656                 if (!this._enabled) { return; }\r
5657 \r
5658                 // If we're currently dragging this draggable,\r
5659                 // disabling it counts as first ending the drag.\r
5660                 if (Draggable._dragging === this) {\r
5661                         this.finishDrag();\r
5662                 }\r
5663 \r
5664                 off(this._dragStartTarget, START, this._onDown, this);\r
5665 \r
5666                 this._enabled = false;\r
5667                 this._moved = false;\r
5668         },\r
5669 \r
5670         _onDown: function (e) {\r
5671                 // Ignore simulated events, since we handle both touch and\r
5672                 // mouse explicitly; otherwise we risk getting duplicates of\r
5673                 // touch events, see #4315.\r
5674                 // Also ignore the event if disabled; this happens in IE11\r
5675                 // under some circumstances, see #3666.\r
5676                 if (e._simulated || !this._enabled) { return; }\r
5677 \r
5678                 this._moved = false;\r
5679 \r
5680                 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }\r
5681 \r
5682                 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }\r
5683                 Draggable._dragging = this;  // Prevent dragging multiple objects at once.\r
5684 \r
5685                 if (this._preventOutline) {\r
5686                         preventOutline(this._element);\r
5687                 }\r
5688 \r
5689                 disableImageDrag();\r
5690                 disableTextSelection();\r
5691 \r
5692                 if (this._moving) { return; }\r
5693 \r
5694                 // @event down: Event\r
5695                 // Fired when a drag is about to start.\r
5696                 this.fire('down');\r
5697 \r
5698                 var first = e.touches ? e.touches[0] : e;\r
5699 \r
5700                 this._startPoint = new Point(first.clientX, first.clientY);\r
5701 \r
5702                 on(document, MOVE[e.type], this._onMove, this);\r
5703                 on(document, END[e.type], this._onUp, this);\r
5704         },\r
5705 \r
5706         _onMove: function (e) {\r
5707                 // Ignore simulated events, since we handle both touch and\r
5708                 // mouse explicitly; otherwise we risk getting duplicates of\r
5709                 // touch events, see #4315.\r
5710                 // Also ignore the event if disabled; this happens in IE11\r
5711                 // under some circumstances, see #3666.\r
5712                 if (e._simulated || !this._enabled) { return; }\r
5713 \r
5714                 if (e.touches && e.touches.length > 1) {\r
5715                         this._moved = true;\r
5716                         return;\r
5717                 }\r
5718 \r
5719                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),\r
5720                     newPoint = new Point(first.clientX, first.clientY),\r
5721                     offset = newPoint.subtract(this._startPoint);\r
5722 \r
5723                 if (!offset.x && !offset.y) { return; }\r
5724                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }\r
5725 \r
5726                 preventDefault(e);\r
5727 \r
5728                 if (!this._moved) {\r
5729                         // @event dragstart: Event\r
5730                         // Fired when a drag starts\r
5731                         this.fire('dragstart');\r
5732 \r
5733                         this._moved = true;\r
5734                         this._startPos = getPosition(this._element).subtract(offset);\r
5735 \r
5736                         addClass(document.body, 'leaflet-dragging');\r
5737 \r
5738                         this._lastTarget = e.target || e.srcElement;\r
5739                         // IE and Edge do not give the <use> element, so fetch it\r
5740                         // if necessary\r
5741                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {\r
5742                                 this._lastTarget = this._lastTarget.correspondingUseElement;\r
5743                         }\r
5744                         addClass(this._lastTarget, 'leaflet-drag-target');\r
5745                 }\r
5746 \r
5747                 this._newPos = this._startPos.add(offset);\r
5748                 this._moving = true;\r
5749 \r
5750                 cancelAnimFrame(this._animRequest);\r
5751                 this._lastEvent = e;\r
5752                 this._animRequest = requestAnimFrame(this._updatePosition, this, true);\r
5753         },\r
5754 \r
5755         _updatePosition: function () {\r
5756                 var e = {originalEvent: this._lastEvent};\r
5757 \r
5758                 // @event predrag: Event\r
5759                 // Fired continuously during dragging *before* each corresponding\r
5760                 // update of the element's position.\r
5761                 this.fire('predrag', e);\r
5762                 setPosition(this._element, this._newPos);\r
5763 \r
5764                 // @event drag: Event\r
5765                 // Fired continuously during dragging.\r
5766                 this.fire('drag', e);\r
5767         },\r
5768 \r
5769         _onUp: function (e) {\r
5770                 // Ignore simulated events, since we handle both touch and\r
5771                 // mouse explicitly; otherwise we risk getting duplicates of\r
5772                 // touch events, see #4315.\r
5773                 // Also ignore the event if disabled; this happens in IE11\r
5774                 // under some circumstances, see #3666.\r
5775                 if (e._simulated || !this._enabled) { return; }\r
5776                 this.finishDrag();\r
5777         },\r
5778 \r
5779         finishDrag: function () {\r
5780                 removeClass(document.body, 'leaflet-dragging');\r
5781 \r
5782                 if (this._lastTarget) {\r
5783                         removeClass(this._lastTarget, 'leaflet-drag-target');\r
5784                         this._lastTarget = null;\r
5785                 }\r
5786 \r
5787                 for (var i in MOVE) {\r
5788                         off(document, MOVE[i], this._onMove, this);\r
5789                         off(document, END[i], this._onUp, this);\r
5790                 }\r
5791 \r
5792                 enableImageDrag();\r
5793                 enableTextSelection();\r
5794 \r
5795                 if (this._moved && this._moving) {\r
5796                         // ensure drag is not fired after dragend\r
5797                         cancelAnimFrame(this._animRequest);\r
5798 \r
5799                         // @event dragend: DragEndEvent\r
5800                         // Fired when the drag ends.\r
5801                         this.fire('dragend', {\r
5802                                 distance: this._newPos.distanceTo(this._startPos)\r
5803                         });\r
5804                 }\r
5805 \r
5806                 this._moving = false;\r
5807                 Draggable._dragging = false;\r
5808         }\r
5809 \r
5810 });
5811
5812 /*\r
5813  * @namespace LineUtil\r
5814  *\r
5815  * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.\r
5816  */\r
5817 \r
5818 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.\r
5819 // Improves rendering performance dramatically by lessening the number of points to draw.\r
5820 \r
5821 // @function simplify(points: Point[], tolerance: Number): Point[]\r
5822 // Dramatically reduces the number of points in a polyline while retaining\r
5823 // its shape and returns a new array of simplified points, using the\r
5824 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).\r
5825 // Used for a huge performance boost when processing/displaying Leaflet polylines for\r
5826 // each zoom level and also reducing visual noise. tolerance affects the amount of\r
5827 // simplification (lesser value means higher quality but slower and with more points).\r
5828 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).\r
5829 function simplify(points, tolerance) {\r
5830         if (!tolerance || !points.length) {\r
5831                 return points.slice();\r
5832         }\r
5833 \r
5834         var sqTolerance = tolerance * tolerance;\r
5835 \r
5836             // stage 1: vertex reduction\r
5837             points = _reducePoints(points, sqTolerance);\r
5838 \r
5839             // stage 2: Douglas-Peucker simplification\r
5840             points = _simplifyDP(points, sqTolerance);\r
5841 \r
5842         return points;\r
5843 }\r
5844 \r
5845 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number\r
5846 // Returns the distance between point `p` and segment `p1` to `p2`.\r
5847 function pointToSegmentDistance(p, p1, p2) {\r
5848         return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));\r
5849 }\r
5850 \r
5851 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number\r
5852 // Returns the closest point from a point `p` on a segment `p1` to `p2`.\r
5853 function closestPointOnSegment(p, p1, p2) {\r
5854         return _sqClosestPointOnSegment(p, p1, p2);\r
5855 }\r
5856 \r
5857 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm\r
5858 function _simplifyDP(points, sqTolerance) {\r
5859 \r
5860         var len = points.length,\r
5861             ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,\r
5862             markers = new ArrayConstructor(len);\r
5863 \r
5864             markers[0] = markers[len - 1] = 1;\r
5865 \r
5866         _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);\r
5867 \r
5868         var i,\r
5869             newPoints = [];\r
5870 \r
5871         for (i = 0; i < len; i++) {\r
5872                 if (markers[i]) {\r
5873                         newPoints.push(points[i]);\r
5874                 }\r
5875         }\r
5876 \r
5877         return newPoints;\r
5878 }\r
5879 \r
5880 function _simplifyDPStep(points, markers, sqTolerance, first, last) {\r
5881 \r
5882         var maxSqDist = 0,\r
5883         index, i, sqDist;\r
5884 \r
5885         for (i = first + 1; i <= last - 1; i++) {\r
5886                 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);\r
5887 \r
5888                 if (sqDist > maxSqDist) {\r
5889                         index = i;\r
5890                         maxSqDist = sqDist;\r
5891                 }\r
5892         }\r
5893 \r
5894         if (maxSqDist > sqTolerance) {\r
5895                 markers[index] = 1;\r
5896 \r
5897                 _simplifyDPStep(points, markers, sqTolerance, first, index);\r
5898                 _simplifyDPStep(points, markers, sqTolerance, index, last);\r
5899         }\r
5900 }\r
5901 \r
5902 // reduce points that are too close to each other to a single point\r
5903 function _reducePoints(points, sqTolerance) {\r
5904         var reducedPoints = [points[0]];\r
5905 \r
5906         for (var i = 1, prev = 0, len = points.length; i < len; i++) {\r
5907                 if (_sqDist(points[i], points[prev]) > sqTolerance) {\r
5908                         reducedPoints.push(points[i]);\r
5909                         prev = i;\r
5910                 }\r
5911         }\r
5912         if (prev < len - 1) {\r
5913                 reducedPoints.push(points[len - 1]);\r
5914         }\r
5915         return reducedPoints;\r
5916 }\r
5917 \r
5918 var _lastCode;\r
5919 \r
5920 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean\r
5921 // Clips the segment a to b by rectangular bounds with the\r
5922 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)\r
5923 // (modifying the segment points directly!). Used by Leaflet to only show polyline\r
5924 // points that are on the screen or near, increasing performance.\r
5925 function clipSegment(a, b, bounds, useLastCode, round) {\r
5926         var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),\r
5927             codeB = _getBitCode(b, bounds),\r
5928 \r
5929             codeOut, p, newCode;\r
5930 \r
5931             // save 2nd code to avoid calculating it on the next segment\r
5932             _lastCode = codeB;\r
5933 \r
5934         while (true) {\r
5935                 // if a,b is inside the clip window (trivial accept)\r
5936                 if (!(codeA | codeB)) {\r
5937                         return [a, b];\r
5938                 }\r
5939 \r
5940                 // if a,b is outside the clip window (trivial reject)\r
5941                 if (codeA & codeB) {\r
5942                         return false;\r
5943                 }\r
5944 \r
5945                 // other cases\r
5946                 codeOut = codeA || codeB;\r
5947                 p = _getEdgeIntersection(a, b, codeOut, bounds, round);\r
5948                 newCode = _getBitCode(p, bounds);\r
5949 \r
5950                 if (codeOut === codeA) {\r
5951                         a = p;\r
5952                         codeA = newCode;\r
5953                 } else {\r
5954                         b = p;\r
5955                         codeB = newCode;\r
5956                 }\r
5957         }\r
5958 }\r
5959 \r
5960 function _getEdgeIntersection(a, b, code, bounds, round) {\r
5961         var dx = b.x - a.x,\r
5962             dy = b.y - a.y,\r
5963             min = bounds.min,\r
5964             max = bounds.max,\r
5965             x, y;\r
5966 \r
5967         if (code & 8) { // top\r
5968                 x = a.x + dx * (max.y - a.y) / dy;\r
5969                 y = max.y;\r
5970 \r
5971         } else if (code & 4) { // bottom\r
5972                 x = a.x + dx * (min.y - a.y) / dy;\r
5973                 y = min.y;\r
5974 \r
5975         } else if (code & 2) { // right\r
5976                 x = max.x;\r
5977                 y = a.y + dy * (max.x - a.x) / dx;\r
5978 \r
5979         } else if (code & 1) { // left\r
5980                 x = min.x;\r
5981                 y = a.y + dy * (min.x - a.x) / dx;\r
5982         }\r
5983 \r
5984         return new Point(x, y, round);\r
5985 }\r
5986 \r
5987 function _getBitCode(p, bounds) {\r
5988         var code = 0;\r
5989 \r
5990         if (p.x < bounds.min.x) { // left\r
5991                 code |= 1;\r
5992         } else if (p.x > bounds.max.x) { // right\r
5993                 code |= 2;\r
5994         }\r
5995 \r
5996         if (p.y < bounds.min.y) { // bottom\r
5997                 code |= 4;\r
5998         } else if (p.y > bounds.max.y) { // top\r
5999                 code |= 8;\r
6000         }\r
6001 \r
6002         return code;\r
6003 }\r
6004 \r
6005 // square distance (to avoid unnecessary Math.sqrt calls)\r
6006 function _sqDist(p1, p2) {\r
6007         var dx = p2.x - p1.x,\r
6008             dy = p2.y - p1.y;\r
6009         return dx * dx + dy * dy;\r
6010 }\r
6011 \r
6012 // return closest point on segment or distance to that point\r
6013 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {\r
6014         var x = p1.x,\r
6015             y = p1.y,\r
6016             dx = p2.x - x,\r
6017             dy = p2.y - y,\r
6018             dot = dx * dx + dy * dy,\r
6019             t;\r
6020 \r
6021         if (dot > 0) {\r
6022                 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;\r
6023 \r
6024                 if (t > 1) {\r
6025                         x = p2.x;\r
6026                         y = p2.y;\r
6027                 } else if (t > 0) {\r
6028                         x += dx * t;\r
6029                         y += dy * t;\r
6030                 }\r
6031         }\r
6032 \r
6033         dx = p.x - x;\r
6034         dy = p.y - y;\r
6035 \r
6036         return sqDist ? dx * dx + dy * dy : new Point(x, y);\r
6037 }\r
6038 \r
6039 \r
6040 // @function isFlat(latlngs: LatLng[]): Boolean\r
6041 // Returns true if `latlngs` is a flat array, false is nested.\r
6042 function isFlat(latlngs) {\r
6043         return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');\r
6044 }\r
6045 \r
6046 function _flat(latlngs) {\r
6047         console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');\r
6048         return isFlat(latlngs);\r
6049 }\r
6050
6051
6052 var LineUtil = (Object.freeze || Object)({
6053         simplify: simplify,
6054         pointToSegmentDistance: pointToSegmentDistance,
6055         closestPointOnSegment: closestPointOnSegment,
6056         clipSegment: clipSegment,
6057         _getEdgeIntersection: _getEdgeIntersection,
6058         _getBitCode: _getBitCode,
6059         _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6060         isFlat: isFlat,
6061         _flat: _flat
6062 });
6063
6064 /*\r
6065  * @namespace PolyUtil\r
6066  * Various utility functions for polygon geometries.\r
6067  */\r
6068 \r
6069 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]\r
6070  * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).\r
6071  * Used by Leaflet to only show polygon points that are on the screen or near, increasing\r
6072  * performance. Note that polygon points needs different algorithm for clipping\r
6073  * than polyline, so there's a seperate method for it.\r
6074  */\r
6075 function clipPolygon(points, bounds, round) {\r
6076         var clippedPoints,\r
6077             edges = [1, 4, 2, 8],\r
6078             i, j, k,\r
6079             a, b,\r
6080             len, edge, p;\r
6081 \r
6082         for (i = 0, len = points.length; i < len; i++) {\r
6083                 points[i]._code = _getBitCode(points[i], bounds);\r
6084         }\r
6085 \r
6086         // for each edge (left, bottom, right, top)\r
6087         for (k = 0; k < 4; k++) {\r
6088                 edge = edges[k];\r
6089                 clippedPoints = [];\r
6090 \r
6091                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {\r
6092                         a = points[i];\r
6093                         b = points[j];\r
6094 \r
6095                         // if a is inside the clip window\r
6096                         if (!(a._code & edge)) {\r
6097                                 // if b is outside the clip window (a->b goes out of screen)\r
6098                                 if (b._code & edge) {\r
6099                                         p = _getEdgeIntersection(b, a, edge, bounds, round);\r
6100                                         p._code = _getBitCode(p, bounds);\r
6101                                         clippedPoints.push(p);\r
6102                                 }\r
6103                                 clippedPoints.push(a);\r
6104 \r
6105                         // else if b is inside the clip window (a->b enters the screen)\r
6106                         } else if (!(b._code & edge)) {\r
6107                                 p = _getEdgeIntersection(b, a, edge, bounds, round);\r
6108                                 p._code = _getBitCode(p, bounds);\r
6109                                 clippedPoints.push(p);\r
6110                         }\r
6111                 }\r
6112                 points = clippedPoints;\r
6113         }\r
6114 \r
6115         return points;\r
6116 }\r
6117
6118
6119 var PolyUtil = (Object.freeze || Object)({
6120         clipPolygon: clipPolygon
6121 });
6122
6123 /*\r
6124  * @namespace Projection\r
6125  * @section\r
6126  * Leaflet comes with a set of already defined Projections out of the box:\r
6127  *\r
6128  * @projection L.Projection.LonLat\r
6129  *\r
6130  * Equirectangular, or Plate Carree projection — the most simple projection,\r
6131  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as\r
6132  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the\r
6133  * `EPSG:4326` and `Simple` CRS.\r
6134  */\r
6135 \r
6136 var LonLat = {\r
6137         project: function (latlng) {\r
6138                 return new Point(latlng.lng, latlng.lat);\r
6139         },\r
6140 \r
6141         unproject: function (point) {\r
6142                 return new LatLng(point.y, point.x);\r
6143         },\r
6144 \r
6145         bounds: new Bounds([-180, -90], [180, 90])\r
6146 };
6147
6148 /*\r
6149  * @namespace Projection\r
6150  * @projection L.Projection.Mercator\r
6151  *\r
6152  * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.\r
6153  */\r
6154 \r
6155 var Mercator = {\r
6156         R: 6378137,\r
6157         R_MINOR: 6356752.314245179,\r
6158 \r
6159         bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),\r
6160 \r
6161         project: function (latlng) {\r
6162                 var d = Math.PI / 180,\r
6163                     r = this.R,\r
6164                     y = latlng.lat * d,\r
6165                     tmp = this.R_MINOR / r,\r
6166                     e = Math.sqrt(1 - tmp * tmp),\r
6167                     con = e * Math.sin(y);\r
6168 \r
6169                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);\r
6170                 y = -r * Math.log(Math.max(ts, 1E-10));\r
6171 \r
6172                 return new Point(latlng.lng * d * r, y);\r
6173         },\r
6174 \r
6175         unproject: function (point) {\r
6176                 var d = 180 / Math.PI,\r
6177                     r = this.R,\r
6178                     tmp = this.R_MINOR / r,\r
6179                     e = Math.sqrt(1 - tmp * tmp),\r
6180                     ts = Math.exp(-point.y / r),\r
6181                     phi = Math.PI / 2 - 2 * Math.atan(ts);\r
6182 \r
6183                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {\r
6184                         con = e * Math.sin(phi);\r
6185                         con = Math.pow((1 - con) / (1 + con), e / 2);\r
6186                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;\r
6187                         phi += dphi;\r
6188                 }\r
6189 \r
6190                 return new LatLng(phi * d, point.x * d / r);\r
6191         }\r
6192 };
6193
6194 /*
6195  * @class Projection
6196
6197  * An object with methods for projecting geographical coordinates of the world onto
6198  * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6199
6200  * @property bounds: Bounds
6201  * The bounds (specified in CRS units) where the projection is valid
6202
6203  * @method project(latlng: LatLng): Point
6204  * Projects geographical coordinates into a 2D point.
6205  * Only accepts actual `L.LatLng` instances, not arrays.
6206
6207  * @method unproject(point: Point): LatLng
6208  * The inverse of `project`. Projects a 2D point into a geographical location.
6209  * Only accepts actual `L.Point` instances, not arrays.
6210
6211  */
6212
6213
6214
6215
6216 var index = (Object.freeze || Object)({
6217         LonLat: LonLat,
6218         Mercator: Mercator,
6219         SphericalMercator: SphericalMercator
6220 });
6221
6222 /*\r
6223  * @namespace CRS\r
6224  * @crs L.CRS.EPSG3395\r
6225  *\r
6226  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.\r
6227  */\r
6228 var EPSG3395 = extend({}, Earth, {\r
6229         code: 'EPSG:3395',\r
6230         projection: Mercator,\r
6231 \r
6232         transformation: (function () {\r
6233                 var scale = 0.5 / (Math.PI * Mercator.R);\r
6234                 return toTransformation(scale, 0.5, -scale, 0.5);\r
6235         }())\r
6236 });
6237
6238 /*\r
6239  * @namespace CRS\r
6240  * @crs L.CRS.EPSG4326\r
6241  *\r
6242  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.\r
6243  *\r
6244  * 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
6245  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`\r
6246  * with this CRS, ensure that there are two 256x256 pixel tiles covering the\r
6247  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),\r
6248  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.\r
6249  */\r
6250 \r
6251 var EPSG4326 = extend({}, Earth, {\r
6252         code: 'EPSG:4326',\r
6253         projection: LonLat,\r
6254         transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)\r
6255 });
6256
6257 /*
6258  * @namespace CRS
6259  * @crs L.CRS.Simple
6260  *
6261  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6262  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6263  * axis should still be inverted (going from bottom to top). `distance()` returns
6264  * simple euclidean distance.
6265  */
6266
6267 var Simple = extend({}, CRS, {
6268         projection: LonLat,
6269         transformation: toTransformation(1, 0, -1, 0),
6270
6271         scale: function (zoom) {
6272                 return Math.pow(2, zoom);
6273         },
6274
6275         zoom: function (scale) {
6276                 return Math.log(scale) / Math.LN2;
6277         },
6278
6279         distance: function (latlng1, latlng2) {
6280                 var dx = latlng2.lng - latlng1.lng,
6281                     dy = latlng2.lat - latlng1.lat;
6282
6283                 return Math.sqrt(dx * dx + dy * dy);
6284         },
6285
6286         infinite: true
6287 });
6288
6289 CRS.Earth = Earth;
6290 CRS.EPSG3395 = EPSG3395;
6291 CRS.EPSG3857 = EPSG3857;
6292 CRS.EPSG900913 = EPSG900913;
6293 CRS.EPSG4326 = EPSG4326;
6294 CRS.Simple = Simple;
6295
6296 /*
6297  * @class Layer
6298  * @inherits Evented
6299  * @aka L.Layer
6300  * @aka ILayer
6301  *
6302  * A set of methods from the Layer base class that all Leaflet layers use.
6303  * Inherits all methods, options and events from `L.Evented`.
6304  *
6305  * @example
6306  *
6307  * ```js
6308  * var layer = L.Marker(latlng).addTo(map);
6309  * layer.addTo(map);
6310  * layer.remove();
6311  * ```
6312  *
6313  * @event add: Event
6314  * Fired after the layer is added to a map
6315  *
6316  * @event remove: Event
6317  * Fired after the layer is removed from a map
6318  */
6319
6320
6321 var Layer = Evented.extend({
6322
6323         // Classes extending `L.Layer` will inherit the following options:
6324         options: {
6325                 // @option pane: String = 'overlayPane'
6326                 // 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.
6327                 pane: 'overlayPane',
6328
6329                 // @option attribution: String = null
6330                 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
6331                 attribution: null,
6332
6333                 bubblingMouseEvents: true
6334         },
6335
6336         /* @section
6337          * Classes extending `L.Layer` will inherit the following methods:
6338          *
6339          * @method addTo(map: Map|LayerGroup): this
6340          * Adds the layer to the given map or layer group.
6341          */
6342         addTo: function (map) {
6343                 map.addLayer(this);
6344                 return this;
6345         },
6346
6347         // @method remove: this
6348         // Removes the layer from the map it is currently active on.
6349         remove: function () {
6350                 return this.removeFrom(this._map || this._mapToAdd);
6351         },
6352
6353         // @method removeFrom(map: Map): this
6354         // Removes the layer from the given map
6355         removeFrom: function (obj) {
6356                 if (obj) {
6357                         obj.removeLayer(this);
6358                 }
6359                 return this;
6360         },
6361
6362         // @method getPane(name? : String): HTMLElement
6363         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6364         getPane: function (name) {
6365                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6366         },
6367
6368         addInteractiveTarget: function (targetEl) {
6369                 this._map._targets[stamp(targetEl)] = this;
6370                 return this;
6371         },
6372
6373         removeInteractiveTarget: function (targetEl) {
6374                 delete this._map._targets[stamp(targetEl)];
6375                 return this;
6376         },
6377
6378         // @method getAttribution: String
6379         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6380         getAttribution: function () {
6381                 return this.options.attribution;
6382         },
6383
6384         _layerAdd: function (e) {
6385                 var map = e.target;
6386
6387                 // check in case layer gets added and then removed before the map is ready
6388                 if (!map.hasLayer(this)) { return; }
6389
6390                 this._map = map;
6391                 this._zoomAnimated = map._zoomAnimated;
6392
6393                 if (this.getEvents) {
6394                         var events = this.getEvents();
6395                         map.on(events, this);
6396                         this.once('remove', function () {
6397                                 map.off(events, this);
6398                         }, this);
6399                 }
6400
6401                 this.onAdd(map);
6402
6403                 if (this.getAttribution && map.attributionControl) {
6404                         map.attributionControl.addAttribution(this.getAttribution());
6405                 }
6406
6407                 this.fire('add');
6408                 map.fire('layeradd', {layer: this});
6409         }
6410 });
6411
6412 /* @section Extension methods
6413  * @uninheritable
6414  *
6415  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6416  *
6417  * @method onAdd(map: Map): this
6418  * 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).
6419  *
6420  * @method onRemove(map: Map): this
6421  * 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).
6422  *
6423  * @method getEvents(): Object
6424  * 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.
6425  *
6426  * @method getAttribution(): String
6427  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6428  *
6429  * @method beforeAdd(map: Map): this
6430  * 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.
6431  */
6432
6433
6434 /* @namespace Map
6435  * @section Layer events
6436  *
6437  * @event layeradd: LayerEvent
6438  * Fired when a new layer is added to the map.
6439  *
6440  * @event layerremove: LayerEvent
6441  * Fired when some layer is removed from the map
6442  *
6443  * @section Methods for Layers and Controls
6444  */
6445 Map.include({
6446         // @method addLayer(layer: Layer): this
6447         // Adds the given layer to the map
6448         addLayer: function (layer) {
6449                 if (!layer._layerAdd) {
6450                         throw new Error('The provided object is not a Layer.');
6451                 }
6452
6453                 var id = stamp(layer);
6454                 if (this._layers[id]) { return this; }
6455                 this._layers[id] = layer;
6456
6457                 layer._mapToAdd = this;
6458
6459                 if (layer.beforeAdd) {
6460                         layer.beforeAdd(this);
6461                 }
6462
6463                 this.whenReady(layer._layerAdd, layer);
6464
6465                 return this;
6466         },
6467
6468         // @method removeLayer(layer: Layer): this
6469         // Removes the given layer from the map.
6470         removeLayer: function (layer) {
6471                 var id = stamp(layer);
6472
6473                 if (!this._layers[id]) { return this; }
6474
6475                 if (this._loaded) {
6476                         layer.onRemove(this);
6477                 }
6478
6479                 if (layer.getAttribution && this.attributionControl) {
6480                         this.attributionControl.removeAttribution(layer.getAttribution());
6481                 }
6482
6483                 delete this._layers[id];
6484
6485                 if (this._loaded) {
6486                         this.fire('layerremove', {layer: layer});
6487                         layer.fire('remove');
6488                 }
6489
6490                 layer._map = layer._mapToAdd = null;
6491
6492                 return this;
6493         },
6494
6495         // @method hasLayer(layer: Layer): Boolean
6496         // Returns `true` if the given layer is currently added to the map
6497         hasLayer: function (layer) {
6498                 return !!layer && (stamp(layer) in this._layers);
6499         },
6500
6501         /* @method eachLayer(fn: Function, context?: Object): this
6502          * Iterates over the layers of the map, optionally specifying context of the iterator function.
6503          * ```
6504          * map.eachLayer(function(layer){
6505          *     layer.bindPopup('Hello');
6506          * });
6507          * ```
6508          */
6509         eachLayer: function (method, context) {
6510                 for (var i in this._layers) {
6511                         method.call(context, this._layers[i]);
6512                 }
6513                 return this;
6514         },
6515
6516         _addLayers: function (layers) {
6517                 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6518
6519                 for (var i = 0, len = layers.length; i < len; i++) {
6520                         this.addLayer(layers[i]);
6521                 }
6522         },
6523
6524         _addZoomLimit: function (layer) {
6525                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6526                         this._zoomBoundLayers[stamp(layer)] = layer;
6527                         this._updateZoomLevels();
6528                 }
6529         },
6530
6531         _removeZoomLimit: function (layer) {
6532                 var id = stamp(layer);
6533
6534                 if (this._zoomBoundLayers[id]) {
6535                         delete this._zoomBoundLayers[id];
6536                         this._updateZoomLevels();
6537                 }
6538         },
6539
6540         _updateZoomLevels: function () {
6541                 var minZoom = Infinity,
6542                     maxZoom = -Infinity,
6543                     oldZoomSpan = this._getZoomSpan();
6544
6545                 for (var i in this._zoomBoundLayers) {
6546                         var options = this._zoomBoundLayers[i].options;
6547
6548                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6549                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6550                 }
6551
6552                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6553                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6554
6555                 // @section Map state change events
6556                 // @event zoomlevelschange: Event
6557                 // Fired when the number of zoomlevels on the map is changed due
6558                 // to adding or removing a layer.
6559                 if (oldZoomSpan !== this._getZoomSpan()) {
6560                         this.fire('zoomlevelschange');
6561                 }
6562
6563                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6564                         this.setZoom(this._layersMaxZoom);
6565                 }
6566                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6567                         this.setZoom(this._layersMinZoom);
6568                 }
6569         }
6570 });
6571
6572 /*\r
6573  * @class LayerGroup\r
6574  * @aka L.LayerGroup\r
6575  * @inherits Layer\r
6576  *\r
6577  * Used to group several layers and handle them as one. If you add it to the map,\r
6578  * any layers added or removed from the group will be added/removed on the map as\r
6579  * well. Extends `Layer`.\r
6580  *\r
6581  * @example\r
6582  *\r
6583  * ```js\r
6584  * L.layerGroup([marker1, marker2])\r
6585  *      .addLayer(polyline)\r
6586  *      .addTo(map);\r
6587  * ```\r
6588  */\r
6589 \r
6590 var LayerGroup = Layer.extend({\r
6591 \r
6592         initialize: function (layers) {\r
6593                 this._layers = {};\r
6594 \r
6595                 var i, len;\r
6596 \r
6597                 if (layers) {\r
6598                         for (i = 0, len = layers.length; i < len; i++) {\r
6599                                 this.addLayer(layers[i]);\r
6600                         }\r
6601                 }\r
6602         },\r
6603 \r
6604         // @method addLayer(layer: Layer): this\r
6605         // Adds the given layer to the group.\r
6606         addLayer: function (layer) {\r
6607                 var id = this.getLayerId(layer);\r
6608 \r
6609                 this._layers[id] = layer;\r
6610 \r
6611                 if (this._map) {\r
6612                         this._map.addLayer(layer);\r
6613                 }\r
6614 \r
6615                 return this;\r
6616         },\r
6617 \r
6618         // @method removeLayer(layer: Layer): this\r
6619         // Removes the given layer from the group.\r
6620         // @alternative\r
6621         // @method removeLayer(id: Number): this\r
6622         // Removes the layer with the given internal ID from the group.\r
6623         removeLayer: function (layer) {\r
6624                 var id = layer in this._layers ? layer : this.getLayerId(layer);\r
6625 \r
6626                 if (this._map && this._layers[id]) {\r
6627                         this._map.removeLayer(this._layers[id]);\r
6628                 }\r
6629 \r
6630                 delete this._layers[id];\r
6631 \r
6632                 return this;\r
6633         },\r
6634 \r
6635         // @method hasLayer(layer: Layer): Boolean\r
6636         // Returns `true` if the given layer is currently added to the group.\r
6637         // @alternative\r
6638         // @method hasLayer(id: Number): Boolean\r
6639         // Returns `true` if the given internal ID is currently added to the group.\r
6640         hasLayer: function (layer) {\r
6641                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);\r
6642         },\r
6643 \r
6644         // @method clearLayers(): this\r
6645         // Removes all the layers from the group.\r
6646         clearLayers: function () {\r
6647                 for (var i in this._layers) {\r
6648                         this.removeLayer(this._layers[i]);\r
6649                 }\r
6650                 return this;\r
6651         },\r
6652 \r
6653         // @method invoke(methodName: String, …): this\r
6654         // Calls `methodName` on every layer contained in this group, passing any\r
6655         // additional parameters. Has no effect if the layers contained do not\r
6656         // implement `methodName`.\r
6657         invoke: function (methodName) {\r
6658                 var args = Array.prototype.slice.call(arguments, 1),\r
6659                     i, layer;\r
6660 \r
6661                 for (i in this._layers) {\r
6662                         layer = this._layers[i];\r
6663 \r
6664                         if (layer[methodName]) {\r
6665                                 layer[methodName].apply(layer, args);\r
6666                         }\r
6667                 }\r
6668 \r
6669                 return this;\r
6670         },\r
6671 \r
6672         onAdd: function (map) {\r
6673                 for (var i in this._layers) {\r
6674                         map.addLayer(this._layers[i]);\r
6675                 }\r
6676         },\r
6677 \r
6678         onRemove: function (map) {\r
6679                 for (var i in this._layers) {\r
6680                         map.removeLayer(this._layers[i]);\r
6681                 }\r
6682         },\r
6683 \r
6684         // @method eachLayer(fn: Function, context?: Object): this\r
6685         // Iterates over the layers of the group, optionally specifying context of the iterator function.\r
6686         // ```js\r
6687         // group.eachLayer(function (layer) {\r
6688         //      layer.bindPopup('Hello');\r
6689         // });\r
6690         // ```\r
6691         eachLayer: function (method, context) {\r
6692                 for (var i in this._layers) {\r
6693                         method.call(context, this._layers[i]);\r
6694                 }\r
6695                 return this;\r
6696         },\r
6697 \r
6698         // @method getLayer(id: Number): Layer\r
6699         // Returns the layer with the given internal ID.\r
6700         getLayer: function (id) {\r
6701                 return this._layers[id];\r
6702         },\r
6703 \r
6704         // @method getLayers(): Layer[]\r
6705         // Returns an array of all the layers added to the group.\r
6706         getLayers: function () {\r
6707                 var layers = [];\r
6708 \r
6709                 for (var i in this._layers) {\r
6710                         layers.push(this._layers[i]);\r
6711                 }\r
6712                 return layers;\r
6713         },\r
6714 \r
6715         // @method setZIndex(zIndex: Number): this\r
6716         // Calls `setZIndex` on every layer contained in this group, passing the z-index.\r
6717         setZIndex: function (zIndex) {\r
6718                 return this.invoke('setZIndex', zIndex);\r
6719         },\r
6720 \r
6721         // @method getLayerId(layer: Layer): Number\r
6722         // Returns the internal ID for a layer\r
6723         getLayerId: function (layer) {\r
6724                 return stamp(layer);\r
6725         }\r
6726 });\r
6727 \r
6728 \r
6729 // @factory L.layerGroup(layers?: Layer[])\r
6730 // Create a layer group, optionally given an initial set of layers.\r
6731 var layerGroup = function (layers) {\r
6732         return new LayerGroup(layers);\r
6733 };
6734
6735 /*\r
6736  * @class FeatureGroup\r
6737  * @aka L.FeatureGroup\r
6738  * @inherits LayerGroup\r
6739  *\r
6740  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:\r
6741  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))\r
6742  *  * Events are propagated to the `FeatureGroup`, so if the group has an event\r
6743  * handler, it will handle events from any of the layers. This includes mouse events\r
6744  * and custom events.\r
6745  *  * Has `layeradd` and `layerremove` events\r
6746  *\r
6747  * @example\r
6748  *\r
6749  * ```js\r
6750  * L.featureGroup([marker1, marker2, polyline])\r
6751  *      .bindPopup('Hello world!')\r
6752  *      .on('click', function() { alert('Clicked on a member of the group!'); })\r
6753  *      .addTo(map);\r
6754  * ```\r
6755  */\r
6756 \r
6757 var FeatureGroup = LayerGroup.extend({\r
6758 \r
6759         addLayer: function (layer) {\r
6760                 if (this.hasLayer(layer)) {\r
6761                         return this;\r
6762                 }\r
6763 \r
6764                 layer.addEventParent(this);\r
6765 \r
6766                 LayerGroup.prototype.addLayer.call(this, layer);\r
6767 \r
6768                 // @event layeradd: LayerEvent\r
6769                 // Fired when a layer is added to this `FeatureGroup`\r
6770                 return this.fire('layeradd', {layer: layer});\r
6771         },\r
6772 \r
6773         removeLayer: function (layer) {\r
6774                 if (!this.hasLayer(layer)) {\r
6775                         return this;\r
6776                 }\r
6777                 if (layer in this._layers) {\r
6778                         layer = this._layers[layer];\r
6779                 }\r
6780 \r
6781                 layer.removeEventParent(this);\r
6782 \r
6783                 LayerGroup.prototype.removeLayer.call(this, layer);\r
6784 \r
6785                 // @event layerremove: LayerEvent\r
6786                 // Fired when a layer is removed from this `FeatureGroup`\r
6787                 return this.fire('layerremove', {layer: layer});\r
6788         },\r
6789 \r
6790         // @method setStyle(style: Path options): this\r
6791         // Sets the given path options to each layer of the group that has a `setStyle` method.\r
6792         setStyle: function (style) {\r
6793                 return this.invoke('setStyle', style);\r
6794         },\r
6795 \r
6796         // @method bringToFront(): this\r
6797         // Brings the layer group to the top of all other layers\r
6798         bringToFront: function () {\r
6799                 return this.invoke('bringToFront');\r
6800         },\r
6801 \r
6802         // @method bringToBack(): this\r
6803         // Brings the layer group to the top of all other layers\r
6804         bringToBack: function () {\r
6805                 return this.invoke('bringToBack');\r
6806         },\r
6807 \r
6808         // @method getBounds(): LatLngBounds\r
6809         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).\r
6810         getBounds: function () {\r
6811                 var bounds = new LatLngBounds();\r
6812 \r
6813                 for (var id in this._layers) {\r
6814                         var layer = this._layers[id];\r
6815                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());\r
6816                 }\r
6817                 return bounds;\r
6818         }\r
6819 });\r
6820 \r
6821 // @factory L.featureGroup(layers: Layer[])\r
6822 // Create a feature group, optionally given an initial set of layers.\r
6823 var featureGroup = function (layers) {\r
6824         return new FeatureGroup(layers);\r
6825 };
6826
6827 /*\r
6828  * @class Icon\r
6829  * @aka L.Icon\r
6830  *\r
6831  * Represents an icon to provide when creating a marker.\r
6832  *\r
6833  * @example\r
6834  *\r
6835  * ```js\r
6836  * var myIcon = L.icon({\r
6837  *     iconUrl: 'my-icon.png',\r
6838  *     iconRetinaUrl: 'my-icon@2x.png',\r
6839  *     iconSize: [38, 95],\r
6840  *     iconAnchor: [22, 94],\r
6841  *     popupAnchor: [-3, -76],\r
6842  *     shadowUrl: 'my-icon-shadow.png',\r
6843  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',\r
6844  *     shadowSize: [68, 95],\r
6845  *     shadowAnchor: [22, 94]\r
6846  * });\r
6847  *\r
6848  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);\r
6849  * ```\r
6850  *\r
6851  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.\r
6852  *\r
6853  */\r
6854 \r
6855 var Icon = Class.extend({\r
6856 \r
6857         /* @section\r
6858          * @aka Icon options\r
6859          *\r
6860          * @option iconUrl: String = null\r
6861          * **(required)** The URL to the icon image (absolute or relative to your script path).\r
6862          *\r
6863          * @option iconRetinaUrl: String = null\r
6864          * The URL to a retina sized version of the icon image (absolute or relative to your\r
6865          * script path). Used for Retina screen devices.\r
6866          *\r
6867          * @option iconSize: Point = null\r
6868          * Size of the icon image in pixels.\r
6869          *\r
6870          * @option iconAnchor: Point = null\r
6871          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon\r
6872          * will be aligned so that this point is at the marker's geographical location. Centered\r
6873          * by default if size is specified, also can be set in CSS with negative margins.\r
6874          *\r
6875          * @option popupAnchor: Point = null\r
6876          * The coordinates of the point from which popups will "open", relative to the icon anchor.\r
6877          *\r
6878          * @option shadowUrl: String = null\r
6879          * The URL to the icon shadow image. If not specified, no shadow image will be created.\r
6880          *\r
6881          * @option shadowRetinaUrl: String = null\r
6882          *\r
6883          * @option shadowSize: Point = null\r
6884          * Size of the shadow image in pixels.\r
6885          *\r
6886          * @option shadowAnchor: Point = null\r
6887          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same\r
6888          * as iconAnchor if not specified).\r
6889          *\r
6890          * @option className: String = ''\r
6891          * A custom class name to assign to both icon and shadow images. Empty by default.\r
6892          */\r
6893 \r
6894         initialize: function (options) {\r
6895                 setOptions(this, options);\r
6896         },\r
6897 \r
6898         // @method createIcon(oldIcon?: HTMLElement): HTMLElement\r
6899         // Called internally when the icon has to be shown, returns a `<img>` HTML element\r
6900         // styled according to the options.\r
6901         createIcon: function (oldIcon) {\r
6902                 return this._createIcon('icon', oldIcon);\r
6903         },\r
6904 \r
6905         // @method createShadow(oldIcon?: HTMLElement): HTMLElement\r
6906         // As `createIcon`, but for the shadow beneath it.\r
6907         createShadow: function (oldIcon) {\r
6908                 return this._createIcon('shadow', oldIcon);\r
6909         },\r
6910 \r
6911         _createIcon: function (name, oldIcon) {\r
6912                 var src = this._getIconUrl(name);\r
6913 \r
6914                 if (!src) {\r
6915                         if (name === 'icon') {\r
6916                                 throw new Error('iconUrl not set in Icon options (see the docs).');\r
6917                         }\r
6918                         return null;\r
6919                 }\r
6920 \r
6921                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);\r
6922                 this._setIconStyles(img, name);\r
6923 \r
6924                 return img;\r
6925         },\r
6926 \r
6927         _setIconStyles: function (img, name) {\r
6928                 var options = this.options;\r
6929                 var sizeOption = options[name + 'Size'];\r
6930 \r
6931                 if (typeof sizeOption === 'number') {\r
6932                         sizeOption = [sizeOption, sizeOption];\r
6933                 }\r
6934 \r
6935                 var size = toPoint(sizeOption),\r
6936                     anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||\r
6937                             size && size.divideBy(2, true));\r
6938 \r
6939                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');\r
6940 \r
6941                 if (anchor) {\r
6942                         img.style.marginLeft = (-anchor.x) + 'px';\r
6943                         img.style.marginTop  = (-anchor.y) + 'px';\r
6944                 }\r
6945 \r
6946                 if (size) {\r
6947                         img.style.width  = size.x + 'px';\r
6948                         img.style.height = size.y + 'px';\r
6949                 }\r
6950         },\r
6951 \r
6952         _createImg: function (src, el) {\r
6953                 el = el || document.createElement('img');\r
6954                 el.src = src;\r
6955                 return el;\r
6956         },\r
6957 \r
6958         _getIconUrl: function (name) {\r
6959                 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];\r
6960         }\r
6961 });\r
6962 \r
6963 \r
6964 // @factory L.icon(options: Icon options)\r
6965 // Creates an icon instance with the given options.\r
6966 function icon(options) {\r
6967         return new Icon(options);\r
6968 }
6969
6970 /*
6971  * @miniclass Icon.Default (Icon)
6972  * @aka L.Icon.Default
6973  * @section
6974  *
6975  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6976  * no icon is specified. Points to the blue marker image distributed with Leaflet
6977  * releases.
6978  *
6979  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6980  * (which is a set of `Icon options`).
6981  *
6982  * If you want to _completely_ replace the default icon, override the
6983  * `L.Marker.prototype.options.icon` with your own icon instead.
6984  */
6985
6986 var IconDefault = Icon.extend({
6987
6988         options: {
6989                 iconUrl:       'marker-icon.png',
6990                 iconRetinaUrl: 'marker-icon-2x.png',
6991                 shadowUrl:     'marker-shadow.png',
6992                 iconSize:    [25, 41],
6993                 iconAnchor:  [12, 41],
6994                 popupAnchor: [1, -34],
6995                 tooltipAnchor: [16, -28],
6996                 shadowSize:  [41, 41]
6997         },
6998
6999         _getIconUrl: function (name) {
7000                 if (!IconDefault.imagePath) {   // Deprecated, backwards-compatibility only
7001                         IconDefault.imagePath = this._detectIconPath();
7002                 }
7003
7004                 // @option imagePath: String
7005                 // `Icon.Default` will try to auto-detect the absolute location of the
7006                 // blue icon images. If you are placing these images in a non-standard
7007                 // way, set this option to point to the right absolute path.
7008                 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7009         },
7010
7011         _detectIconPath: function () {
7012                 var el = create$1('div',  'leaflet-default-icon-path', document.body);
7013                 var path = getStyle(el, 'background-image') ||
7014                            getStyle(el, 'backgroundImage');     // IE8
7015
7016                 document.body.removeChild(el);
7017
7018                 if (path === null || path.indexOf('url') !== 0) {
7019                         path = '';
7020                 } else {
7021                         path = path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '');
7022                 }
7023
7024                 return path;
7025         }
7026 });
7027
7028 /*
7029  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7030  */
7031
7032
7033 /* @namespace Marker
7034  * @section Interaction handlers
7035  *
7036  * 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:
7037  *
7038  * ```js
7039  * marker.dragging.disable();
7040  * ```
7041  *
7042  * @property dragging: Handler
7043  * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7044  */
7045
7046 var MarkerDrag = Handler.extend({
7047         initialize: function (marker) {
7048                 this._marker = marker;
7049         },
7050
7051         addHooks: function () {
7052                 var icon = this._marker._icon;
7053
7054                 if (!this._draggable) {
7055                         this._draggable = new Draggable(icon, icon, true);
7056                 }
7057
7058                 this._draggable.on({
7059                         dragstart: this._onDragStart,
7060                         drag: this._onDrag,
7061                         dragend: this._onDragEnd
7062                 }, this).enable();
7063
7064                 addClass(icon, 'leaflet-marker-draggable');
7065         },
7066
7067         removeHooks: function () {
7068                 this._draggable.off({
7069                         dragstart: this._onDragStart,
7070                         drag: this._onDrag,
7071                         dragend: this._onDragEnd
7072                 }, this).disable();
7073
7074                 if (this._marker._icon) {
7075                         removeClass(this._marker._icon, 'leaflet-marker-draggable');
7076                 }
7077         },
7078
7079         moved: function () {
7080                 return this._draggable && this._draggable._moved;
7081         },
7082
7083         _onDragStart: function () {
7084                 // @section Dragging events
7085                 // @event dragstart: Event
7086                 // Fired when the user starts dragging the marker.
7087
7088                 // @event movestart: Event
7089                 // Fired when the marker starts moving (because of dragging).
7090
7091                 this._oldLatLng = this._marker.getLatLng();
7092                 this._marker
7093                     .closePopup()
7094                     .fire('movestart')
7095                     .fire('dragstart');
7096         },
7097
7098         _onDrag: function (e) {
7099                 var marker = this._marker,
7100                     shadow = marker._shadow,
7101                 iconPos = getPosition(marker._icon),
7102                     latlng = marker._map.layerPointToLatLng(iconPos);
7103
7104                 // update shadow position
7105                 if (shadow) {
7106                         setPosition(shadow, iconPos);
7107                 }
7108
7109                 marker._latlng = latlng;
7110                 e.latlng = latlng;
7111                 e.oldLatLng = this._oldLatLng;
7112
7113                 // @event drag: Event
7114                 // Fired repeatedly while the user drags the marker.
7115                 marker
7116                     .fire('move', e)
7117                     .fire('drag', e);
7118         },
7119
7120         _onDragEnd: function (e) {
7121                 // @event dragend: DragEndEvent
7122                 // Fired when the user stops dragging the marker.
7123
7124                 // @event moveend: Event
7125                 // Fired when the marker stops moving (because of dragging).
7126                 delete this._oldLatLng;
7127                 this._marker
7128                     .fire('moveend')
7129                     .fire('dragend', e);
7130         }
7131 });
7132
7133 /*\r
7134  * @class Marker\r
7135  * @inherits Interactive layer\r
7136  * @aka L.Marker\r
7137  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.\r
7138  *\r
7139  * @example\r
7140  *\r
7141  * ```js\r
7142  * L.marker([50.5, 30.5]).addTo(map);\r
7143  * ```\r
7144  */\r
7145 \r
7146 var Marker = Layer.extend({\r
7147 \r
7148         // @section\r
7149         // @aka Marker options\r
7150         options: {\r
7151                 // @option icon: Icon = *\r
7152                 // Icon instance to use for rendering the marker.\r
7153                 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.\r
7154                 // If not specified, a common instance of `L.Icon.Default` is used.\r
7155                 icon: new IconDefault(),\r
7156 \r
7157                 // Option inherited from "Interactive layer" abstract class\r
7158                 interactive: true,\r
7159 \r
7160                 // @option draggable: Boolean = false\r
7161                 // Whether the marker is draggable with mouse/touch or not.\r
7162                 draggable: false,\r
7163 \r
7164                 // @option keyboard: Boolean = true\r
7165                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.\r
7166                 keyboard: true,\r
7167 \r
7168                 // @option title: String = ''\r
7169                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).\r
7170                 title: '',\r
7171 \r
7172                 // @option alt: String = ''\r
7173                 // Text for the `alt` attribute of the icon image (useful for accessibility).\r
7174                 alt: '',\r
7175 \r
7176                 // @option zIndexOffset: Number = 0\r
7177                 // 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
7178                 zIndexOffset: 0,\r
7179 \r
7180                 // @option opacity: Number = 1.0\r
7181                 // The opacity of the marker.\r
7182                 opacity: 1,\r
7183 \r
7184                 // @option riseOnHover: Boolean = false\r
7185                 // If `true`, the marker will get on top of others when you hover the mouse over it.\r
7186                 riseOnHover: false,\r
7187 \r
7188                 // @option riseOffset: Number = 250\r
7189                 // The z-index offset used for the `riseOnHover` feature.\r
7190                 riseOffset: 250,\r
7191 \r
7192                 // @option pane: String = 'markerPane'\r
7193                 // `Map pane` where the markers icon will be added.\r
7194                 pane: 'markerPane',\r
7195 \r
7196                 // @option bubblingMouseEvents: Boolean = false\r
7197                 // When `true`, a mouse event on this marker will trigger the same event on the map\r
7198                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).\r
7199                 bubblingMouseEvents: false\r
7200         },\r
7201 \r
7202         /* @section\r
7203          *\r
7204          * 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
7205          */\r
7206 \r
7207         initialize: function (latlng, options) {\r
7208                 setOptions(this, options);\r
7209                 this._latlng = toLatLng(latlng);\r
7210         },\r
7211 \r
7212         onAdd: function (map) {\r
7213                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;\r
7214 \r
7215                 if (this._zoomAnimated) {\r
7216                         map.on('zoomanim', this._animateZoom, this);\r
7217                 }\r
7218 \r
7219                 this._initIcon();\r
7220                 this.update();\r
7221         },\r
7222 \r
7223         onRemove: function (map) {\r
7224                 if (this.dragging && this.dragging.enabled()) {\r
7225                         this.options.draggable = true;\r
7226                         this.dragging.removeHooks();\r
7227                 }\r
7228                 delete this.dragging;\r
7229 \r
7230                 if (this._zoomAnimated) {\r
7231                         map.off('zoomanim', this._animateZoom, this);\r
7232                 }\r
7233 \r
7234                 this._removeIcon();\r
7235                 this._removeShadow();\r
7236         },\r
7237 \r
7238         getEvents: function () {\r
7239                 return {\r
7240                         zoom: this.update,\r
7241                         viewreset: this.update\r
7242                 };\r
7243         },\r
7244 \r
7245         // @method getLatLng: LatLng\r
7246         // Returns the current geographical position of the marker.\r
7247         getLatLng: function () {\r
7248                 return this._latlng;\r
7249         },\r
7250 \r
7251         // @method setLatLng(latlng: LatLng): this\r
7252         // Changes the marker position to the given point.\r
7253         setLatLng: function (latlng) {\r
7254                 var oldLatLng = this._latlng;\r
7255                 this._latlng = toLatLng(latlng);\r
7256                 this.update();\r
7257 \r
7258                 // @event move: Event\r
7259                 // 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
7260                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});\r
7261         },\r
7262 \r
7263         // @method setZIndexOffset(offset: Number): this\r
7264         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.\r
7265         setZIndexOffset: function (offset) {\r
7266                 this.options.zIndexOffset = offset;\r
7267                 return this.update();\r
7268         },\r
7269 \r
7270         // @method setIcon(icon: Icon): this\r
7271         // Changes the marker icon.\r
7272         setIcon: function (icon) {\r
7273 \r
7274                 this.options.icon = icon;\r
7275 \r
7276                 if (this._map) {\r
7277                         this._initIcon();\r
7278                         this.update();\r
7279                 }\r
7280 \r
7281                 if (this._popup) {\r
7282                         this.bindPopup(this._popup, this._popup.options);\r
7283                 }\r
7284 \r
7285                 return this;\r
7286         },\r
7287 \r
7288         getElement: function () {\r
7289                 return this._icon;\r
7290         },\r
7291 \r
7292         update: function () {\r
7293 \r
7294                 if (this._icon) {\r
7295                         var pos = this._map.latLngToLayerPoint(this._latlng).round();\r
7296                         this._setPos(pos);\r
7297                 }\r
7298 \r
7299                 return this;\r
7300         },\r
7301 \r
7302         _initIcon: function () {\r
7303                 var options = this.options,\r
7304                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');\r
7305 \r
7306                 var icon = options.icon.createIcon(this._icon),\r
7307                     addIcon = false;\r
7308 \r
7309                 // if we're not reusing the icon, remove the old one and init new one\r
7310                 if (icon !== this._icon) {\r
7311                         if (this._icon) {\r
7312                                 this._removeIcon();\r
7313                         }\r
7314                         addIcon = true;\r
7315 \r
7316                         if (options.title) {\r
7317                                 icon.title = options.title;\r
7318                         }\r
7319                         if (options.alt) {\r
7320                                 icon.alt = options.alt;\r
7321                         }\r
7322                 }\r
7323 \r
7324                 addClass(icon, classToAdd);\r
7325 \r
7326                 if (options.keyboard) {\r
7327                         icon.tabIndex = '0';\r
7328                 }\r
7329 \r
7330                 this._icon = icon;\r
7331 \r
7332                 if (options.riseOnHover) {\r
7333                         this.on({\r
7334                                 mouseover: this._bringToFront,\r
7335                                 mouseout: this._resetZIndex\r
7336                         });\r
7337                 }\r
7338 \r
7339                 var newShadow = options.icon.createShadow(this._shadow),\r
7340                     addShadow = false;\r
7341 \r
7342                 if (newShadow !== this._shadow) {\r
7343                         this._removeShadow();\r
7344                         addShadow = true;\r
7345                 }\r
7346 \r
7347                 if (newShadow) {\r
7348                         addClass(newShadow, classToAdd);\r
7349                         newShadow.alt = '';\r
7350                 }\r
7351                 this._shadow = newShadow;\r
7352 \r
7353 \r
7354                 if (options.opacity < 1) {\r
7355                         this._updateOpacity();\r
7356                 }\r
7357 \r
7358 \r
7359                 if (addIcon) {\r
7360                         this.getPane().appendChild(this._icon);\r
7361                 }\r
7362                 this._initInteraction();\r
7363                 if (newShadow && addShadow) {\r
7364                         this.getPane('shadowPane').appendChild(this._shadow);\r
7365                 }\r
7366         },\r
7367 \r
7368         _removeIcon: function () {\r
7369                 if (this.options.riseOnHover) {\r
7370                         this.off({\r
7371                                 mouseover: this._bringToFront,\r
7372                                 mouseout: this._resetZIndex\r
7373                         });\r
7374                 }\r
7375 \r
7376                 remove(this._icon);\r
7377                 this.removeInteractiveTarget(this._icon);\r
7378 \r
7379                 this._icon = null;\r
7380         },\r
7381 \r
7382         _removeShadow: function () {\r
7383                 if (this._shadow) {\r
7384                         remove(this._shadow);\r
7385                 }\r
7386                 this._shadow = null;\r
7387         },\r
7388 \r
7389         _setPos: function (pos) {\r
7390                 setPosition(this._icon, pos);\r
7391 \r
7392                 if (this._shadow) {\r
7393                         setPosition(this._shadow, pos);\r
7394                 }\r
7395 \r
7396                 this._zIndex = pos.y + this.options.zIndexOffset;\r
7397 \r
7398                 this._resetZIndex();\r
7399         },\r
7400 \r
7401         _updateZIndex: function (offset) {\r
7402                 this._icon.style.zIndex = this._zIndex + offset;\r
7403         },\r
7404 \r
7405         _animateZoom: function (opt) {\r
7406                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();\r
7407 \r
7408                 this._setPos(pos);\r
7409         },\r
7410 \r
7411         _initInteraction: function () {\r
7412 \r
7413                 if (!this.options.interactive) { return; }\r
7414 \r
7415                 addClass(this._icon, 'leaflet-interactive');\r
7416 \r
7417                 this.addInteractiveTarget(this._icon);\r
7418 \r
7419                 if (MarkerDrag) {\r
7420                         var draggable = this.options.draggable;\r
7421                         if (this.dragging) {\r
7422                                 draggable = this.dragging.enabled();\r
7423                                 this.dragging.disable();\r
7424                         }\r
7425 \r
7426                         this.dragging = new MarkerDrag(this);\r
7427 \r
7428                         if (draggable) {\r
7429                                 this.dragging.enable();\r
7430                         }\r
7431                 }\r
7432         },\r
7433 \r
7434         // @method setOpacity(opacity: Number): this\r
7435         // Changes the opacity of the marker.\r
7436         setOpacity: function (opacity) {\r
7437                 this.options.opacity = opacity;\r
7438                 if (this._map) {\r
7439                         this._updateOpacity();\r
7440                 }\r
7441 \r
7442                 return this;\r
7443         },\r
7444 \r
7445         _updateOpacity: function () {\r
7446                 var opacity = this.options.opacity;\r
7447 \r
7448                 setOpacity(this._icon, opacity);\r
7449 \r
7450                 if (this._shadow) {\r
7451                         setOpacity(this._shadow, opacity);\r
7452                 }\r
7453         },\r
7454 \r
7455         _bringToFront: function () {\r
7456                 this._updateZIndex(this.options.riseOffset);\r
7457         },\r
7458 \r
7459         _resetZIndex: function () {\r
7460                 this._updateZIndex(0);\r
7461         },\r
7462 \r
7463         _getPopupAnchor: function () {\r
7464                 return this.options.icon.options.popupAnchor || [0, 0];\r
7465         },\r
7466 \r
7467         _getTooltipAnchor: function () {\r
7468                 return this.options.icon.options.tooltipAnchor || [0, 0];\r
7469         }\r
7470 });\r
7471 \r
7472 \r
7473 // factory L.marker(latlng: LatLng, options? : Marker options)\r
7474 \r
7475 // @factory L.marker(latlng: LatLng, options? : Marker options)\r
7476 // Instantiates a Marker object given a geographical point and optionally an options object.\r
7477 function marker(latlng, options) {\r
7478         return new Marker(latlng, options);\r
7479 }
7480
7481 /*
7482  * @class Path
7483  * @aka L.Path
7484  * @inherits Interactive layer
7485  *
7486  * An abstract class that contains options and constants shared between vector
7487  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7488  */
7489
7490 var Path = Layer.extend({
7491
7492         // @section
7493         // @aka Path options
7494         options: {
7495                 // @option stroke: Boolean = true
7496                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7497                 stroke: true,
7498
7499                 // @option color: String = '#3388ff'
7500                 // Stroke color
7501                 color: '#3388ff',
7502
7503                 // @option weight: Number = 3
7504                 // Stroke width in pixels
7505                 weight: 3,
7506
7507                 // @option opacity: Number = 1.0
7508                 // Stroke opacity
7509                 opacity: 1,
7510
7511                 // @option lineCap: String= 'round'
7512                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7513                 lineCap: 'round',
7514
7515                 // @option lineJoin: String = 'round'
7516                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7517                 lineJoin: 'round',
7518
7519                 // @option dashArray: String = null
7520                 // 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).
7521                 dashArray: null,
7522
7523                 // @option dashOffset: String = null
7524                 // 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).
7525                 dashOffset: null,
7526
7527                 // @option fill: Boolean = depends
7528                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7529                 fill: false,
7530
7531                 // @option fillColor: String = *
7532                 // Fill color. Defaults to the value of the [`color`](#path-color) option
7533                 fillColor: null,
7534
7535                 // @option fillOpacity: Number = 0.2
7536                 // Fill opacity.
7537                 fillOpacity: 0.2,
7538
7539                 // @option fillRule: String = 'evenodd'
7540                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7541                 fillRule: 'evenodd',
7542
7543                 // className: '',
7544
7545                 // Option inherited from "Interactive layer" abstract class
7546                 interactive: true,
7547
7548                 // @option bubblingMouseEvents: Boolean = true
7549                 // When `true`, a mouse event on this path will trigger the same event on the map
7550                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7551                 bubblingMouseEvents: true
7552         },
7553
7554         beforeAdd: function (map) {
7555                 // Renderer is set here because we need to call renderer.getEvents
7556                 // before this.getEvents.
7557                 this._renderer = map.getRenderer(this);
7558         },
7559
7560         onAdd: function () {
7561                 this._renderer._initPath(this);
7562                 this._reset();
7563                 this._renderer._addPath(this);
7564         },
7565
7566         onRemove: function () {
7567                 this._renderer._removePath(this);
7568         },
7569
7570         // @method redraw(): this
7571         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7572         redraw: function () {
7573                 if (this._map) {
7574                         this._renderer._updatePath(this);
7575                 }
7576                 return this;
7577         },
7578
7579         // @method setStyle(style: Path options): this
7580         // Changes the appearance of a Path based on the options in the `Path options` object.
7581         setStyle: function (style) {
7582                 setOptions(this, style);
7583                 if (this._renderer) {
7584                         this._renderer._updateStyle(this);
7585                 }
7586                 return this;
7587         },
7588
7589         // @method bringToFront(): this
7590         // Brings the layer to the top of all path layers.
7591         bringToFront: function () {
7592                 if (this._renderer) {
7593                         this._renderer._bringToFront(this);
7594                 }
7595                 return this;
7596         },
7597
7598         // @method bringToBack(): this
7599         // Brings the layer to the bottom of all path layers.
7600         bringToBack: function () {
7601                 if (this._renderer) {
7602                         this._renderer._bringToBack(this);
7603                 }
7604                 return this;
7605         },
7606
7607         getElement: function () {
7608                 return this._path;
7609         },
7610
7611         _reset: function () {
7612                 // defined in child classes
7613                 this._project();
7614                 this._update();
7615         },
7616
7617         _clickTolerance: function () {
7618                 // used when doing hit detection for Canvas layers
7619                 return (this.options.stroke ? this.options.weight / 2 : 0) + (touch ? 10 : 0);
7620         }
7621 });
7622
7623 /*
7624  * @class CircleMarker
7625  * @aka L.CircleMarker
7626  * @inherits Path
7627  *
7628  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7629  */
7630
7631 var CircleMarker = Path.extend({
7632
7633         // @section
7634         // @aka CircleMarker options
7635         options: {
7636                 fill: true,
7637
7638                 // @option radius: Number = 10
7639                 // Radius of the circle marker, in pixels
7640                 radius: 10
7641         },
7642
7643         initialize: function (latlng, options) {
7644                 setOptions(this, options);
7645                 this._latlng = toLatLng(latlng);
7646                 this._radius = this.options.radius;
7647         },
7648
7649         // @method setLatLng(latLng: LatLng): this
7650         // Sets the position of a circle marker to a new location.
7651         setLatLng: function (latlng) {
7652                 this._latlng = toLatLng(latlng);
7653                 this.redraw();
7654                 return this.fire('move', {latlng: this._latlng});
7655         },
7656
7657         // @method getLatLng(): LatLng
7658         // Returns the current geographical position of the circle marker
7659         getLatLng: function () {
7660                 return this._latlng;
7661         },
7662
7663         // @method setRadius(radius: Number): this
7664         // Sets the radius of a circle marker. Units are in pixels.
7665         setRadius: function (radius) {
7666                 this.options.radius = this._radius = radius;
7667                 return this.redraw();
7668         },
7669
7670         // @method getRadius(): Number
7671         // Returns the current radius of the circle
7672         getRadius: function () {
7673                 return this._radius;
7674         },
7675
7676         setStyle : function (options) {
7677                 var radius = options && options.radius || this._radius;
7678                 Path.prototype.setStyle.call(this, options);
7679                 this.setRadius(radius);
7680                 return this;
7681         },
7682
7683         _project: function () {
7684                 this._point = this._map.latLngToLayerPoint(this._latlng);
7685                 this._updateBounds();
7686         },
7687
7688         _updateBounds: function () {
7689                 var r = this._radius,
7690                     r2 = this._radiusY || r,
7691                     w = this._clickTolerance(),
7692                     p = [r + w, r2 + w];
7693                 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7694         },
7695
7696         _update: function () {
7697                 if (this._map) {
7698                         this._updatePath();
7699                 }
7700         },
7701
7702         _updatePath: function () {
7703                 this._renderer._updateCircle(this);
7704         },
7705
7706         _empty: function () {
7707                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7708         },
7709
7710         // Needed by the `Canvas` renderer for interactivity
7711         _containsPoint: function (p) {
7712                 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7713         }
7714 });
7715
7716
7717 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7718 // Instantiates a circle marker object given a geographical point, and an optional options object.
7719 function circleMarker(latlng, options) {
7720         return new CircleMarker(latlng, options);
7721 }
7722
7723 /*
7724  * @class Circle
7725  * @aka L.Circle
7726  * @inherits CircleMarker
7727  *
7728  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7729  *
7730  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7731  *
7732  * @example
7733  *
7734  * ```js
7735  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7736  * ```
7737  */
7738
7739 var Circle = CircleMarker.extend({
7740
7741         initialize: function (latlng, options, legacyOptions) {
7742                 if (typeof options === 'number') {
7743                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7744                         options = extend({}, legacyOptions, {radius: options});
7745                 }
7746                 setOptions(this, options);
7747                 this._latlng = toLatLng(latlng);
7748
7749                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7750
7751                 // @section
7752                 // @aka Circle options
7753                 // @option radius: Number; Radius of the circle, in meters.
7754                 this._mRadius = this.options.radius;
7755         },
7756
7757         // @method setRadius(radius: Number): this
7758         // Sets the radius of a circle. Units are in meters.
7759         setRadius: function (radius) {
7760                 this._mRadius = radius;
7761                 return this.redraw();
7762         },
7763
7764         // @method getRadius(): Number
7765         // Returns the current radius of a circle. Units are in meters.
7766         getRadius: function () {
7767                 return this._mRadius;
7768         },
7769
7770         // @method getBounds(): LatLngBounds
7771         // Returns the `LatLngBounds` of the path.
7772         getBounds: function () {
7773                 var half = [this._radius, this._radiusY || this._radius];
7774
7775                 return new LatLngBounds(
7776                         this._map.layerPointToLatLng(this._point.subtract(half)),
7777                         this._map.layerPointToLatLng(this._point.add(half)));
7778         },
7779
7780         setStyle: Path.prototype.setStyle,
7781
7782         _project: function () {
7783
7784                 var lng = this._latlng.lng,
7785                     lat = this._latlng.lat,
7786                     map = this._map,
7787                     crs = map.options.crs;
7788
7789                 if (crs.distance === Earth.distance) {
7790                         var d = Math.PI / 180,
7791                             latR = (this._mRadius / Earth.R) / d,
7792                             top = map.project([lat + latR, lng]),
7793                             bottom = map.project([lat - latR, lng]),
7794                             p = top.add(bottom).divideBy(2),
7795                             lat2 = map.unproject(p).lat,
7796                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
7797                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
7798
7799                         if (isNaN(lngR) || lngR === 0) {
7800                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
7801                         }
7802
7803                         this._point = p.subtract(map.getPixelOrigin());
7804                         this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
7805                         this._radiusY = Math.max(Math.round(p.y - top.y), 1);
7806
7807                 } else {
7808                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
7809
7810                         this._point = map.latLngToLayerPoint(this._latlng);
7811                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
7812                 }
7813
7814                 this._updateBounds();
7815         }
7816 });
7817
7818 // @factory L.circle(latlng: LatLng, options?: Circle options)
7819 // Instantiates a circle object given a geographical point, and an options object
7820 // which contains the circle radius.
7821 // @alternative
7822 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
7823 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
7824 // Do not use in new applications or plugins.
7825 function circle(latlng, options, legacyOptions) {
7826         return new Circle(latlng, options, legacyOptions);
7827 }
7828
7829 /*
7830  * @class Polyline
7831  * @aka L.Polyline
7832  * @inherits Path
7833  *
7834  * A class for drawing polyline overlays on a map. Extends `Path`.
7835  *
7836  * @example
7837  *
7838  * ```js
7839  * // create a red polyline from an array of LatLng points
7840  * var latlngs = [
7841  *      [45.51, -122.68],
7842  *      [37.77, -122.43],
7843  *      [34.04, -118.2]
7844  * ];
7845  *
7846  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7847  *
7848  * // zoom the map to the polyline
7849  * map.fitBounds(polyline.getBounds());
7850  * ```
7851  *
7852  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7853  *
7854  * ```js
7855  * // create a red polyline from an array of arrays of LatLng points
7856  * var latlngs = [
7857  *      [[45.51, -122.68],
7858  *       [37.77, -122.43],
7859  *       [34.04, -118.2]],
7860  *      [[40.78, -73.91],
7861  *       [41.83, -87.62],
7862  *       [32.76, -96.72]]
7863  * ];
7864  * ```
7865  */
7866
7867
7868 var Polyline = Path.extend({
7869
7870         // @section
7871         // @aka Polyline options
7872         options: {
7873                 // @option smoothFactor: Number = 1.0
7874                 // How much to simplify the polyline on each zoom level. More means
7875                 // better performance and smoother look, and less means more accurate representation.
7876                 smoothFactor: 1.0,
7877
7878                 // @option noClip: Boolean = false
7879                 // Disable polyline clipping.
7880                 noClip: false
7881         },
7882
7883         initialize: function (latlngs, options) {
7884                 setOptions(this, options);
7885                 this._setLatLngs(latlngs);
7886         },
7887
7888         // @method getLatLngs(): LatLng[]
7889         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
7890         getLatLngs: function () {
7891                 return this._latlngs;
7892         },
7893
7894         // @method setLatLngs(latlngs: LatLng[]): this
7895         // Replaces all the points in the polyline with the given array of geographical points.
7896         setLatLngs: function (latlngs) {
7897                 this._setLatLngs(latlngs);
7898                 return this.redraw();
7899         },
7900
7901         // @method isEmpty(): Boolean
7902         // Returns `true` if the Polyline has no LatLngs.
7903         isEmpty: function () {
7904                 return !this._latlngs.length;
7905         },
7906
7907         closestLayerPoint: function (p) {
7908                 var minDistance = Infinity,
7909                     minPoint = null,
7910                     closest = _sqClosestPointOnSegment,
7911                     p1, p2;
7912
7913                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
7914                         var points = this._parts[j];
7915
7916                         for (var i = 1, len = points.length; i < len; i++) {
7917                                 p1 = points[i - 1];
7918                                 p2 = points[i];
7919
7920                                 var sqDist = closest(p, p1, p2, true);
7921
7922                                 if (sqDist < minDistance) {
7923                                         minDistance = sqDist;
7924                                         minPoint = closest(p, p1, p2);
7925                                 }
7926                         }
7927                 }
7928                 if (minPoint) {
7929                         minPoint.distance = Math.sqrt(minDistance);
7930                 }
7931                 return minPoint;
7932         },
7933
7934         // @method getCenter(): LatLng
7935         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
7936         getCenter: function () {
7937                 // throws error when not yet added to map as this center calculation requires projected coordinates
7938                 if (!this._map) {
7939                         throw new Error('Must add layer to map before using getCenter()');
7940                 }
7941
7942                 var i, halfDist, segDist, dist, p1, p2, ratio,
7943                     points = this._rings[0],
7944                     len = points.length;
7945
7946                 if (!len) { return null; }
7947
7948                 // polyline centroid algorithm; only uses the first ring if there are multiple
7949
7950                 for (i = 0, halfDist = 0; i < len - 1; i++) {
7951                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
7952                 }
7953
7954                 // The line is so small in the current view that all points are on the same pixel.
7955                 if (halfDist === 0) {
7956                         return this._map.layerPointToLatLng(points[0]);
7957                 }
7958
7959                 for (i = 0, dist = 0; i < len - 1; i++) {
7960                         p1 = points[i];
7961                         p2 = points[i + 1];
7962                         segDist = p1.distanceTo(p2);
7963                         dist += segDist;
7964
7965                         if (dist > halfDist) {
7966                                 ratio = (dist - halfDist) / segDist;
7967                                 return this._map.layerPointToLatLng([
7968                                         p2.x - ratio * (p2.x - p1.x),
7969                                         p2.y - ratio * (p2.y - p1.y)
7970                                 ]);
7971                         }
7972                 }
7973         },
7974
7975         // @method getBounds(): LatLngBounds
7976         // Returns the `LatLngBounds` of the path.
7977         getBounds: function () {
7978                 return this._bounds;
7979         },
7980
7981         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
7982         // Adds a given point to the polyline. By default, adds to the first ring of
7983         // the polyline in case of a multi-polyline, but can be overridden by passing
7984         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
7985         addLatLng: function (latlng, latlngs) {
7986                 latlngs = latlngs || this._defaultShape();
7987                 latlng = toLatLng(latlng);
7988                 latlngs.push(latlng);
7989                 this._bounds.extend(latlng);
7990                 return this.redraw();
7991         },
7992
7993         _setLatLngs: function (latlngs) {
7994                 this._bounds = new LatLngBounds();
7995                 this._latlngs = this._convertLatLngs(latlngs);
7996         },
7997
7998         _defaultShape: function () {
7999                 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8000         },
8001
8002         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8003         _convertLatLngs: function (latlngs) {
8004                 var result = [],
8005                     flat = isFlat(latlngs);
8006
8007                 for (var i = 0, len = latlngs.length; i < len; i++) {
8008                         if (flat) {
8009                                 result[i] = toLatLng(latlngs[i]);
8010                                 this._bounds.extend(result[i]);
8011                         } else {
8012                                 result[i] = this._convertLatLngs(latlngs[i]);
8013                         }
8014                 }
8015
8016                 return result;
8017         },
8018
8019         _project: function () {
8020                 var pxBounds = new Bounds();
8021                 this._rings = [];
8022                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8023
8024                 var w = this._clickTolerance(),
8025                     p = new Point(w, w);
8026
8027                 if (this._bounds.isValid() && pxBounds.isValid()) {
8028                         pxBounds.min._subtract(p);
8029                         pxBounds.max._add(p);
8030                         this._pxBounds = pxBounds;
8031                 }
8032         },
8033
8034         // recursively turns latlngs into a set of rings with projected coordinates
8035         _projectLatlngs: function (latlngs, result, projectedBounds) {
8036                 var flat = latlngs[0] instanceof LatLng,
8037                     len = latlngs.length,
8038                     i, ring;
8039
8040                 if (flat) {
8041                         ring = [];
8042                         for (i = 0; i < len; i++) {
8043                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8044                                 projectedBounds.extend(ring[i]);
8045                         }
8046                         result.push(ring);
8047                 } else {
8048                         for (i = 0; i < len; i++) {
8049                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8050                         }
8051                 }
8052         },
8053
8054         // clip polyline by renderer bounds so that we have less to render for performance
8055         _clipPoints: function () {
8056                 var bounds = this._renderer._bounds;
8057
8058                 this._parts = [];
8059                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8060                         return;
8061                 }
8062
8063                 if (this.options.noClip) {
8064                         this._parts = this._rings;
8065                         return;
8066                 }
8067
8068                 var parts = this._parts,
8069                     i, j, k, len, len2, segment, points;
8070
8071                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8072                         points = this._rings[i];
8073
8074                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8075                                 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8076
8077                                 if (!segment) { continue; }
8078
8079                                 parts[k] = parts[k] || [];
8080                                 parts[k].push(segment[0]);
8081
8082                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8083                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8084                                         parts[k].push(segment[1]);
8085                                         k++;
8086                                 }
8087                         }
8088                 }
8089         },
8090
8091         // simplify each clipped part of the polyline for performance
8092         _simplifyPoints: function () {
8093                 var parts = this._parts,
8094                     tolerance = this.options.smoothFactor;
8095
8096                 for (var i = 0, len = parts.length; i < len; i++) {
8097                         parts[i] = simplify(parts[i], tolerance);
8098                 }
8099         },
8100
8101         _update: function () {
8102                 if (!this._map) { return; }
8103
8104                 this._clipPoints();
8105                 this._simplifyPoints();
8106                 this._updatePath();
8107         },
8108
8109         _updatePath: function () {
8110                 this._renderer._updatePoly(this);
8111         },
8112
8113         // Needed by the `Canvas` renderer for interactivity
8114         _containsPoint: function (p, closed) {
8115                 var i, j, k, len, len2, part,
8116                     w = this._clickTolerance();
8117
8118                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8119
8120                 // hit detection for polylines
8121                 for (i = 0, len = this._parts.length; i < len; i++) {
8122                         part = this._parts[i];
8123
8124                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8125                                 if (!closed && (j === 0)) { continue; }
8126
8127                                 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8128                                         return true;
8129                                 }
8130                         }
8131                 }
8132                 return false;
8133         }
8134 });
8135
8136 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8137 // Instantiates a polyline object given an array of geographical points and
8138 // optionally an options object. You can create a `Polyline` object with
8139 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8140 // of geographic points.
8141 function polyline(latlngs, options) {
8142         return new Polyline(latlngs, options);
8143 }
8144
8145 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8146 Polyline._flat = _flat;
8147
8148 /*
8149  * @class Polygon
8150  * @aka L.Polygon
8151  * @inherits Polyline
8152  *
8153  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8154  *
8155  * 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.
8156  *
8157  *
8158  * @example
8159  *
8160  * ```js
8161  * // create a red polygon from an array of LatLng points
8162  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8163  *
8164  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8165  *
8166  * // zoom the map to the polygon
8167  * map.fitBounds(polygon.getBounds());
8168  * ```
8169  *
8170  * 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:
8171  *
8172  * ```js
8173  * var latlngs = [
8174  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8175  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8176  * ];
8177  * ```
8178  *
8179  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8180  *
8181  * ```js
8182  * var latlngs = [
8183  *   [ // first polygon
8184  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8185  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8186  *   ],
8187  *   [ // second polygon
8188  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8189  *   ]
8190  * ];
8191  * ```
8192  */
8193
8194 var Polygon = Polyline.extend({
8195
8196         options: {
8197                 fill: true
8198         },
8199
8200         isEmpty: function () {
8201                 return !this._latlngs.length || !this._latlngs[0].length;
8202         },
8203
8204         getCenter: function () {
8205                 // throws error when not yet added to map as this center calculation requires projected coordinates
8206                 if (!this._map) {
8207                         throw new Error('Must add layer to map before using getCenter()');
8208                 }
8209
8210                 var i, j, p1, p2, f, area, x, y, center,
8211                     points = this._rings[0],
8212                     len = points.length;
8213
8214                 if (!len) { return null; }
8215
8216                 // polygon centroid algorithm; only uses the first ring if there are multiple
8217
8218                 area = x = y = 0;
8219
8220                 for (i = 0, j = len - 1; i < len; j = i++) {
8221                         p1 = points[i];
8222                         p2 = points[j];
8223
8224                         f = p1.y * p2.x - p2.y * p1.x;
8225                         x += (p1.x + p2.x) * f;
8226                         y += (p1.y + p2.y) * f;
8227                         area += f * 3;
8228                 }
8229
8230                 if (area === 0) {
8231                         // Polygon is so small that all points are on same pixel.
8232                         center = points[0];
8233                 } else {
8234                         center = [x / area, y / area];
8235                 }
8236                 return this._map.layerPointToLatLng(center);
8237         },
8238
8239         _convertLatLngs: function (latlngs) {
8240                 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8241                     len = result.length;
8242
8243                 // remove last point if it equals first one
8244                 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8245                         result.pop();
8246                 }
8247                 return result;
8248         },
8249
8250         _setLatLngs: function (latlngs) {
8251                 Polyline.prototype._setLatLngs.call(this, latlngs);
8252                 if (isFlat(this._latlngs)) {
8253                         this._latlngs = [this._latlngs];
8254                 }
8255         },
8256
8257         _defaultShape: function () {
8258                 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8259         },
8260
8261         _clipPoints: function () {
8262                 // polygons need a different clipping algorithm so we redefine that
8263
8264                 var bounds = this._renderer._bounds,
8265                     w = this.options.weight,
8266                     p = new Point(w, w);
8267
8268                 // increase clip padding by stroke width to avoid stroke on clip edges
8269                 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8270
8271                 this._parts = [];
8272                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8273                         return;
8274                 }
8275
8276                 if (this.options.noClip) {
8277                         this._parts = this._rings;
8278                         return;
8279                 }
8280
8281                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8282                         clipped = clipPolygon(this._rings[i], bounds, true);
8283                         if (clipped.length) {
8284                                 this._parts.push(clipped);
8285                         }
8286                 }
8287         },
8288
8289         _updatePath: function () {
8290                 this._renderer._updatePoly(this, true);
8291         },
8292
8293         // Needed by the `Canvas` renderer for interactivity
8294         _containsPoint: function (p) {
8295                 var inside = false,
8296                     part, p1, p2, i, j, k, len, len2;
8297
8298                 if (!this._pxBounds.contains(p)) { return false; }
8299
8300                 // ray casting algorithm for detecting if point is in polygon
8301                 for (i = 0, len = this._parts.length; i < len; i++) {
8302                         part = this._parts[i];
8303
8304                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8305                                 p1 = part[j];
8306                                 p2 = part[k];
8307
8308                                 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)) {
8309                                         inside = !inside;
8310                                 }
8311                         }
8312                 }
8313
8314                 // also check if it's on polygon stroke
8315                 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8316         }
8317
8318 });
8319
8320
8321 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8322 function polygon(latlngs, options) {
8323         return new Polygon(latlngs, options);
8324 }
8325
8326 /*\r
8327  * @class GeoJSON\r
8328  * @aka L.GeoJSON\r
8329  * @inherits FeatureGroup\r
8330  *\r
8331  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse\r
8332  * GeoJSON data and display it on the map. Extends `FeatureGroup`.\r
8333  *\r
8334  * @example\r
8335  *\r
8336  * ```js\r
8337  * L.geoJSON(data, {\r
8338  *      style: function (feature) {\r
8339  *              return {color: feature.properties.color};\r
8340  *      }\r
8341  * }).bindPopup(function (layer) {\r
8342  *      return layer.feature.properties.description;\r
8343  * }).addTo(map);\r
8344  * ```\r
8345  */\r
8346 \r
8347 var GeoJSON = FeatureGroup.extend({\r
8348 \r
8349         /* @section\r
8350          * @aka GeoJSON options\r
8351          *\r
8352          * @option pointToLayer: Function = *\r
8353          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally\r
8354          * called when data is added, passing the GeoJSON point feature and its `LatLng`.\r
8355          * The default is to spawn a default `Marker`:\r
8356          * ```js\r
8357          * function(geoJsonPoint, latlng) {\r
8358          *      return L.marker(latlng);\r
8359          * }\r
8360          * ```\r
8361          *\r
8362          * @option style: Function = *\r
8363          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,\r
8364          * called internally when data is added.\r
8365          * The default value is to not override any defaults:\r
8366          * ```js\r
8367          * function (geoJsonFeature) {\r
8368          *      return {}\r
8369          * }\r
8370          * ```\r
8371          *\r
8372          * @option onEachFeature: Function = *\r
8373          * A `Function` that will be called once for each created `Feature`, after it has\r
8374          * been created and styled. Useful for attaching events and popups to features.\r
8375          * The default is to do nothing with the newly created layers:\r
8376          * ```js\r
8377          * function (feature, layer) {}\r
8378          * ```\r
8379          *\r
8380          * @option filter: Function = *\r
8381          * A `Function` that will be used to decide whether to include a feature or not.\r
8382          * The default is to include all features:\r
8383          * ```js\r
8384          * function (geoJsonFeature) {\r
8385          *      return true;\r
8386          * }\r
8387          * ```\r
8388          * Note: dynamically changing the `filter` option will have effect only on newly\r
8389          * added data. It will _not_ re-evaluate already included features.\r
8390          *\r
8391          * @option coordsToLatLng: Function = *\r
8392          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.\r
8393          * The default is the `coordsToLatLng` static method.\r
8394          */\r
8395 \r
8396         initialize: function (geojson, options) {\r
8397                 setOptions(this, options);\r
8398 \r
8399                 this._layers = {};\r
8400 \r
8401                 if (geojson) {\r
8402                         this.addData(geojson);\r
8403                 }\r
8404         },\r
8405 \r
8406         // @method addData( <GeoJSON> data ): this\r
8407         // Adds a GeoJSON object to the layer.\r
8408         addData: function (geojson) {\r
8409                 var features = isArray(geojson) ? geojson : geojson.features,\r
8410                     i, len, feature;\r
8411 \r
8412                 if (features) {\r
8413                         for (i = 0, len = features.length; i < len; i++) {\r
8414                                 // only add this if geometry or geometries are set and not null\r
8415                                 feature = features[i];\r
8416                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {\r
8417                                         this.addData(feature);\r
8418                                 }\r
8419                         }\r
8420                         return this;\r
8421                 }\r
8422 \r
8423                 var options = this.options;\r
8424 \r
8425                 if (options.filter && !options.filter(geojson)) { return this; }\r
8426 \r
8427                 var layer = geometryToLayer(geojson, options);\r
8428                 if (!layer) {\r
8429                         return this;\r
8430                 }\r
8431                 layer.feature = asFeature(geojson);\r
8432 \r
8433                 layer.defaultOptions = layer.options;\r
8434                 this.resetStyle(layer);\r
8435 \r
8436                 if (options.onEachFeature) {\r
8437                         options.onEachFeature(geojson, layer);\r
8438                 }\r
8439 \r
8440                 return this.addLayer(layer);\r
8441         },\r
8442 \r
8443         // @method resetStyle( <Path> layer ): this\r
8444         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.\r
8445         resetStyle: function (layer) {\r
8446                 // reset any custom styles\r
8447                 layer.options = extend({}, layer.defaultOptions);\r
8448                 this._setLayerStyle(layer, this.options.style);\r
8449                 return this;\r
8450         },\r
8451 \r
8452         // @method setStyle( <Function> style ): this\r
8453         // Changes styles of GeoJSON vector layers with the given style function.\r
8454         setStyle: function (style) {\r
8455                 return this.eachLayer(function (layer) {\r
8456                         this._setLayerStyle(layer, style);\r
8457                 }, this);\r
8458         },\r
8459 \r
8460         _setLayerStyle: function (layer, style) {\r
8461                 if (typeof style === 'function') {\r
8462                         style = style(layer.feature);\r
8463                 }\r
8464                 if (layer.setStyle) {\r
8465                         layer.setStyle(style);\r
8466                 }\r
8467         }\r
8468 });\r
8469 \r
8470 // @section\r
8471 // There are several static functions which can be called without instantiating L.GeoJSON:\r
8472 \r
8473 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer\r
8474 // Creates a `Layer` from a given GeoJSON feature. Can use a custom\r
8475 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)\r
8476 // functions if provided as options.\r
8477 function geometryToLayer(geojson, options) {\r
8478 \r
8479         var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,\r
8480             coords = geometry ? geometry.coordinates : null,\r
8481             layers = [],\r
8482             pointToLayer = options && options.pointToLayer,\r
8483             _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,\r
8484             latlng, latlngs, i, len;\r
8485 \r
8486         if (!coords && !geometry) {\r
8487                 return null;\r
8488         }\r
8489 \r
8490         switch (geometry.type) {\r
8491         case 'Point':\r
8492                 latlng = _coordsToLatLng(coords);\r
8493                 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);\r
8494 \r
8495         case 'MultiPoint':\r
8496                 for (i = 0, len = coords.length; i < len; i++) {\r
8497                         latlng = _coordsToLatLng(coords[i]);\r
8498                         layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));\r
8499                 }\r
8500                 return new FeatureGroup(layers);\r
8501 \r
8502         case 'LineString':\r
8503         case 'MultiLineString':\r
8504                 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);\r
8505                 return new Polyline(latlngs, options);\r
8506 \r
8507         case 'Polygon':\r
8508         case 'MultiPolygon':\r
8509                 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);\r
8510                 return new Polygon(latlngs, options);\r
8511 \r
8512         case 'GeometryCollection':\r
8513                 for (i = 0, len = geometry.geometries.length; i < len; i++) {\r
8514                         var layer = geometryToLayer({\r
8515                                 geometry: geometry.geometries[i],\r
8516                                 type: 'Feature',\r
8517                                 properties: geojson.properties\r
8518                         }, options);\r
8519 \r
8520                         if (layer) {\r
8521                                 layers.push(layer);\r
8522                         }\r
8523                 }\r
8524                 return new FeatureGroup(layers);\r
8525 \r
8526         default:\r
8527                 throw new Error('Invalid GeoJSON object.');\r
8528         }\r
8529 }\r
8530 \r
8531 // @function coordsToLatLng(coords: Array): LatLng\r
8532 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)\r
8533 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.\r
8534 function coordsToLatLng(coords) {\r
8535         return new LatLng(coords[1], coords[0], coords[2]);\r
8536 }\r
8537 \r
8538 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array\r
8539 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.\r
8540 // `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
8541 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.\r
8542 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {\r
8543         var latlngs = [];\r
8544 \r
8545         for (var i = 0, len = coords.length, latlng; i < len; i++) {\r
8546                 latlng = levelsDeep ?\r
8547                                 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :\r
8548                                 (_coordsToLatLng || coordsToLatLng)(coords[i]);\r
8549 \r
8550                 latlngs.push(latlng);\r
8551         }\r
8552 \r
8553         return latlngs;\r
8554 }\r
8555 \r
8556 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array\r
8557 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)\r
8558 function latLngToCoords(latlng, precision) {\r
8559         precision = typeof precision === 'number' ? precision : 6;\r
8560         return latlng.alt !== undefined ?\r
8561                         [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :\r
8562                         [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];\r
8563 }\r
8564 \r
8565 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array\r
8566 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)\r
8567 // `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
8568 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {\r
8569         var coords = [];\r
8570 \r
8571         for (var i = 0, len = latlngs.length; i < len; i++) {\r
8572                 coords.push(levelsDeep ?\r
8573                         latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :\r
8574                         latLngToCoords(latlngs[i], precision));\r
8575         }\r
8576 \r
8577         if (!levelsDeep && closed) {\r
8578                 coords.push(coords[0]);\r
8579         }\r
8580 \r
8581         return coords;\r
8582 }\r
8583 \r
8584 function getFeature(layer, newGeometry) {\r
8585         return layer.feature ?\r
8586                         extend({}, layer.feature, {geometry: newGeometry}) :\r
8587                         asFeature(newGeometry);\r
8588 }\r
8589 \r
8590 // @function asFeature(geojson: Object): Object\r
8591 // Normalize GeoJSON geometries/features into GeoJSON features.\r
8592 function asFeature(geojson) {\r
8593         if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {\r
8594                 return geojson;\r
8595         }\r
8596 \r
8597         return {\r
8598                 type: 'Feature',\r
8599                 properties: {},\r
8600                 geometry: geojson\r
8601         };\r
8602 }\r
8603 \r
8604 var PointToGeoJSON = {\r
8605         toGeoJSON: function (precision) {\r
8606                 return getFeature(this, {\r
8607                         type: 'Point',\r
8608                         coordinates: latLngToCoords(this.getLatLng(), precision)\r
8609                 });\r
8610         }\r
8611 };\r
8612 \r
8613 // @namespace Marker\r
8614 // @method toGeoJSON(): Object\r
8615 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).\r
8616 Marker.include(PointToGeoJSON);\r
8617 \r
8618 // @namespace CircleMarker\r
8619 // @method toGeoJSON(): Object\r
8620 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).\r
8621 Circle.include(PointToGeoJSON);\r
8622 CircleMarker.include(PointToGeoJSON);\r
8623 \r
8624 \r
8625 // @namespace Polyline\r
8626 // @method toGeoJSON(): Object\r
8627 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).\r
8628 Polyline.include({\r
8629         toGeoJSON: function (precision) {\r
8630                 var multi = !isFlat(this._latlngs);\r
8631 \r
8632                 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);\r
8633 \r
8634                 return getFeature(this, {\r
8635                         type: (multi ? 'Multi' : '') + 'LineString',\r
8636                         coordinates: coords\r
8637                 });\r
8638         }\r
8639 });\r
8640 \r
8641 // @namespace Polygon\r
8642 // @method toGeoJSON(): Object\r
8643 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).\r
8644 Polygon.include({\r
8645         toGeoJSON: function (precision) {\r
8646                 var holes = !isFlat(this._latlngs),\r
8647                     multi = holes && !isFlat(this._latlngs[0]);\r
8648 \r
8649                 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);\r
8650 \r
8651                 if (!holes) {\r
8652                         coords = [coords];\r
8653                 }\r
8654 \r
8655                 return getFeature(this, {\r
8656                         type: (multi ? 'Multi' : '') + 'Polygon',\r
8657                         coordinates: coords\r
8658                 });\r
8659         }\r
8660 });\r
8661 \r
8662 \r
8663 // @namespace LayerGroup\r
8664 LayerGroup.include({\r
8665         toMultiPoint: function (precision) {\r
8666                 var coords = [];\r
8667 \r
8668                 this.eachLayer(function (layer) {\r
8669                         coords.push(layer.toGeoJSON(precision).geometry.coordinates);\r
8670                 });\r
8671 \r
8672                 return getFeature(this, {\r
8673                         type: 'MultiPoint',\r
8674                         coordinates: coords\r
8675                 });\r
8676         },\r
8677 \r
8678         // @method toGeoJSON(): Object\r
8679         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).\r
8680         toGeoJSON: function (precision) {\r
8681 \r
8682                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;\r
8683 \r
8684                 if (type === 'MultiPoint') {\r
8685                         return this.toMultiPoint(precision);\r
8686                 }\r
8687 \r
8688                 var isGeometryCollection = type === 'GeometryCollection',\r
8689                     jsons = [];\r
8690 \r
8691                 this.eachLayer(function (layer) {\r
8692                         if (layer.toGeoJSON) {\r
8693                                 var json = layer.toGeoJSON(precision);\r
8694                                 if (isGeometryCollection) {\r
8695                                         jsons.push(json.geometry);\r
8696                                 } else {\r
8697                                         var feature = asFeature(json);\r
8698                                         // Squash nested feature collections\r
8699                                         if (feature.type === 'FeatureCollection') {\r
8700                                                 jsons.push.apply(jsons, feature.features);\r
8701                                         } else {\r
8702                                                 jsons.push(feature);\r
8703                                         }\r
8704                                 }\r
8705                         }\r
8706                 });\r
8707 \r
8708                 if (isGeometryCollection) {\r
8709                         return getFeature(this, {\r
8710                                 geometries: jsons,\r
8711                                 type: 'GeometryCollection'\r
8712                         });\r
8713                 }\r
8714 \r
8715                 return {\r
8716                         type: 'FeatureCollection',\r
8717                         features: jsons\r
8718                 };\r
8719         }\r
8720 });\r
8721 \r
8722 // @namespace GeoJSON\r
8723 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)\r
8724 // Creates a GeoJSON layer. Optionally accepts an object in\r
8725 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map\r
8726 // (you can alternatively add it later with `addData` method) and an `options` object.\r
8727 function geoJSON(geojson, options) {\r
8728         return new GeoJSON(geojson, options);\r
8729 }\r
8730 \r
8731 // Backward compatibility.\r
8732 var geoJson = geoJSON;
8733
8734 /*\r
8735  * @class ImageOverlay\r
8736  * @aka L.ImageOverlay\r
8737  * @inherits Interactive layer\r
8738  *\r
8739  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.\r
8740  *\r
8741  * @example\r
8742  *\r
8743  * ```js\r
8744  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',\r
8745  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];\r
8746  * L.imageOverlay(imageUrl, imageBounds).addTo(map);\r
8747  * ```\r
8748  */\r
8749 \r
8750 var ImageOverlay = Layer.extend({\r
8751 \r
8752         // @section\r
8753         // @aka ImageOverlay options\r
8754         options: {\r
8755                 // @option opacity: Number = 1.0\r
8756                 // The opacity of the image overlay.\r
8757                 opacity: 1,\r
8758 \r
8759                 // @option alt: String = ''\r
8760                 // Text for the `alt` attribute of the image (useful for accessibility).\r
8761                 alt: '',\r
8762 \r
8763                 // @option interactive: Boolean = false\r
8764                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.\r
8765                 interactive: false,\r
8766 \r
8767                 // @option crossOrigin: Boolean = false\r
8768                 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.\r
8769                 crossOrigin: false,\r
8770 \r
8771                 // @option errorOverlayUrl: String = ''\r
8772                 // URL to the overlay image to show in place of the overlay that failed to load.\r
8773                 errorOverlayUrl: '',\r
8774 \r
8775                 // @option zIndex: Number = 1\r
8776                 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.\r
8777                 zIndex: 1,\r
8778 \r
8779                 // @option className: String = ''\r
8780                 // A custom class name to assign to the image. Empty by default.\r
8781                 className: '',\r
8782         },\r
8783 \r
8784         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)\r
8785                 this._url = url;\r
8786                 this._bounds = toLatLngBounds(bounds);\r
8787 \r
8788                 setOptions(this, options);\r
8789         },\r
8790 \r
8791         onAdd: function () {\r
8792                 if (!this._image) {\r
8793                         this._initImage();\r
8794 \r
8795                         if (this.options.opacity < 1) {\r
8796                                 this._updateOpacity();\r
8797                         }\r
8798                 }\r
8799 \r
8800                 if (this.options.interactive) {\r
8801                         addClass(this._image, 'leaflet-interactive');\r
8802                         this.addInteractiveTarget(this._image);\r
8803                 }\r
8804 \r
8805                 this.getPane().appendChild(this._image);\r
8806                 this._reset();\r
8807         },\r
8808 \r
8809         onRemove: function () {\r
8810                 remove(this._image);\r
8811                 if (this.options.interactive) {\r
8812                         this.removeInteractiveTarget(this._image);\r
8813                 }\r
8814         },\r
8815 \r
8816         // @method setOpacity(opacity: Number): this\r
8817         // Sets the opacity of the overlay.\r
8818         setOpacity: function (opacity) {\r
8819                 this.options.opacity = opacity;\r
8820 \r
8821                 if (this._image) {\r
8822                         this._updateOpacity();\r
8823                 }\r
8824                 return this;\r
8825         },\r
8826 \r
8827         setStyle: function (styleOpts) {\r
8828                 if (styleOpts.opacity) {\r
8829                         this.setOpacity(styleOpts.opacity);\r
8830                 }\r
8831                 return this;\r
8832         },\r
8833 \r
8834         // @method bringToFront(): this\r
8835         // Brings the layer to the top of all overlays.\r
8836         bringToFront: function () {\r
8837                 if (this._map) {\r
8838                         toFront(this._image);\r
8839                 }\r
8840                 return this;\r
8841         },\r
8842 \r
8843         // @method bringToBack(): this\r
8844         // Brings the layer to the bottom of all overlays.\r
8845         bringToBack: function () {\r
8846                 if (this._map) {\r
8847                         toBack(this._image);\r
8848                 }\r
8849                 return this;\r
8850         },\r
8851 \r
8852         // @method setUrl(url: String): this\r
8853         // Changes the URL of the image.\r
8854         setUrl: function (url) {\r
8855                 this._url = url;\r
8856 \r
8857                 if (this._image) {\r
8858                         this._image.src = url;\r
8859                 }\r
8860                 return this;\r
8861         },\r
8862 \r
8863         // @method setBounds(bounds: LatLngBounds): this\r
8864         // Update the bounds that this ImageOverlay covers\r
8865         setBounds: function (bounds) {\r
8866                 this._bounds = toLatLngBounds(bounds);\r
8867 \r
8868                 if (this._map) {\r
8869                         this._reset();\r
8870                 }\r
8871                 return this;\r
8872         },\r
8873 \r
8874         getEvents: function () {\r
8875                 var events = {\r
8876                         zoom: this._reset,\r
8877                         viewreset: this._reset\r
8878                 };\r
8879 \r
8880                 if (this._zoomAnimated) {\r
8881                         events.zoomanim = this._animateZoom;\r
8882                 }\r
8883 \r
8884                 return events;\r
8885         },\r
8886 \r
8887         // @method: setZIndex(value: Number) : this\r
8888         // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.\r
8889         setZIndex: function (value) {\r
8890                 this.options.zIndex = value;\r
8891                 this._updateZIndex();\r
8892                 return this;\r
8893         },\r
8894 \r
8895         // @method getBounds(): LatLngBounds\r
8896         // Get the bounds that this ImageOverlay covers\r
8897         getBounds: function () {\r
8898                 return this._bounds;\r
8899         },\r
8900 \r
8901         // @method getElement(): HTMLElement\r
8902         // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)\r
8903         // used by this overlay.\r
8904         getElement: function () {\r
8905                 return this._image;\r
8906         },\r
8907 \r
8908         _initImage: function () {\r
8909                 var img = this._image = create$1('img',\r
8910                                 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '') +\r
8911                                  (this.options.className || ''));\r
8912 \r
8913                 img.onselectstart = falseFn;\r
8914                 img.onmousemove = falseFn;\r
8915 \r
8916                 // @event load: Event\r
8917                 // Fired when the ImageOverlay layer has loaded its image\r
8918                 img.onload = bind(this.fire, this, 'load');\r
8919                 img.onerror = bind(this._overlayOnError, this, 'error');\r
8920 \r
8921                 if (this.options.crossOrigin) {\r
8922                         img.crossOrigin = '';\r
8923                 }\r
8924 \r
8925                 if (this.options.zIndex) {\r
8926                         this._updateZIndex();\r
8927                 }\r
8928 \r
8929                 img.src = this._url;\r
8930                 img.alt = this.options.alt;\r
8931         },\r
8932 \r
8933         _animateZoom: function (e) {\r
8934                 var scale = this._map.getZoomScale(e.zoom),\r
8935                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;\r
8936 \r
8937                 setTransform(this._image, offset, scale);\r
8938         },\r
8939 \r
8940         _reset: function () {\r
8941                 var image = this._image,\r
8942                     bounds = new Bounds(\r
8943                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),\r
8944                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),\r
8945                     size = bounds.getSize();\r
8946 \r
8947                 setPosition(image, bounds.min);\r
8948 \r
8949                 image.style.width  = size.x + 'px';\r
8950                 image.style.height = size.y + 'px';\r
8951         },\r
8952 \r
8953         _updateOpacity: function () {\r
8954                 setOpacity(this._image, this.options.opacity);\r
8955         },\r
8956 \r
8957         _updateZIndex: function () {\r
8958                 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {\r
8959                         this._image.style.zIndex = this.options.zIndex;\r
8960                 }\r
8961         },\r
8962 \r
8963         _overlayOnError: function () {\r
8964                 // @event error: Event\r
8965                 // Fired when the ImageOverlay layer has loaded its image\r
8966                 this.fire('error');\r
8967 \r
8968                 var errorUrl = this.options.errorOverlayUrl;\r
8969                 if (errorUrl && this._url !== errorUrl) {\r
8970                         this._url = errorUrl;\r
8971                         this._image.src = errorUrl;\r
8972                 }\r
8973         }\r
8974 });\r
8975 \r
8976 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)\r
8977 // Instantiates an image overlay object given the URL of the image and the\r
8978 // geographical bounds it is tied to.\r
8979 var imageOverlay = function (url, bounds, options) {\r
8980         return new ImageOverlay(url, bounds, options);\r
8981 };
8982
8983 /*\r
8984  * @class VideoOverlay\r
8985  * @aka L.VideoOverlay\r
8986  * @inherits ImageOverlay\r
8987  *\r
8988  * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.\r
8989  *\r
8990  * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)\r
8991  * HTML5 element.\r
8992  *\r
8993  * @example\r
8994  *\r
8995  * ```js\r
8996  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',\r
8997  *      videoBounds = [[ 32, -130], [ 13, -100]];\r
8998  * L.VideoOverlay(videoUrl, videoBounds ).addTo(map);\r
8999  * ```\r
9000  */\r
9001 \r
9002 var VideoOverlay = ImageOverlay.extend({\r
9003 \r
9004         // @section\r
9005         // @aka VideoOverlay options\r
9006         options: {\r
9007                 // @option autoplay: Boolean = true\r
9008                 // Whether the video starts playing automatically when loaded.\r
9009                 autoplay: true,\r
9010 \r
9011                 // @option loop: Boolean = true\r
9012                 // Whether the video will loop back to the beginning when played.\r
9013                 loop: true\r
9014         },\r
9015 \r
9016         _initImage: function () {\r
9017                 var wasElementSupplied = this._url.tagName === 'VIDEO';\r
9018                 var vid = this._image = wasElementSupplied ? this._url : create$1('video');\r
9019 \r
9020                 vid.class = vid.class || '';\r
9021                 vid.class += 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '');\r
9022 \r
9023                 vid.onselectstart = falseFn;\r
9024                 vid.onmousemove = falseFn;\r
9025 \r
9026                 // @event load: Event\r
9027                 // Fired when the video has finished loading the first frame\r
9028                 vid.onloadeddata = bind(this.fire, this, 'load');\r
9029 \r
9030                 if (wasElementSupplied) { return; }\r
9031 \r
9032                 if (!isArray(this._url)) { this._url = [this._url]; }\r
9033 \r
9034                 vid.autoplay = !!this.options.autoplay;\r
9035                 vid.loop = !!this.options.loop;\r
9036                 for (var i = 0; i < this._url.length; i++) {\r
9037                         var source = create$1('source');\r
9038                         source.src = this._url[i];\r
9039                         vid.appendChild(source);\r
9040                 }\r
9041         }\r
9042 \r
9043         // @method getElement(): HTMLVideoElement\r
9044         // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)\r
9045         // used by this overlay.\r
9046 });\r
9047 \r
9048 \r
9049 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)\r
9050 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the\r
9051 // geographical bounds it is tied to.\r
9052 \r
9053 function videoOverlay(video, bounds, options) {\r
9054         return new VideoOverlay(video, bounds, options);\r
9055 }
9056
9057 /*\r
9058  * @class DivOverlay\r
9059  * @inherits Layer\r
9060  * @aka L.DivOverlay\r
9061  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.\r
9062  */\r
9063 \r
9064 // @namespace DivOverlay\r
9065 var DivOverlay = Layer.extend({\r
9066 \r
9067         // @section\r
9068         // @aka DivOverlay options\r
9069         options: {\r
9070                 // @option offset: Point = Point(0, 7)\r
9071                 // The offset of the popup position. Useful to control the anchor\r
9072                 // of the popup when opening it on some overlays.\r
9073                 offset: [0, 7],\r
9074 \r
9075                 // @option className: String = ''\r
9076                 // A custom CSS class name to assign to the popup.\r
9077                 className: '',\r
9078 \r
9079                 // @option pane: String = 'popupPane'\r
9080                 // `Map pane` where the popup will be added.\r
9081                 pane: 'popupPane'\r
9082         },\r
9083 \r
9084         initialize: function (options, source) {\r
9085                 setOptions(this, options);\r
9086 \r
9087                 this._source = source;\r
9088         },\r
9089 \r
9090         onAdd: function (map) {\r
9091                 this._zoomAnimated = map._zoomAnimated;\r
9092 \r
9093                 if (!this._container) {\r
9094                         this._initLayout();\r
9095                 }\r
9096 \r
9097                 if (map._fadeAnimated) {\r
9098                         setOpacity(this._container, 0);\r
9099                 }\r
9100 \r
9101                 clearTimeout(this._removeTimeout);\r
9102                 this.getPane().appendChild(this._container);\r
9103                 this.update();\r
9104 \r
9105                 if (map._fadeAnimated) {\r
9106                         setOpacity(this._container, 1);\r
9107                 }\r
9108 \r
9109                 this.bringToFront();\r
9110         },\r
9111 \r
9112         onRemove: function (map) {\r
9113                 if (map._fadeAnimated) {\r
9114                         setOpacity(this._container, 0);\r
9115                         this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);\r
9116                 } else {\r
9117                         remove(this._container);\r
9118                 }\r
9119         },\r
9120 \r
9121         // @namespace Popup\r
9122         // @method getLatLng: LatLng\r
9123         // Returns the geographical point of popup.\r
9124         getLatLng: function () {\r
9125                 return this._latlng;\r
9126         },\r
9127 \r
9128         // @method setLatLng(latlng: LatLng): this\r
9129         // Sets the geographical point where the popup will open.\r
9130         setLatLng: function (latlng) {\r
9131                 this._latlng = toLatLng(latlng);\r
9132                 if (this._map) {\r
9133                         this._updatePosition();\r
9134                         this._adjustPan();\r
9135                 }\r
9136                 return this;\r
9137         },\r
9138 \r
9139         // @method getContent: String|HTMLElement\r
9140         // Returns the content of the popup.\r
9141         getContent: function () {\r
9142                 return this._content;\r
9143         },\r
9144 \r
9145         // @method setContent(htmlContent: String|HTMLElement|Function): this\r
9146         // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.\r
9147         setContent: function (content) {\r
9148                 this._content = content;\r
9149                 this.update();\r
9150                 return this;\r
9151         },\r
9152 \r
9153         // @method getElement: String|HTMLElement\r
9154         // Alias for [getContent()](#popup-getcontent)\r
9155         getElement: function () {\r
9156                 return this._container;\r
9157         },\r
9158 \r
9159         // @method update: null\r
9160         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.\r
9161         update: function () {\r
9162                 if (!this._map) { return; }\r
9163 \r
9164                 this._container.style.visibility = 'hidden';\r
9165 \r
9166                 this._updateContent();\r
9167                 this._updateLayout();\r
9168                 this._updatePosition();\r
9169 \r
9170                 this._container.style.visibility = '';\r
9171 \r
9172                 this._adjustPan();\r
9173         },\r
9174 \r
9175         getEvents: function () {\r
9176                 var events = {\r
9177                         zoom: this._updatePosition,\r
9178                         viewreset: this._updatePosition\r
9179                 };\r
9180 \r
9181                 if (this._zoomAnimated) {\r
9182                         events.zoomanim = this._animateZoom;\r
9183                 }\r
9184                 return events;\r
9185         },\r
9186 \r
9187         // @method isOpen: Boolean\r
9188         // Returns `true` when the popup is visible on the map.\r
9189         isOpen: function () {\r
9190                 return !!this._map && this._map.hasLayer(this);\r
9191         },\r
9192 \r
9193         // @method bringToFront: this\r
9194         // Brings this popup in front of other popups (in the same map pane).\r
9195         bringToFront: function () {\r
9196                 if (this._map) {\r
9197                         toFront(this._container);\r
9198                 }\r
9199                 return this;\r
9200         },\r
9201 \r
9202         // @method bringToBack: this\r
9203         // Brings this popup to the back of other popups (in the same map pane).\r
9204         bringToBack: function () {\r
9205                 if (this._map) {\r
9206                         toBack(this._container);\r
9207                 }\r
9208                 return this;\r
9209         },\r
9210 \r
9211         _updateContent: function () {\r
9212                 if (!this._content) { return; }\r
9213 \r
9214                 var node = this._contentNode;\r
9215                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;\r
9216 \r
9217                 if (typeof content === 'string') {\r
9218                         node.innerHTML = content;\r
9219                 } else {\r
9220                         while (node.hasChildNodes()) {\r
9221                                 node.removeChild(node.firstChild);\r
9222                         }\r
9223                         node.appendChild(content);\r
9224                 }\r
9225                 this.fire('contentupdate');\r
9226         },\r
9227 \r
9228         _updatePosition: function () {\r
9229                 if (!this._map) { return; }\r
9230 \r
9231                 var pos = this._map.latLngToLayerPoint(this._latlng),\r
9232                     offset = toPoint(this.options.offset),\r
9233                     anchor = this._getAnchor();\r
9234 \r
9235                 if (this._zoomAnimated) {\r
9236                         setPosition(this._container, pos.add(anchor));\r
9237                 } else {\r
9238                         offset = offset.add(pos).add(anchor);\r
9239                 }\r
9240 \r
9241                 var bottom = this._containerBottom = -offset.y,\r
9242                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;\r
9243 \r
9244                 // bottom position the popup in case the height of the popup changes (images loading etc)\r
9245                 this._container.style.bottom = bottom + 'px';\r
9246                 this._container.style.left = left + 'px';\r
9247         },\r
9248 \r
9249         _getAnchor: function () {\r
9250                 return [0, 0];\r
9251         }\r
9252 \r
9253 });
9254
9255 /*\r
9256  * @class Popup\r
9257  * @inherits DivOverlay\r
9258  * @aka L.Popup\r
9259  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to\r
9260  * open popups while making sure that only one popup is open at one time\r
9261  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.\r
9262  *\r
9263  * @example\r
9264  *\r
9265  * If you want to just bind a popup to marker click and then open it, it's really easy:\r
9266  *\r
9267  * ```js\r
9268  * marker.bindPopup(popupContent).openPopup();\r
9269  * ```\r
9270  * Path overlays like polylines also have a `bindPopup` method.\r
9271  * Here's a more complicated way to open a popup on a map:\r
9272  *\r
9273  * ```js\r
9274  * var popup = L.popup()\r
9275  *      .setLatLng(latlng)\r
9276  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')\r
9277  *      .openOn(map);\r
9278  * ```\r
9279  */\r
9280 \r
9281 \r
9282 // @namespace Popup\r
9283 var Popup = DivOverlay.extend({\r
9284 \r
9285         // @section\r
9286         // @aka Popup options\r
9287         options: {\r
9288                 // @option maxWidth: Number = 300\r
9289                 // Max width of the popup, in pixels.\r
9290                 maxWidth: 300,\r
9291 \r
9292                 // @option minWidth: Number = 50\r
9293                 // Min width of the popup, in pixels.\r
9294                 minWidth: 50,\r
9295 \r
9296                 // @option maxHeight: Number = null\r
9297                 // If set, creates a scrollable container of the given height\r
9298                 // inside a popup if its content exceeds it.\r
9299                 maxHeight: null,\r
9300 \r
9301                 // @option autoPan: Boolean = true\r
9302                 // Set it to `false` if you don't want the map to do panning animation\r
9303                 // to fit the opened popup.\r
9304                 autoPan: true,\r
9305 \r
9306                 // @option autoPanPaddingTopLeft: Point = null\r
9307                 // The margin between the popup and the top left corner of the map\r
9308                 // view after autopanning was performed.\r
9309                 autoPanPaddingTopLeft: null,\r
9310 \r
9311                 // @option autoPanPaddingBottomRight: Point = null\r
9312                 // The margin between the popup and the bottom right corner of the map\r
9313                 // view after autopanning was performed.\r
9314                 autoPanPaddingBottomRight: null,\r
9315 \r
9316                 // @option autoPanPadding: Point = Point(5, 5)\r
9317                 // Equivalent of setting both top left and bottom right autopan padding to the same value.\r
9318                 autoPanPadding: [5, 5],\r
9319 \r
9320                 // @option keepInView: Boolean = false\r
9321                 // Set it to `true` if you want to prevent users from panning the popup\r
9322                 // off of the screen while it is open.\r
9323                 keepInView: false,\r
9324 \r
9325                 // @option closeButton: Boolean = true\r
9326                 // Controls the presence of a close button in the popup.\r
9327                 closeButton: true,\r
9328 \r
9329                 // @option autoClose: Boolean = true\r
9330                 // Set it to `false` if you want to override the default behavior of\r
9331                 // the popup closing when another popup is opened.\r
9332                 autoClose: true,\r
9333 \r
9334                 // @option closeOnClick: Boolean = *\r
9335                 // Set it if you want to override the default behavior of the popup closing when user clicks\r
9336                 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.\r
9337 \r
9338                 // @option className: String = ''\r
9339                 // A custom CSS class name to assign to the popup.\r
9340                 className: ''\r
9341         },\r
9342 \r
9343         // @namespace Popup\r
9344         // @method openOn(map: Map): this\r
9345         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.\r
9346         openOn: function (map) {\r
9347                 map.openPopup(this);\r
9348                 return this;\r
9349         },\r
9350 \r
9351         onAdd: function (map) {\r
9352                 DivOverlay.prototype.onAdd.call(this, map);\r
9353 \r
9354                 // @namespace Map\r
9355                 // @section Popup events\r
9356                 // @event popupopen: PopupEvent\r
9357                 // Fired when a popup is opened in the map\r
9358                 map.fire('popupopen', {popup: this});\r
9359 \r
9360                 if (this._source) {\r
9361                         // @namespace Layer\r
9362                         // @section Popup events\r
9363                         // @event popupopen: PopupEvent\r
9364                         // Fired when a popup bound to this layer is opened\r
9365                         this._source.fire('popupopen', {popup: this}, true);\r
9366                         // For non-path layers, we toggle the popup when clicking\r
9367                         // again the layer, so prevent the map to reopen it.\r
9368                         if (!(this._source instanceof Path)) {\r
9369                                 this._source.on('preclick', stopPropagation);\r
9370                         }\r
9371                 }\r
9372         },\r
9373 \r
9374         onRemove: function (map) {\r
9375                 DivOverlay.prototype.onRemove.call(this, map);\r
9376 \r
9377                 // @namespace Map\r
9378                 // @section Popup events\r
9379                 // @event popupclose: PopupEvent\r
9380                 // Fired when a popup in the map is closed\r
9381                 map.fire('popupclose', {popup: this});\r
9382 \r
9383                 if (this._source) {\r
9384                         // @namespace Layer\r
9385                         // @section Popup events\r
9386                         // @event popupclose: PopupEvent\r
9387                         // Fired when a popup bound to this layer is closed\r
9388                         this._source.fire('popupclose', {popup: this}, true);\r
9389                         if (!(this._source instanceof Path)) {\r
9390                                 this._source.off('preclick', stopPropagation);\r
9391                         }\r
9392                 }\r
9393         },\r
9394 \r
9395         getEvents: function () {\r
9396                 var events = DivOverlay.prototype.getEvents.call(this);\r
9397 \r
9398                 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {\r
9399                         events.preclick = this._close;\r
9400                 }\r
9401 \r
9402                 if (this.options.keepInView) {\r
9403                         events.moveend = this._adjustPan;\r
9404                 }\r
9405 \r
9406                 return events;\r
9407         },\r
9408 \r
9409         _close: function () {\r
9410                 if (this._map) {\r
9411                         this._map.closePopup(this);\r
9412                 }\r
9413         },\r
9414 \r
9415         _initLayout: function () {\r
9416                 var prefix = 'leaflet-popup',\r
9417                     container = this._container = create$1('div',\r
9418                         prefix + ' ' + (this.options.className || '') +\r
9419                         ' leaflet-zoom-animated');\r
9420 \r
9421                 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);\r
9422                 this._contentNode = create$1('div', prefix + '-content', wrapper);\r
9423 \r
9424                 disableClickPropagation(wrapper);\r
9425                 disableScrollPropagation(this._contentNode);\r
9426                 on(wrapper, 'contextmenu', stopPropagation);\r
9427 \r
9428                 this._tipContainer = create$1('div', prefix + '-tip-container', container);\r
9429                 this._tip = create$1('div', prefix + '-tip', this._tipContainer);\r
9430 \r
9431                 if (this.options.closeButton) {\r
9432                         var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);\r
9433                         closeButton.href = '#close';\r
9434                         closeButton.innerHTML = '&#215;';\r
9435 \r
9436                         on(closeButton, 'click', this._onCloseButtonClick, this);\r
9437                 }\r
9438         },\r
9439 \r
9440         _updateLayout: function () {\r
9441                 var container = this._contentNode,\r
9442                     style = container.style;\r
9443 \r
9444                 style.width = '';\r
9445                 style.whiteSpace = 'nowrap';\r
9446 \r
9447                 var width = container.offsetWidth;\r
9448                 width = Math.min(width, this.options.maxWidth);\r
9449                 width = Math.max(width, this.options.minWidth);\r
9450 \r
9451                 style.width = (width + 1) + 'px';\r
9452                 style.whiteSpace = '';\r
9453 \r
9454                 style.height = '';\r
9455 \r
9456                 var height = container.offsetHeight,\r
9457                     maxHeight = this.options.maxHeight,\r
9458                     scrolledClass = 'leaflet-popup-scrolled';\r
9459 \r
9460                 if (maxHeight && height > maxHeight) {\r
9461                         style.height = maxHeight + 'px';\r
9462                         addClass(container, scrolledClass);\r
9463                 } else {\r
9464                         removeClass(container, scrolledClass);\r
9465                 }\r
9466 \r
9467                 this._containerWidth = this._container.offsetWidth;\r
9468         },\r
9469 \r
9470         _animateZoom: function (e) {\r
9471                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),\r
9472                     anchor = this._getAnchor();\r
9473                 setPosition(this._container, pos.add(anchor));\r
9474         },\r
9475 \r
9476         _adjustPan: function () {\r
9477                 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }\r
9478 \r
9479                 var map = this._map,\r
9480                     marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,\r
9481                     containerHeight = this._container.offsetHeight + marginBottom,\r
9482                     containerWidth = this._containerWidth,\r
9483                     layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);\r
9484 \r
9485                 layerPos._add(getPosition(this._container));\r
9486 \r
9487                 var containerPos = map.layerPointToContainerPoint(layerPos),\r
9488                     padding = toPoint(this.options.autoPanPadding),\r
9489                     paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),\r
9490                     paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),\r
9491                     size = map.getSize(),\r
9492                     dx = 0,\r
9493                     dy = 0;\r
9494 \r
9495                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right\r
9496                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;\r
9497                 }\r
9498                 if (containerPos.x - dx - paddingTL.x < 0) { // left\r
9499                         dx = containerPos.x - paddingTL.x;\r
9500                 }\r
9501                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom\r
9502                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;\r
9503                 }\r
9504                 if (containerPos.y - dy - paddingTL.y < 0) { // top\r
9505                         dy = containerPos.y - paddingTL.y;\r
9506                 }\r
9507 \r
9508                 // @namespace Map\r
9509                 // @section Popup events\r
9510                 // @event autopanstart: Event\r
9511                 // Fired when the map starts autopanning when opening a popup.\r
9512                 if (dx || dy) {\r
9513                         map\r
9514                             .fire('autopanstart')\r
9515                             .panBy([dx, dy]);\r
9516                 }\r
9517         },\r
9518 \r
9519         _onCloseButtonClick: function (e) {\r
9520                 this._close();\r
9521                 stop(e);\r
9522         },\r
9523 \r
9524         _getAnchor: function () {\r
9525                 // Where should we anchor the popup on the source layer?\r
9526                 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);\r
9527         }\r
9528 \r
9529 });\r
9530 \r
9531 // @namespace Popup\r
9532 // @factory L.popup(options?: Popup options, source?: Layer)\r
9533 // 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
9534 var popup = function (options, source) {\r
9535         return new Popup(options, source);\r
9536 };\r
9537 \r
9538 \r
9539 /* @namespace Map\r
9540  * @section Interaction Options\r
9541  * @option closePopupOnClick: Boolean = true\r
9542  * Set it to `false` if you don't want popups to close when user clicks the map.\r
9543  */\r
9544 Map.mergeOptions({\r
9545         closePopupOnClick: true\r
9546 });\r
9547 \r
9548 \r
9549 // @namespace Map\r
9550 // @section Methods for Layers and Controls\r
9551 Map.include({\r
9552         // @method openPopup(popup: Popup): this\r
9553         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).\r
9554         // @alternative\r
9555         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this\r
9556         // Creates a popup with the specified content and options and opens it in the given point on a map.\r
9557         openPopup: function (popup, latlng, options) {\r
9558                 if (!(popup instanceof Popup)) {\r
9559                         popup = new Popup(options).setContent(popup);\r
9560                 }\r
9561 \r
9562                 if (latlng) {\r
9563                         popup.setLatLng(latlng);\r
9564                 }\r
9565 \r
9566                 if (this.hasLayer(popup)) {\r
9567                         return this;\r
9568                 }\r
9569 \r
9570                 if (this._popup && this._popup.options.autoClose) {\r
9571                         this.closePopup();\r
9572                 }\r
9573 \r
9574                 this._popup = popup;\r
9575                 return this.addLayer(popup);\r
9576         },\r
9577 \r
9578         // @method closePopup(popup?: Popup): this\r
9579         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).\r
9580         closePopup: function (popup) {\r
9581                 if (!popup || popup === this._popup) {\r
9582                         popup = this._popup;\r
9583                         this._popup = null;\r
9584                 }\r
9585                 if (popup) {\r
9586                         this.removeLayer(popup);\r
9587                 }\r
9588                 return this;\r
9589         }\r
9590 });\r
9591 \r
9592 /*\r
9593  * @namespace Layer\r
9594  * @section Popup methods example\r
9595  *\r
9596  * All layers share a set of methods convenient for binding popups to it.\r
9597  *\r
9598  * ```js\r
9599  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);\r
9600  * layer.openPopup();\r
9601  * layer.closePopup();\r
9602  * ```\r
9603  *\r
9604  * 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
9605  */\r
9606 \r
9607 // @section Popup methods\r
9608 Layer.include({\r
9609 \r
9610         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this\r
9611         // Binds a popup to the layer with the passed `content` and sets up the\r
9612         // necessary event listeners. If a `Function` is passed it will receive\r
9613         // the layer as the first argument and should return a `String` or `HTMLElement`.\r
9614         bindPopup: function (content, options) {\r
9615 \r
9616                 if (content instanceof Popup) {\r
9617                         setOptions(content, options);\r
9618                         this._popup = content;\r
9619                         content._source = this;\r
9620                 } else {\r
9621                         if (!this._popup || options) {\r
9622                                 this._popup = new Popup(options, this);\r
9623                         }\r
9624                         this._popup.setContent(content);\r
9625                 }\r
9626 \r
9627                 if (!this._popupHandlersAdded) {\r
9628                         this.on({\r
9629                                 click: this._openPopup,\r
9630                                 keypress: this._onKeyPress,\r
9631                                 remove: this.closePopup,\r
9632                                 move: this._movePopup\r
9633                         });\r
9634                         this._popupHandlersAdded = true;\r
9635                 }\r
9636 \r
9637                 return this;\r
9638         },\r
9639 \r
9640         // @method unbindPopup(): this\r
9641         // Removes the popup previously bound with `bindPopup`.\r
9642         unbindPopup: function () {\r
9643                 if (this._popup) {\r
9644                         this.off({\r
9645                                 click: this._openPopup,\r
9646                                 keypress: this._onKeyPress,\r
9647                                 remove: this.closePopup,\r
9648                                 move: this._movePopup\r
9649                         });\r
9650                         this._popupHandlersAdded = false;\r
9651                         this._popup = null;\r
9652                 }\r
9653                 return this;\r
9654         },\r
9655 \r
9656         // @method openPopup(latlng?: LatLng): this\r
9657         // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.\r
9658         openPopup: function (layer, latlng) {\r
9659                 if (!(layer instanceof Layer)) {\r
9660                         latlng = layer;\r
9661                         layer = this;\r
9662                 }\r
9663 \r
9664                 if (layer instanceof FeatureGroup) {\r
9665                         for (var id in this._layers) {\r
9666                                 layer = this._layers[id];\r
9667                                 break;\r
9668                         }\r
9669                 }\r
9670 \r
9671                 if (!latlng) {\r
9672                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();\r
9673                 }\r
9674 \r
9675                 if (this._popup && this._map) {\r
9676                         // set popup source to this layer\r
9677                         this._popup._source = layer;\r
9678 \r
9679                         // update the popup (content, layout, ect...)\r
9680                         this._popup.update();\r
9681 \r
9682                         // open the popup on the map\r
9683                         this._map.openPopup(this._popup, latlng);\r
9684                 }\r
9685 \r
9686                 return this;\r
9687         },\r
9688 \r
9689         // @method closePopup(): this\r
9690         // Closes the popup bound to this layer if it is open.\r
9691         closePopup: function () {\r
9692                 if (this._popup) {\r
9693                         this._popup._close();\r
9694                 }\r
9695                 return this;\r
9696         },\r
9697 \r
9698         // @method togglePopup(): this\r
9699         // Opens or closes the popup bound to this layer depending on its current state.\r
9700         togglePopup: function (target) {\r
9701                 if (this._popup) {\r
9702                         if (this._popup._map) {\r
9703                                 this.closePopup();\r
9704                         } else {\r
9705                                 this.openPopup(target);\r
9706                         }\r
9707                 }\r
9708                 return this;\r
9709         },\r
9710 \r
9711         // @method isPopupOpen(): boolean\r
9712         // Returns `true` if the popup bound to this layer is currently open.\r
9713         isPopupOpen: function () {\r
9714                 return (this._popup ? this._popup.isOpen() : false);\r
9715         },\r
9716 \r
9717         // @method setPopupContent(content: String|HTMLElement|Popup): this\r
9718         // Sets the content of the popup bound to this layer.\r
9719         setPopupContent: function (content) {\r
9720                 if (this._popup) {\r
9721                         this._popup.setContent(content);\r
9722                 }\r
9723                 return this;\r
9724         },\r
9725 \r
9726         // @method getPopup(): Popup\r
9727         // Returns the popup bound to this layer.\r
9728         getPopup: function () {\r
9729                 return this._popup;\r
9730         },\r
9731 \r
9732         _openPopup: function (e) {\r
9733                 var layer = e.layer || e.target;\r
9734 \r
9735                 if (!this._popup) {\r
9736                         return;\r
9737                 }\r
9738 \r
9739                 if (!this._map) {\r
9740                         return;\r
9741                 }\r
9742 \r
9743                 // prevent map click\r
9744                 stop(e);\r
9745 \r
9746                 // if this inherits from Path its a vector and we can just\r
9747                 // open the popup at the new location\r
9748                 if (layer instanceof Path) {\r
9749                         this.openPopup(e.layer || e.target, e.latlng);\r
9750                         return;\r
9751                 }\r
9752 \r
9753                 // otherwise treat it like a marker and figure out\r
9754                 // if we should toggle it open/closed\r
9755                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {\r
9756                         this.closePopup();\r
9757                 } else {\r
9758                         this.openPopup(layer, e.latlng);\r
9759                 }\r
9760         },\r
9761 \r
9762         _movePopup: function (e) {\r
9763                 this._popup.setLatLng(e.latlng);\r
9764         },\r
9765 \r
9766         _onKeyPress: function (e) {\r
9767                 if (e.originalEvent.keyCode === 13) {\r
9768                         this._openPopup(e);\r
9769                 }\r
9770         }\r
9771 });
9772
9773 /*
9774  * @class Tooltip
9775  * @inherits DivOverlay
9776  * @aka L.Tooltip
9777  * Used to display small texts on top of map layers.
9778  *
9779  * @example
9780  *
9781  * ```js
9782  * marker.bindTooltip("my tooltip text").openTooltip();
9783  * ```
9784  * Note about tooltip offset. Leaflet takes two options in consideration
9785  * for computing tooltip offseting:
9786  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
9787  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
9788  *   move it to the bottom. Negatives will move to the left and top.
9789  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
9790  *   should adapt this value if you use a custom icon.
9791  */
9792
9793
9794 // @namespace Tooltip
9795 var Tooltip = DivOverlay.extend({
9796
9797         // @section
9798         // @aka Tooltip options
9799         options: {
9800                 // @option pane: String = 'tooltipPane'
9801                 // `Map pane` where the tooltip will be added.
9802                 pane: 'tooltipPane',
9803
9804                 // @option offset: Point = Point(0, 0)
9805                 // Optional offset of the tooltip position.
9806                 offset: [0, 0],
9807
9808                 // @option direction: String = 'auto'
9809                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
9810                 // `top`, `bottom`, `center`, `auto`.
9811                 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
9812                 // position on the map.
9813                 direction: 'auto',
9814
9815                 // @option permanent: Boolean = false
9816                 // Whether to open the tooltip permanently or only on mouseover.
9817                 permanent: false,
9818
9819                 // @option sticky: Boolean = false
9820                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
9821                 sticky: false,
9822
9823                 // @option interactive: Boolean = false
9824                 // If true, the tooltip will listen to the feature events.
9825                 interactive: false,
9826
9827                 // @option opacity: Number = 0.9
9828                 // Tooltip container opacity.
9829                 opacity: 0.9
9830         },
9831
9832         onAdd: function (map) {
9833                 DivOverlay.prototype.onAdd.call(this, map);
9834                 this.setOpacity(this.options.opacity);
9835
9836                 // @namespace Map
9837                 // @section Tooltip events
9838                 // @event tooltipopen: TooltipEvent
9839                 // Fired when a tooltip is opened in the map.
9840                 map.fire('tooltipopen', {tooltip: this});
9841
9842                 if (this._source) {
9843                         // @namespace Layer
9844                         // @section Tooltip events
9845                         // @event tooltipopen: TooltipEvent
9846                         // Fired when a tooltip bound to this layer is opened.
9847                         this._source.fire('tooltipopen', {tooltip: this}, true);
9848                 }
9849         },
9850
9851         onRemove: function (map) {
9852                 DivOverlay.prototype.onRemove.call(this, map);
9853
9854                 // @namespace Map
9855                 // @section Tooltip events
9856                 // @event tooltipclose: TooltipEvent
9857                 // Fired when a tooltip in the map is closed.
9858                 map.fire('tooltipclose', {tooltip: this});
9859
9860                 if (this._source) {
9861                         // @namespace Layer
9862                         // @section Tooltip events
9863                         // @event tooltipclose: TooltipEvent
9864                         // Fired when a tooltip bound to this layer is closed.
9865                         this._source.fire('tooltipclose', {tooltip: this}, true);
9866                 }
9867         },
9868
9869         getEvents: function () {
9870                 var events = DivOverlay.prototype.getEvents.call(this);
9871
9872                 if (touch && !this.options.permanent) {
9873                         events.preclick = this._close;
9874                 }
9875
9876                 return events;
9877         },
9878
9879         _close: function () {
9880                 if (this._map) {
9881                         this._map.closeTooltip(this);
9882                 }
9883         },
9884
9885         _initLayout: function () {
9886                 var prefix = 'leaflet-tooltip',
9887                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
9888
9889                 this._contentNode = this._container = create$1('div', className);
9890         },
9891
9892         _updateLayout: function () {},
9893
9894         _adjustPan: function () {},
9895
9896         _setPosition: function (pos) {
9897                 var map = this._map,
9898                     container = this._container,
9899                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
9900                     tooltipPoint = map.layerPointToContainerPoint(pos),
9901                     direction = this.options.direction,
9902                     tooltipWidth = container.offsetWidth,
9903                     tooltipHeight = container.offsetHeight,
9904                     offset = toPoint(this.options.offset),
9905                     anchor = this._getAnchor();
9906
9907                 if (direction === 'top') {
9908                         pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
9909                 } else if (direction === 'bottom') {
9910                         pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
9911                 } else if (direction === 'center') {
9912                         pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
9913                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
9914                         direction = 'right';
9915                         pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
9916                 } else {
9917                         direction = 'left';
9918                         pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
9919                 }
9920
9921                 removeClass(container, 'leaflet-tooltip-right');
9922                 removeClass(container, 'leaflet-tooltip-left');
9923                 removeClass(container, 'leaflet-tooltip-top');
9924                 removeClass(container, 'leaflet-tooltip-bottom');
9925                 addClass(container, 'leaflet-tooltip-' + direction);
9926                 setPosition(container, pos);
9927         },
9928
9929         _updatePosition: function () {
9930                 var pos = this._map.latLngToLayerPoint(this._latlng);
9931                 this._setPosition(pos);
9932         },
9933
9934         setOpacity: function (opacity) {
9935                 this.options.opacity = opacity;
9936
9937                 if (this._container) {
9938                         setOpacity(this._container, opacity);
9939                 }
9940         },
9941
9942         _animateZoom: function (e) {
9943                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
9944                 this._setPosition(pos);
9945         },
9946
9947         _getAnchor: function () {
9948                 // Where should we anchor the tooltip on the source layer?
9949                 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
9950         }
9951
9952 });
9953
9954 // @namespace Tooltip
9955 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
9956 // 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.
9957 var tooltip = function (options, source) {
9958         return new Tooltip(options, source);
9959 };
9960
9961 // @namespace Map
9962 // @section Methods for Layers and Controls
9963 Map.include({
9964
9965         // @method openTooltip(tooltip: Tooltip): this
9966         // Opens the specified tooltip.
9967         // @alternative
9968         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
9969         // Creates a tooltip with the specified content and options and open it.
9970         openTooltip: function (tooltip, latlng, options) {
9971                 if (!(tooltip instanceof Tooltip)) {
9972                         tooltip = new Tooltip(options).setContent(tooltip);
9973                 }
9974
9975                 if (latlng) {
9976                         tooltip.setLatLng(latlng);
9977                 }
9978
9979                 if (this.hasLayer(tooltip)) {
9980                         return this;
9981                 }
9982
9983                 return this.addLayer(tooltip);
9984         },
9985
9986         // @method closeTooltip(tooltip?: Tooltip): this
9987         // Closes the tooltip given as parameter.
9988         closeTooltip: function (tooltip) {
9989                 if (tooltip) {
9990                         this.removeLayer(tooltip);
9991                 }
9992                 return this;
9993         }
9994
9995 });
9996
9997 /*
9998  * @namespace Layer
9999  * @section Tooltip methods example
10000  *
10001  * All layers share a set of methods convenient for binding tooltips to it.
10002  *
10003  * ```js
10004  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10005  * layer.openTooltip();
10006  * layer.closeTooltip();
10007  * ```
10008  */
10009
10010 // @section Tooltip methods
10011 Layer.include({
10012
10013         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10014         // Binds a tooltip to the layer with the passed `content` and sets up the
10015         // necessary event listeners. If a `Function` is passed it will receive
10016         // the layer as the first argument and should return a `String` or `HTMLElement`.
10017         bindTooltip: function (content, options) {
10018
10019                 if (content instanceof Tooltip) {
10020                         setOptions(content, options);
10021                         this._tooltip = content;
10022                         content._source = this;
10023                 } else {
10024                         if (!this._tooltip || options) {
10025                                 this._tooltip = new Tooltip(options, this);
10026                         }
10027                         this._tooltip.setContent(content);
10028
10029                 }
10030
10031                 this._initTooltipInteractions();
10032
10033                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10034                         this.openTooltip();
10035                 }
10036
10037                 return this;
10038         },
10039
10040         // @method unbindTooltip(): this
10041         // Removes the tooltip previously bound with `bindTooltip`.
10042         unbindTooltip: function () {
10043                 if (this._tooltip) {
10044                         this._initTooltipInteractions(true);
10045                         this.closeTooltip();
10046                         this._tooltip = null;
10047                 }
10048                 return this;
10049         },
10050
10051         _initTooltipInteractions: function (remove$$1) {
10052                 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10053                 var onOff = remove$$1 ? 'off' : 'on',
10054                     events = {
10055                         remove: this.closeTooltip,
10056                         move: this._moveTooltip
10057                     };
10058                 if (!this._tooltip.options.permanent) {
10059                         events.mouseover = this._openTooltip;
10060                         events.mouseout = this.closeTooltip;
10061                         if (this._tooltip.options.sticky) {
10062                                 events.mousemove = this._moveTooltip;
10063                         }
10064                         if (touch) {
10065                                 events.click = this._openTooltip;
10066                         }
10067                 } else {
10068                         events.add = this._openTooltip;
10069                 }
10070                 this[onOff](events);
10071                 this._tooltipHandlersAdded = !remove$$1;
10072         },
10073
10074         // @method openTooltip(latlng?: LatLng): this
10075         // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
10076         openTooltip: function (layer, latlng) {
10077                 if (!(layer instanceof Layer)) {
10078                         latlng = layer;
10079                         layer = this;
10080                 }
10081
10082                 if (layer instanceof FeatureGroup) {
10083                         for (var id in this._layers) {
10084                                 layer = this._layers[id];
10085                                 break;
10086                         }
10087                 }
10088
10089                 if (!latlng) {
10090                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10091                 }
10092
10093                 if (this._tooltip && this._map) {
10094
10095                         // set tooltip source to this layer
10096                         this._tooltip._source = layer;
10097
10098                         // update the tooltip (content, layout, ect...)
10099                         this._tooltip.update();
10100
10101                         // open the tooltip on the map
10102                         this._map.openTooltip(this._tooltip, latlng);
10103
10104                         // Tooltip container may not be defined if not permanent and never
10105                         // opened.
10106                         if (this._tooltip.options.interactive && this._tooltip._container) {
10107                                 addClass(this._tooltip._container, 'leaflet-clickable');
10108                                 this.addInteractiveTarget(this._tooltip._container);
10109                         }
10110                 }
10111
10112                 return this;
10113         },
10114
10115         // @method closeTooltip(): this
10116         // Closes the tooltip bound to this layer if it is open.
10117         closeTooltip: function () {
10118                 if (this._tooltip) {
10119                         this._tooltip._close();
10120                         if (this._tooltip.options.interactive && this._tooltip._container) {
10121                                 removeClass(this._tooltip._container, 'leaflet-clickable');
10122                                 this.removeInteractiveTarget(this._tooltip._container);
10123                         }
10124                 }
10125                 return this;
10126         },
10127
10128         // @method toggleTooltip(): this
10129         // Opens or closes the tooltip bound to this layer depending on its current state.
10130         toggleTooltip: function (target) {
10131                 if (this._tooltip) {
10132                         if (this._tooltip._map) {
10133                                 this.closeTooltip();
10134                         } else {
10135                                 this.openTooltip(target);
10136                         }
10137                 }
10138                 return this;
10139         },
10140
10141         // @method isTooltipOpen(): boolean
10142         // Returns `true` if the tooltip bound to this layer is currently open.
10143         isTooltipOpen: function () {
10144                 return this._tooltip.isOpen();
10145         },
10146
10147         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10148         // Sets the content of the tooltip bound to this layer.
10149         setTooltipContent: function (content) {
10150                 if (this._tooltip) {
10151                         this._tooltip.setContent(content);
10152                 }
10153                 return this;
10154         },
10155
10156         // @method getTooltip(): Tooltip
10157         // Returns the tooltip bound to this layer.
10158         getTooltip: function () {
10159                 return this._tooltip;
10160         },
10161
10162         _openTooltip: function (e) {
10163                 var layer = e.layer || e.target;
10164
10165                 if (!this._tooltip || !this._map) {
10166                         return;
10167                 }
10168                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10169         },
10170
10171         _moveTooltip: function (e) {
10172                 var latlng = e.latlng, containerPoint, layerPoint;
10173                 if (this._tooltip.options.sticky && e.originalEvent) {
10174                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10175                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10176                         latlng = this._map.layerPointToLatLng(layerPoint);
10177                 }
10178                 this._tooltip.setLatLng(latlng);
10179         }
10180 });
10181
10182 /*
10183  * @class DivIcon
10184  * @aka L.DivIcon
10185  * @inherits Icon
10186  *
10187  * Represents a lightweight icon for markers that uses a simple `<div>`
10188  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10189  *
10190  * @example
10191  * ```js
10192  * var myIcon = L.divIcon({className: 'my-div-icon'});
10193  * // you can set .my-div-icon styles in CSS
10194  *
10195  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10196  * ```
10197  *
10198  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10199  */
10200
10201 var DivIcon = Icon.extend({
10202         options: {
10203                 // @section
10204                 // @aka DivIcon options
10205                 iconSize: [12, 12], // also can be set through CSS
10206
10207                 // iconAnchor: (Point),
10208                 // popupAnchor: (Point),
10209
10210                 // @option html: String = ''
10211                 // Custom HTML code to put inside the div element, empty by default.
10212                 html: false,
10213
10214                 // @option bgPos: Point = [0, 0]
10215                 // Optional relative position of the background, in pixels
10216                 bgPos: null,
10217
10218                 className: 'leaflet-div-icon'
10219         },
10220
10221         createIcon: function (oldIcon) {
10222                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10223                     options = this.options;
10224
10225                 div.innerHTML = options.html !== false ? options.html : '';
10226
10227                 if (options.bgPos) {
10228                         var bgPos = toPoint(options.bgPos);
10229                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10230                 }
10231                 this._setIconStyles(div, 'icon');
10232
10233                 return div;
10234         },
10235
10236         createShadow: function () {
10237                 return null;
10238         }
10239 });
10240
10241 // @factory L.divIcon(options: DivIcon options)
10242 // Creates a `DivIcon` instance with the given options.
10243 function divIcon(options) {
10244         return new DivIcon(options);
10245 }
10246
10247 Icon.Default = IconDefault;
10248
10249 /*
10250  * @class GridLayer
10251  * @inherits Layer
10252  * @aka L.GridLayer
10253  *
10254  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10255  * 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.
10256  *
10257  *
10258  * @section Synchronous usage
10259  * @example
10260  *
10261  * 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.
10262  *
10263  * ```js
10264  * var CanvasLayer = L.GridLayer.extend({
10265  *     createTile: function(coords){
10266  *         // create a <canvas> element for drawing
10267  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10268  *
10269  *         // setup tile width and height according to the options
10270  *         var size = this.getTileSize();
10271  *         tile.width = size.x;
10272  *         tile.height = size.y;
10273  *
10274  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10275  *         var ctx = tile.getContext('2d');
10276  *
10277  *         // return the tile so it can be rendered on screen
10278  *         return tile;
10279  *     }
10280  * });
10281  * ```
10282  *
10283  * @section Asynchronous usage
10284  * @example
10285  *
10286  * 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.
10287  *
10288  * ```js
10289  * var CanvasLayer = L.GridLayer.extend({
10290  *     createTile: function(coords, done){
10291  *         var error;
10292  *
10293  *         // create a <canvas> element for drawing
10294  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10295  *
10296  *         // setup tile width and height according to the options
10297  *         var size = this.getTileSize();
10298  *         tile.width = size.x;
10299  *         tile.height = size.y;
10300  *
10301  *         // draw something asynchronously and pass the tile to the done() callback
10302  *         setTimeout(function() {
10303  *             done(error, tile);
10304  *         }, 1000);
10305  *
10306  *         return tile;
10307  *     }
10308  * });
10309  * ```
10310  *
10311  * @section
10312  */
10313
10314
10315 var GridLayer = Layer.extend({
10316
10317         // @section
10318         // @aka GridLayer options
10319         options: {
10320                 // @option tileSize: Number|Point = 256
10321                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10322                 tileSize: 256,
10323
10324                 // @option opacity: Number = 1.0
10325                 // Opacity of the tiles. Can be used in the `createTile()` function.
10326                 opacity: 1,
10327
10328                 // @option updateWhenIdle: Boolean = (depends)
10329                 // Load new tiles only when panning ends.
10330                 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10331                 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10332                 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10333                 updateWhenIdle: mobile,
10334
10335                 // @option updateWhenZooming: Boolean = true
10336                 // 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.
10337                 updateWhenZooming: true,
10338
10339                 // @option updateInterval: Number = 200
10340                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10341                 updateInterval: 200,
10342
10343                 // @option zIndex: Number = 1
10344                 // The explicit zIndex of the tile layer.
10345                 zIndex: 1,
10346
10347                 // @option bounds: LatLngBounds = undefined
10348                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10349                 bounds: null,
10350
10351                 // @option minZoom: Number = 0
10352                 // The minimum zoom level down to which this layer will be displayed (inclusive).
10353                 minZoom: 0,
10354
10355                 // @option maxZoom: Number = undefined
10356                 // The maximum zoom level up to which this layer will be displayed (inclusive).
10357                 maxZoom: undefined,
10358
10359                 // @option maxNativeZoom: Number = undefined
10360                 // Maximum zoom number the tile source has available. If it is specified,
10361                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10362                 // from `maxNativeZoom` level and auto-scaled.
10363                 maxNativeZoom: undefined,
10364
10365                 // @option minNativeZoom: Number = undefined
10366                 // Minimum zoom number the tile source has available. If it is specified,
10367                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10368                 // from `minNativeZoom` level and auto-scaled.
10369                 minNativeZoom: undefined,
10370
10371                 // @option noWrap: Boolean = false
10372                 // Whether the layer is wrapped around the antimeridian. If `true`, the
10373                 // GridLayer will only be displayed once at low zoom levels. Has no
10374                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10375                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10376                 // tiles outside the CRS limits.
10377                 noWrap: false,
10378
10379                 // @option pane: String = 'tilePane'
10380                 // `Map pane` where the grid layer will be added.
10381                 pane: 'tilePane',
10382
10383                 // @option className: String = ''
10384                 // A custom class name to assign to the tile layer. Empty by default.
10385                 className: '',
10386
10387                 // @option keepBuffer: Number = 2
10388                 // When panning the map, keep this many rows and columns of tiles before unloading them.
10389                 keepBuffer: 2
10390         },
10391
10392         initialize: function (options) {
10393                 setOptions(this, options);
10394         },
10395
10396         onAdd: function () {
10397                 this._initContainer();
10398
10399                 this._levels = {};
10400                 this._tiles = {};
10401
10402                 this._resetView();
10403                 this._update();
10404         },
10405
10406         beforeAdd: function (map) {
10407                 map._addZoomLimit(this);
10408         },
10409
10410         onRemove: function (map) {
10411                 this._removeAllTiles();
10412                 remove(this._container);
10413                 map._removeZoomLimit(this);
10414                 this._container = null;
10415                 this._tileZoom = null;
10416         },
10417
10418         // @method bringToFront: this
10419         // Brings the tile layer to the top of all tile layers.
10420         bringToFront: function () {
10421                 if (this._map) {
10422                         toFront(this._container);
10423                         this._setAutoZIndex(Math.max);
10424                 }
10425                 return this;
10426         },
10427
10428         // @method bringToBack: this
10429         // Brings the tile layer to the bottom of all tile layers.
10430         bringToBack: function () {
10431                 if (this._map) {
10432                         toBack(this._container);
10433                         this._setAutoZIndex(Math.min);
10434                 }
10435                 return this;
10436         },
10437
10438         // @method getContainer: HTMLElement
10439         // Returns the HTML element that contains the tiles for this layer.
10440         getContainer: function () {
10441                 return this._container;
10442         },
10443
10444         // @method setOpacity(opacity: Number): this
10445         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10446         setOpacity: function (opacity) {
10447                 this.options.opacity = opacity;
10448                 this._updateOpacity();
10449                 return this;
10450         },
10451
10452         // @method setZIndex(zIndex: Number): this
10453         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10454         setZIndex: function (zIndex) {
10455                 this.options.zIndex = zIndex;
10456                 this._updateZIndex();
10457
10458                 return this;
10459         },
10460
10461         // @method isLoading: Boolean
10462         // Returns `true` if any tile in the grid layer has not finished loading.
10463         isLoading: function () {
10464                 return this._loading;
10465         },
10466
10467         // @method redraw: this
10468         // Causes the layer to clear all the tiles and request them again.
10469         redraw: function () {
10470                 if (this._map) {
10471                         this._removeAllTiles();
10472                         this._update();
10473                 }
10474                 return this;
10475         },
10476
10477         getEvents: function () {
10478                 var events = {
10479                         viewprereset: this._invalidateAll,
10480                         viewreset: this._resetView,
10481                         zoom: this._resetView,
10482                         moveend: this._onMoveEnd
10483                 };
10484
10485                 if (!this.options.updateWhenIdle) {
10486                         // update tiles on move, but not more often than once per given interval
10487                         if (!this._onMove) {
10488                                 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10489                         }
10490
10491                         events.move = this._onMove;
10492                 }
10493
10494                 if (this._zoomAnimated) {
10495                         events.zoomanim = this._animateZoom;
10496                 }
10497
10498                 return events;
10499         },
10500
10501         // @section Extension methods
10502         // Layers extending `GridLayer` shall reimplement the following method.
10503         // @method createTile(coords: Object, done?: Function): HTMLElement
10504         // Called only internally, must be overriden by classes extending `GridLayer`.
10505         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10506         // is specified, it must be called when the tile has finished loading and drawing.
10507         createTile: function () {
10508                 return document.createElement('div');
10509         },
10510
10511         // @section
10512         // @method getTileSize: Point
10513         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10514         getTileSize: function () {
10515                 var s = this.options.tileSize;
10516                 return s instanceof Point ? s : new Point(s, s);
10517         },
10518
10519         _updateZIndex: function () {
10520                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10521                         this._container.style.zIndex = this.options.zIndex;
10522                 }
10523         },
10524
10525         _setAutoZIndex: function (compare) {
10526                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10527
10528                 var layers = this.getPane().children,
10529                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10530
10531                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10532
10533                         zIndex = layers[i].style.zIndex;
10534
10535                         if (layers[i] !== this._container && zIndex) {
10536                                 edgeZIndex = compare(edgeZIndex, +zIndex);
10537                         }
10538                 }
10539
10540                 if (isFinite(edgeZIndex)) {
10541                         this.options.zIndex = edgeZIndex + compare(-1, 1);
10542                         this._updateZIndex();
10543                 }
10544         },
10545
10546         _updateOpacity: function () {
10547                 if (!this._map) { return; }
10548
10549                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10550                 if (ielt9) { return; }
10551
10552                 setOpacity(this._container, this.options.opacity);
10553
10554                 var now = +new Date(),
10555                     nextFrame = false,
10556                     willPrune = false;
10557
10558                 for (var key in this._tiles) {
10559                         var tile = this._tiles[key];
10560                         if (!tile.current || !tile.loaded) { continue; }
10561
10562                         var fade = Math.min(1, (now - tile.loaded) / 200);
10563
10564                         setOpacity(tile.el, fade);
10565                         if (fade < 1) {
10566                                 nextFrame = true;
10567                         } else {
10568                                 if (tile.active) {
10569                                         willPrune = true;
10570                                 } else {
10571                                         this._onOpaqueTile(tile);
10572                                 }
10573                                 tile.active = true;
10574                         }
10575                 }
10576
10577                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10578
10579                 if (nextFrame) {
10580                         cancelAnimFrame(this._fadeFrame);
10581                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10582                 }
10583         },
10584
10585         _onOpaqueTile: falseFn,
10586
10587         _initContainer: function () {
10588                 if (this._container) { return; }
10589
10590                 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10591                 this._updateZIndex();
10592
10593                 if (this.options.opacity < 1) {
10594                         this._updateOpacity();
10595                 }
10596
10597                 this.getPane().appendChild(this._container);
10598         },
10599
10600         _updateLevels: function () {
10601
10602                 var zoom = this._tileZoom,
10603                     maxZoom = this.options.maxZoom;
10604
10605                 if (zoom === undefined) { return undefined; }
10606
10607                 for (var z in this._levels) {
10608                         if (this._levels[z].el.children.length || z === zoom) {
10609                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10610                                 this._onUpdateLevel(z);
10611                         } else {
10612                                 remove(this._levels[z].el);
10613                                 this._removeTilesAtZoom(z);
10614                                 this._onRemoveLevel(z);
10615                                 delete this._levels[z];
10616                         }
10617                 }
10618
10619                 var level = this._levels[zoom],
10620                     map = this._map;
10621
10622                 if (!level) {
10623                         level = this._levels[zoom] = {};
10624
10625                         level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10626                         level.el.style.zIndex = maxZoom;
10627
10628                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10629                         level.zoom = zoom;
10630
10631                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
10632
10633                         // force the browser to consider the newly added element for transition
10634                         falseFn(level.el.offsetWidth);
10635
10636                         this._onCreateLevel(level);
10637                 }
10638
10639                 this._level = level;
10640
10641                 return level;
10642         },
10643
10644         _onUpdateLevel: falseFn,
10645
10646         _onRemoveLevel: falseFn,
10647
10648         _onCreateLevel: falseFn,
10649
10650         _pruneTiles: function () {
10651                 if (!this._map) {
10652                         return;
10653                 }
10654
10655                 var key, tile;
10656
10657                 var zoom = this._map.getZoom();
10658                 if (zoom > this.options.maxZoom ||
10659                         zoom < this.options.minZoom) {
10660                         this._removeAllTiles();
10661                         return;
10662                 }
10663
10664                 for (key in this._tiles) {
10665                         tile = this._tiles[key];
10666                         tile.retain = tile.current;
10667                 }
10668
10669                 for (key in this._tiles) {
10670                         tile = this._tiles[key];
10671                         if (tile.current && !tile.active) {
10672                                 var coords = tile.coords;
10673                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10674                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10675                                 }
10676                         }
10677                 }
10678
10679                 for (key in this._tiles) {
10680                         if (!this._tiles[key].retain) {
10681                                 this._removeTile(key);
10682                         }
10683                 }
10684         },
10685
10686         _removeTilesAtZoom: function (zoom) {
10687                 for (var key in this._tiles) {
10688                         if (this._tiles[key].coords.z !== zoom) {
10689                                 continue;
10690                         }
10691                         this._removeTile(key);
10692                 }
10693         },
10694
10695         _removeAllTiles: function () {
10696                 for (var key in this._tiles) {
10697                         this._removeTile(key);
10698                 }
10699         },
10700
10701         _invalidateAll: function () {
10702                 for (var z in this._levels) {
10703                         remove(this._levels[z].el);
10704                         this._onRemoveLevel(z);
10705                         delete this._levels[z];
10706                 }
10707                 this._removeAllTiles();
10708
10709                 this._tileZoom = null;
10710         },
10711
10712         _retainParent: function (x, y, z, minZoom) {
10713                 var x2 = Math.floor(x / 2),
10714                     y2 = Math.floor(y / 2),
10715                     z2 = z - 1,
10716                     coords2 = new Point(+x2, +y2);
10717                 coords2.z = +z2;
10718
10719                 var key = this._tileCoordsToKey(coords2),
10720                     tile = this._tiles[key];
10721
10722                 if (tile && tile.active) {
10723                         tile.retain = true;
10724                         return true;
10725
10726                 } else if (tile && tile.loaded) {
10727                         tile.retain = true;
10728                 }
10729
10730                 if (z2 > minZoom) {
10731                         return this._retainParent(x2, y2, z2, minZoom);
10732                 }
10733
10734                 return false;
10735         },
10736
10737         _retainChildren: function (x, y, z, maxZoom) {
10738
10739                 for (var i = 2 * x; i < 2 * x + 2; i++) {
10740                         for (var j = 2 * y; j < 2 * y + 2; j++) {
10741
10742                                 var coords = new Point(i, j);
10743                                 coords.z = z + 1;
10744
10745                                 var key = this._tileCoordsToKey(coords),
10746                                     tile = this._tiles[key];
10747
10748                                 if (tile && tile.active) {
10749                                         tile.retain = true;
10750                                         continue;
10751
10752                                 } else if (tile && tile.loaded) {
10753                                         tile.retain = true;
10754                                 }
10755
10756                                 if (z + 1 < maxZoom) {
10757                                         this._retainChildren(i, j, z + 1, maxZoom);
10758                                 }
10759                         }
10760                 }
10761         },
10762
10763         _resetView: function (e) {
10764                 var animating = e && (e.pinch || e.flyTo);
10765                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
10766         },
10767
10768         _animateZoom: function (e) {
10769                 this._setView(e.center, e.zoom, true, e.noUpdate);
10770         },
10771
10772         _clampZoom: function (zoom) {
10773                 var options = this.options;
10774
10775                 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
10776                         return options.minNativeZoom;
10777                 }
10778
10779                 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
10780                         return options.maxNativeZoom;
10781                 }
10782
10783                 return zoom;
10784         },
10785
10786         _setView: function (center, zoom, noPrune, noUpdate) {
10787                 var tileZoom = this._clampZoom(Math.round(zoom));
10788                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
10789                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
10790                         tileZoom = undefined;
10791                 }
10792
10793                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
10794
10795                 if (!noUpdate || tileZoomChanged) {
10796
10797                         this._tileZoom = tileZoom;
10798
10799                         if (this._abortLoading) {
10800                                 this._abortLoading();
10801                         }
10802
10803                         this._updateLevels();
10804                         this._resetGrid();
10805
10806                         if (tileZoom !== undefined) {
10807                                 this._update(center);
10808                         }
10809
10810                         if (!noPrune) {
10811                                 this._pruneTiles();
10812                         }
10813
10814                         // Flag to prevent _updateOpacity from pruning tiles during
10815                         // a zoom anim or a pinch gesture
10816                         this._noPrune = !!noPrune;
10817                 }
10818
10819                 this._setZoomTransforms(center, zoom);
10820         },
10821
10822         _setZoomTransforms: function (center, zoom) {
10823                 for (var i in this._levels) {
10824                         this._setZoomTransform(this._levels[i], center, zoom);
10825                 }
10826         },
10827
10828         _setZoomTransform: function (level, center, zoom) {
10829                 var scale = this._map.getZoomScale(zoom, level.zoom),
10830                     translate = level.origin.multiplyBy(scale)
10831                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
10832
10833                 if (any3d) {
10834                         setTransform(level.el, translate, scale);
10835                 } else {
10836                         setPosition(level.el, translate);
10837                 }
10838         },
10839
10840         _resetGrid: function () {
10841                 var map = this._map,
10842                     crs = map.options.crs,
10843                     tileSize = this._tileSize = this.getTileSize(),
10844                     tileZoom = this._tileZoom;
10845
10846                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
10847                 if (bounds) {
10848                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
10849                 }
10850
10851                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
10852                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
10853                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
10854                 ];
10855                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
10856                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
10857                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
10858                 ];
10859         },
10860
10861         _onMoveEnd: function () {
10862                 if (!this._map || this._map._animatingZoom) { return; }
10863
10864                 this._update();
10865         },
10866
10867         _getTiledPixelBounds: function (center) {
10868                 var map = this._map,
10869                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
10870                     scale = map.getZoomScale(mapZoom, this._tileZoom),
10871                     pixelCenter = map.project(center, this._tileZoom).floor(),
10872                     halfSize = map.getSize().divideBy(scale * 2);
10873
10874                 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
10875         },
10876
10877         // Private method to load tiles in the grid's active zoom level according to map bounds
10878         _update: function (center) {
10879                 var map = this._map;
10880                 if (!map) { return; }
10881                 var zoom = this._clampZoom(map.getZoom());
10882
10883                 if (center === undefined) { center = map.getCenter(); }
10884                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
10885
10886                 var pixelBounds = this._getTiledPixelBounds(center),
10887                     tileRange = this._pxBoundsToTileRange(pixelBounds),
10888                     tileCenter = tileRange.getCenter(),
10889                     queue = [],
10890                     margin = this.options.keepBuffer,
10891                     noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
10892                                               tileRange.getTopRight().add([margin, -margin]));
10893
10894                 // Sanity check: panic if the tile range contains Infinity somewhere.
10895                 if (!(isFinite(tileRange.min.x) &&
10896                       isFinite(tileRange.min.y) &&
10897                       isFinite(tileRange.max.x) &&
10898                       isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
10899
10900                 for (var key in this._tiles) {
10901                         var c = this._tiles[key].coords;
10902                         if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
10903                                 this._tiles[key].current = false;
10904                         }
10905                 }
10906
10907                 // _update just loads more tiles. If the tile zoom level differs too much
10908                 // from the map's, let _setView reset levels and prune old tiles.
10909                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
10910
10911                 // create a queue of coordinates to load tiles from
10912                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
10913                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
10914                                 var coords = new Point(i, j);
10915                                 coords.z = this._tileZoom;
10916
10917                                 if (!this._isValidTile(coords)) { continue; }
10918
10919                                 if (!this._tiles[this._tileCoordsToKey(coords)]) {
10920                                         queue.push(coords);
10921                                 }
10922                         }
10923                 }
10924
10925                 // sort tile queue to load tiles in order of their distance to center
10926                 queue.sort(function (a, b) {
10927                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
10928                 });
10929
10930                 if (queue.length !== 0) {
10931                         // if it's the first batch of tiles to load
10932                         if (!this._loading) {
10933                                 this._loading = true;
10934                                 // @event loading: Event
10935                                 // Fired when the grid layer starts loading tiles.
10936                                 this.fire('loading');
10937                         }
10938
10939                         // create DOM fragment to append tiles in one batch
10940                         var fragment = document.createDocumentFragment();
10941
10942                         for (i = 0; i < queue.length; i++) {
10943                                 this._addTile(queue[i], fragment);
10944                         }
10945
10946                         this._level.el.appendChild(fragment);
10947                 }
10948         },
10949
10950         _isValidTile: function (coords) {
10951                 var crs = this._map.options.crs;
10952
10953                 if (!crs.infinite) {
10954                         // don't load tile if it's out of bounds and not wrapped
10955                         var bounds = this._globalTileRange;
10956                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
10957                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
10958                 }
10959
10960                 if (!this.options.bounds) { return true; }
10961
10962                 // don't load tile if it doesn't intersect the bounds in options
10963                 var tileBounds = this._tileCoordsToBounds(coords);
10964                 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
10965         },
10966
10967         _keyToBounds: function (key) {
10968                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
10969         },
10970
10971         // converts tile coordinates to its geographical bounds
10972         _tileCoordsToBounds: function (coords) {
10973
10974                 var map = this._map,
10975                     tileSize = this.getTileSize(),
10976
10977                     nwPoint = coords.scaleBy(tileSize),
10978                     sePoint = nwPoint.add(tileSize),
10979
10980                     nw = map.unproject(nwPoint, coords.z),
10981                     se = map.unproject(sePoint, coords.z),
10982                     bounds = new LatLngBounds(nw, se);
10983
10984                 if (!this.options.noWrap) {
10985                         map.wrapLatLngBounds(bounds);
10986                 }
10987
10988                 return bounds;
10989         },
10990
10991         // converts tile coordinates to key for the tile cache
10992         _tileCoordsToKey: function (coords) {
10993                 return coords.x + ':' + coords.y + ':' + coords.z;
10994         },
10995
10996         // converts tile cache key to coordinates
10997         _keyToTileCoords: function (key) {
10998                 var k = key.split(':'),
10999                     coords = new Point(+k[0], +k[1]);
11000                 coords.z = +k[2];
11001                 return coords;
11002         },
11003
11004         _removeTile: function (key) {
11005                 var tile = this._tiles[key];
11006                 if (!tile) { return; }
11007
11008                 remove(tile.el);
11009
11010                 delete this._tiles[key];
11011
11012                 // @event tileunload: TileEvent
11013                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11014                 this.fire('tileunload', {
11015                         tile: tile.el,
11016                         coords: this._keyToTileCoords(key)
11017                 });
11018         },
11019
11020         _initTile: function (tile) {
11021                 addClass(tile, 'leaflet-tile');
11022
11023                 var tileSize = this.getTileSize();
11024                 tile.style.width = tileSize.x + 'px';
11025                 tile.style.height = tileSize.y + 'px';
11026
11027                 tile.onselectstart = falseFn;
11028                 tile.onmousemove = falseFn;
11029
11030                 // update opacity on tiles in IE7-8 because of filter inheritance problems
11031                 if (ielt9 && this.options.opacity < 1) {
11032                         setOpacity(tile, this.options.opacity);
11033                 }
11034
11035                 // without this hack, tiles disappear after zoom on Chrome for Android
11036                 // https://github.com/Leaflet/Leaflet/issues/2078
11037                 if (android && !android23) {
11038                         tile.style.WebkitBackfaceVisibility = 'hidden';
11039                 }
11040         },
11041
11042         _addTile: function (coords, container) {
11043                 var tilePos = this._getTilePos(coords),
11044                     key = this._tileCoordsToKey(coords);
11045
11046                 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11047
11048                 this._initTile(tile);
11049
11050                 // if createTile is defined with a second argument ("done" callback),
11051                 // we know that tile is async and will be ready later; otherwise
11052                 if (this.createTile.length < 2) {
11053                         // mark tile as ready, but delay one frame for opacity animation to happen
11054                         requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11055                 }
11056
11057                 setPosition(tile, tilePos);
11058
11059                 // save tile in cache
11060                 this._tiles[key] = {
11061                         el: tile,
11062                         coords: coords,
11063                         current: true
11064                 };
11065
11066                 container.appendChild(tile);
11067                 // @event tileloadstart: TileEvent
11068                 // Fired when a tile is requested and starts loading.
11069                 this.fire('tileloadstart', {
11070                         tile: tile,
11071                         coords: coords
11072                 });
11073         },
11074
11075         _tileReady: function (coords, err, tile) {
11076                 if (!this._map) { return; }
11077
11078                 if (err) {
11079                         // @event tileerror: TileErrorEvent
11080                         // Fired when there is an error loading a tile.
11081                         this.fire('tileerror', {
11082                                 error: err,
11083                                 tile: tile,
11084                                 coords: coords
11085                         });
11086                 }
11087
11088                 var key = this._tileCoordsToKey(coords);
11089
11090                 tile = this._tiles[key];
11091                 if (!tile) { return; }
11092
11093                 tile.loaded = +new Date();
11094                 if (this._map._fadeAnimated) {
11095                         setOpacity(tile.el, 0);
11096                         cancelAnimFrame(this._fadeFrame);
11097                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11098                 } else {
11099                         tile.active = true;
11100                         this._pruneTiles();
11101                 }
11102
11103                 if (!err) {
11104                         addClass(tile.el, 'leaflet-tile-loaded');
11105
11106                         // @event tileload: TileEvent
11107                         // Fired when a tile loads.
11108                         this.fire('tileload', {
11109                                 tile: tile.el,
11110                                 coords: coords
11111                         });
11112                 }
11113
11114                 if (this._noTilesToLoad()) {
11115                         this._loading = false;
11116                         // @event load: Event
11117                         // Fired when the grid layer loaded all visible tiles.
11118                         this.fire('load');
11119
11120                         if (ielt9 || !this._map._fadeAnimated) {
11121                                 requestAnimFrame(this._pruneTiles, this);
11122                         } else {
11123                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11124                                 // to trigger a pruning.
11125                                 setTimeout(bind(this._pruneTiles, this), 250);
11126                         }
11127                 }
11128         },
11129
11130         _getTilePos: function (coords) {
11131                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11132         },
11133
11134         _wrapCoords: function (coords) {
11135                 var newCoords = new Point(
11136                         this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11137                         this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11138                 newCoords.z = coords.z;
11139                 return newCoords;
11140         },
11141
11142         _pxBoundsToTileRange: function (bounds) {
11143                 var tileSize = this.getTileSize();
11144                 return new Bounds(
11145                         bounds.min.unscaleBy(tileSize).floor(),
11146                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11147         },
11148
11149         _noTilesToLoad: function () {
11150                 for (var key in this._tiles) {
11151                         if (!this._tiles[key].loaded) { return false; }
11152                 }
11153                 return true;
11154         }
11155 });
11156
11157 // @factory L.gridLayer(options?: GridLayer options)
11158 // Creates a new instance of GridLayer with the supplied options.
11159 function gridLayer(options) {
11160         return new GridLayer(options);
11161 }
11162
11163 /*\r
11164  * @class TileLayer\r
11165  * @inherits GridLayer\r
11166  * @aka L.TileLayer\r
11167  * Used to load and display tile layers on the map. Extends `GridLayer`.\r
11168  *\r
11169  * @example\r
11170  *\r
11171  * ```js\r
11172  * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);\r
11173  * ```\r
11174  *\r
11175  * @section URL template\r
11176  * @example\r
11177  *\r
11178  * A string of the following form:\r
11179  *\r
11180  * ```\r
11181  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'\r
11182  * ```\r
11183  *\r
11184  * `{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
11185  *\r
11186  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:\r
11187  *\r
11188  * ```\r
11189  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});\r
11190  * ```\r
11191  */\r
11192 \r
11193 \r
11194 var TileLayer = GridLayer.extend({\r
11195 \r
11196         // @section\r
11197         // @aka TileLayer options\r
11198         options: {\r
11199                 // @option minZoom: Number = 0\r
11200                 // The minimum zoom level down to which this layer will be displayed (inclusive).\r
11201                 minZoom: 0,\r
11202 \r
11203                 // @option maxZoom: Number = 18\r
11204                 // The maximum zoom level up to which this layer will be displayed (inclusive).\r
11205                 maxZoom: 18,\r
11206 \r
11207                 // @option subdomains: String|String[] = 'abc'\r
11208                 // 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
11209                 subdomains: 'abc',\r
11210 \r
11211                 // @option errorTileUrl: String = ''\r
11212                 // URL to the tile image to show in place of the tile that failed to load.\r
11213                 errorTileUrl: '',\r
11214 \r
11215                 // @option zoomOffset: Number = 0\r
11216                 // The zoom number used in tile URLs will be offset with this value.\r
11217                 zoomOffset: 0,\r
11218 \r
11219                 // @option tms: Boolean = false\r
11220                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).\r
11221                 tms: false,\r
11222 \r
11223                 // @option zoomReverse: Boolean = false\r
11224                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)\r
11225                 zoomReverse: false,\r
11226 \r
11227                 // @option detectRetina: Boolean = false\r
11228                 // 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
11229                 detectRetina: false,\r
11230 \r
11231                 // @option crossOrigin: Boolean = false\r
11232                 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.\r
11233                 crossOrigin: false\r
11234         },\r
11235 \r
11236         initialize: function (url, options) {\r
11237 \r
11238                 this._url = url;\r
11239 \r
11240                 options = setOptions(this, options);\r
11241 \r
11242                 // detecting retina displays, adjusting tileSize and zoom levels\r
11243                 if (options.detectRetina && retina && options.maxZoom > 0) {\r
11244 \r
11245                         options.tileSize = Math.floor(options.tileSize / 2);\r
11246 \r
11247                         if (!options.zoomReverse) {\r
11248                                 options.zoomOffset++;\r
11249                                 options.maxZoom--;\r
11250                         } else {\r
11251                                 options.zoomOffset--;\r
11252                                 options.minZoom++;\r
11253                         }\r
11254 \r
11255                         options.minZoom = Math.max(0, options.minZoom);\r
11256                 }\r
11257 \r
11258                 if (typeof options.subdomains === 'string') {\r
11259                         options.subdomains = options.subdomains.split('');\r
11260                 }\r
11261 \r
11262                 // for https://github.com/Leaflet/Leaflet/issues/137\r
11263                 if (!android) {\r
11264                         this.on('tileunload', this._onTileRemove);\r
11265                 }\r
11266         },\r
11267 \r
11268         // @method setUrl(url: String, noRedraw?: Boolean): this\r
11269         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).\r
11270         setUrl: function (url, noRedraw) {\r
11271                 this._url = url;\r
11272 \r
11273                 if (!noRedraw) {\r
11274                         this.redraw();\r
11275                 }\r
11276                 return this;\r
11277         },\r
11278 \r
11279         // @method createTile(coords: Object, done?: Function): HTMLElement\r
11280         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)\r
11281         // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`\r
11282         // callback is called when the tile has been loaded.\r
11283         createTile: function (coords, done) {\r
11284                 var tile = document.createElement('img');\r
11285 \r
11286                 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));\r
11287                 on(tile, 'error', bind(this._tileOnError, this, done, tile));\r
11288 \r
11289                 if (this.options.crossOrigin) {\r
11290                         tile.crossOrigin = '';\r
11291                 }\r
11292 \r
11293                 /*\r
11294                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons\r
11295                  http://www.w3.org/TR/WCAG20-TECHS/H67\r
11296                 */\r
11297                 tile.alt = '';\r
11298 \r
11299                 /*\r
11300                  Set role="presentation" to force screen readers to ignore this\r
11301                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation\r
11302                 */\r
11303                 tile.setAttribute('role', 'presentation');\r
11304 \r
11305                 tile.src = this.getTileUrl(coords);\r
11306 \r
11307                 return tile;\r
11308         },\r
11309 \r
11310         // @section Extension methods\r
11311         // @uninheritable\r
11312         // Layers extending `TileLayer` might reimplement the following method.\r
11313         // @method getTileUrl(coords: Object): String\r
11314         // Called only internally, returns the URL for a tile given its coordinates.\r
11315         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.\r
11316         getTileUrl: function (coords) {\r
11317                 var data = {\r
11318                         r: retina ? '@2x' : '',\r
11319                         s: this._getSubdomain(coords),\r
11320                         x: coords.x,\r
11321                         y: coords.y,\r
11322                         z: this._getZoomForUrl()\r
11323                 };\r
11324                 if (this._map && !this._map.options.crs.infinite) {\r
11325                         var invertedY = this._globalTileRange.max.y - coords.y;\r
11326                         if (this.options.tms) {\r
11327                                 data['y'] = invertedY;\r
11328                         }\r
11329                         data['-y'] = invertedY;\r
11330                 }\r
11331 \r
11332                 return template(this._url, extend(data, this.options));\r
11333         },\r
11334 \r
11335         _tileOnLoad: function (done, tile) {\r
11336                 // For https://github.com/Leaflet/Leaflet/issues/3332\r
11337                 if (ielt9) {\r
11338                         setTimeout(bind(done, this, null, tile), 0);\r
11339                 } else {\r
11340                         done(null, tile);\r
11341                 }\r
11342         },\r
11343 \r
11344         _tileOnError: function (done, tile, e) {\r
11345                 var errorUrl = this.options.errorTileUrl;\r
11346                 if (errorUrl && tile.src !== errorUrl) {\r
11347                         tile.src = errorUrl;\r
11348                 }\r
11349                 done(e, tile);\r
11350         },\r
11351 \r
11352         _onTileRemove: function (e) {\r
11353                 e.tile.onload = null;\r
11354         },\r
11355 \r
11356         _getZoomForUrl: function () {\r
11357                 var zoom = this._tileZoom,\r
11358                 maxZoom = this.options.maxZoom,\r
11359                 zoomReverse = this.options.zoomReverse,\r
11360                 zoomOffset = this.options.zoomOffset;\r
11361 \r
11362                 if (zoomReverse) {\r
11363                         zoom = maxZoom - zoom;\r
11364                 }\r
11365 \r
11366                 return zoom + zoomOffset;\r
11367         },\r
11368 \r
11369         _getSubdomain: function (tilePoint) {\r
11370                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;\r
11371                 return this.options.subdomains[index];\r
11372         },\r
11373 \r
11374         // stops loading all tiles in the background layer\r
11375         _abortLoading: function () {\r
11376                 var i, tile;\r
11377                 for (i in this._tiles) {\r
11378                         if (this._tiles[i].coords.z !== this._tileZoom) {\r
11379                                 tile = this._tiles[i].el;\r
11380 \r
11381                                 tile.onload = falseFn;\r
11382                                 tile.onerror = falseFn;\r
11383 \r
11384                                 if (!tile.complete) {\r
11385                                         tile.src = emptyImageUrl;\r
11386                                         remove(tile);\r
11387                                 }\r
11388                         }\r
11389                 }\r
11390         }\r
11391 });\r
11392 \r
11393 \r
11394 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)\r
11395 // Instantiates a tile layer object given a `URL template` and optionally an options object.\r
11396 \r
11397 function tileLayer(url, options) {\r
11398         return new TileLayer(url, options);\r
11399 }
11400
11401 /*\r
11402  * @class TileLayer.WMS\r
11403  * @inherits TileLayer\r
11404  * @aka L.TileLayer.WMS\r
11405  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.\r
11406  *\r
11407  * @example\r
11408  *\r
11409  * ```js\r
11410  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {\r
11411  *      layers: 'nexrad-n0r-900913',\r
11412  *      format: 'image/png',\r
11413  *      transparent: true,\r
11414  *      attribution: "Weather data © 2012 IEM Nexrad"\r
11415  * });\r
11416  * ```\r
11417  */\r
11418 \r
11419 var TileLayerWMS = TileLayer.extend({\r
11420 \r
11421         // @section\r
11422         // @aka TileLayer.WMS options\r
11423         // If any custom options not documented here are used, they will be sent to the\r
11424         // WMS server as extra parameters in each request URL. This can be useful for\r
11425         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).\r
11426         defaultWmsParams: {\r
11427                 service: 'WMS',\r
11428                 request: 'GetMap',\r
11429 \r
11430                 // @option layers: String = ''\r
11431                 // **(required)** Comma-separated list of WMS layers to show.\r
11432                 layers: '',\r
11433 \r
11434                 // @option styles: String = ''\r
11435                 // Comma-separated list of WMS styles.\r
11436                 styles: '',\r
11437 \r
11438                 // @option format: String = 'image/jpeg'\r
11439                 // WMS image format (use `'image/png'` for layers with transparency).\r
11440                 format: 'image/jpeg',\r
11441 \r
11442                 // @option transparent: Boolean = false\r
11443                 // If `true`, the WMS service will return images with transparency.\r
11444                 transparent: false,\r
11445 \r
11446                 // @option version: String = '1.1.1'\r
11447                 // Version of the WMS service to use\r
11448                 version: '1.1.1'\r
11449         },\r
11450 \r
11451         options: {\r
11452                 // @option crs: CRS = null\r
11453                 // Coordinate Reference System to use for the WMS requests, defaults to\r
11454                 // map CRS. Don't change this if you're not sure what it means.\r
11455                 crs: null,\r
11456 \r
11457                 // @option uppercase: Boolean = false\r
11458                 // If `true`, WMS request parameter keys will be uppercase.\r
11459                 uppercase: false\r
11460         },\r
11461 \r
11462         initialize: function (url, options) {\r
11463 \r
11464                 this._url = url;\r
11465 \r
11466                 var wmsParams = extend({}, this.defaultWmsParams);\r
11467 \r
11468                 // all keys that are not TileLayer options go to WMS params\r
11469                 for (var i in options) {\r
11470                         if (!(i in this.options)) {\r
11471                                 wmsParams[i] = options[i];\r
11472                         }\r
11473                 }\r
11474 \r
11475                 options = setOptions(this, options);\r
11476 \r
11477                 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && retina ? 2 : 1);\r
11478 \r
11479                 this.wmsParams = wmsParams;\r
11480         },\r
11481 \r
11482         onAdd: function (map) {\r
11483 \r
11484                 this._crs = this.options.crs || map.options.crs;\r
11485                 this._wmsVersion = parseFloat(this.wmsParams.version);\r
11486 \r
11487                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';\r
11488                 this.wmsParams[projectionKey] = this._crs.code;\r
11489 \r
11490                 TileLayer.prototype.onAdd.call(this, map);\r
11491         },\r
11492 \r
11493         getTileUrl: function (coords) {\r
11494 \r
11495                 var tileBounds = this._tileCoordsToBounds(coords),\r
11496                     nw = this._crs.project(tileBounds.getNorthWest()),\r
11497                     se = this._crs.project(tileBounds.getSouthEast()),\r
11498 \r
11499                     bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?\r
11500                             [se.y, nw.x, nw.y, se.x] :\r
11501                             [nw.x, se.y, se.x, nw.y]).join(','),\r
11502 \r
11503                     url = TileLayer.prototype.getTileUrl.call(this, coords);\r
11504 \r
11505                 return url +\r
11506                         getParamString(this.wmsParams, url, this.options.uppercase) +\r
11507                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;\r
11508         },\r
11509 \r
11510         // @method setParams(params: Object, noRedraw?: Boolean): this\r
11511         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).\r
11512         setParams: function (params, noRedraw) {\r
11513 \r
11514                 extend(this.wmsParams, params);\r
11515 \r
11516                 if (!noRedraw) {\r
11517                         this.redraw();\r
11518                 }\r
11519 \r
11520                 return this;\r
11521         }\r
11522 });\r
11523 \r
11524 \r
11525 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)\r
11526 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.\r
11527 function tileLayerWMS(url, options) {\r
11528         return new TileLayerWMS(url, options);\r
11529 }
11530
11531 TileLayer.WMS = TileLayerWMS;
11532 tileLayer.wms = tileLayerWMS;
11533
11534 /*
11535  * @class Renderer
11536  * @inherits Layer
11537  * @aka L.Renderer
11538  *
11539  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11540  * DOM container of the renderer, its bounds, and its zoom animation.
11541  *
11542  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11543  * itself can be added or removed to the map. All paths use a renderer, which can
11544  * be implicit (the map will decide the type of renderer and use it automatically)
11545  * or explicit (using the [`renderer`](#path-renderer) option of the path).
11546  *
11547  * Do not use this class directly, use `SVG` and `Canvas` instead.
11548  *
11549  * @event update: Event
11550  * Fired when the renderer updates its bounds, center and zoom, for example when
11551  * its map has moved
11552  */
11553
11554 var Renderer = Layer.extend({
11555
11556         // @section
11557         // @aka Renderer options
11558         options: {
11559                 // @option padding: Number = 0.1
11560                 // How much to extend the clip area around the map view (relative to its size)
11561                 // e.g. 0.1 would be 10% of map view in each direction
11562                 padding: 0.1
11563         },
11564
11565         initialize: function (options) {
11566                 setOptions(this, options);
11567                 stamp(this);
11568                 this._layers = this._layers || {};
11569         },
11570
11571         onAdd: function () {
11572                 if (!this._container) {
11573                         this._initContainer(); // defined by renderer implementations
11574
11575                         if (this._zoomAnimated) {
11576                                 addClass(this._container, 'leaflet-zoom-animated');
11577                         }
11578                 }
11579
11580                 this.getPane().appendChild(this._container);
11581                 this._update();
11582                 this.on('update', this._updatePaths, this);
11583         },
11584
11585         onRemove: function () {
11586                 this.off('update', this._updatePaths, this);
11587                 this._destroyContainer();
11588         },
11589
11590         getEvents: function () {
11591                 var events = {
11592                         viewreset: this._reset,
11593                         zoom: this._onZoom,
11594                         moveend: this._update,
11595                         zoomend: this._onZoomEnd
11596                 };
11597                 if (this._zoomAnimated) {
11598                         events.zoomanim = this._onAnimZoom;
11599                 }
11600                 return events;
11601         },
11602
11603         _onAnimZoom: function (ev) {
11604                 this._updateTransform(ev.center, ev.zoom);
11605         },
11606
11607         _onZoom: function () {
11608                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11609         },
11610
11611         _updateTransform: function (center, zoom) {
11612                 var scale = this._map.getZoomScale(zoom, this._zoom),
11613                     position = getPosition(this._container),
11614                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11615                     currentCenterPoint = this._map.project(this._center, zoom),
11616                     destCenterPoint = this._map.project(center, zoom),
11617                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
11618
11619                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11620
11621                 if (any3d) {
11622                         setTransform(this._container, topLeftOffset, scale);
11623                 } else {
11624                         setPosition(this._container, topLeftOffset);
11625                 }
11626         },
11627
11628         _reset: function () {
11629                 this._update();
11630                 this._updateTransform(this._center, this._zoom);
11631
11632                 for (var id in this._layers) {
11633                         this._layers[id]._reset();
11634                 }
11635         },
11636
11637         _onZoomEnd: function () {
11638                 for (var id in this._layers) {
11639                         this._layers[id]._project();
11640                 }
11641         },
11642
11643         _updatePaths: function () {
11644                 for (var id in this._layers) {
11645                         this._layers[id]._update();
11646                 }
11647         },
11648
11649         _update: function () {
11650                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11651                 // Subclasses are responsible of firing the 'update' event.
11652                 var p = this.options.padding,
11653                     size = this._map.getSize(),
11654                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11655
11656                 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11657
11658                 this._center = this._map.getCenter();
11659                 this._zoom = this._map.getZoom();
11660         }
11661 });
11662
11663 /*
11664  * @class Canvas
11665  * @inherits Renderer
11666  * @aka L.Canvas
11667  *
11668  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11669  * Inherits `Renderer`.
11670  *
11671  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11672  * available in all web browsers, notably IE8, and overlapping geometries might
11673  * not display properly in some edge cases.
11674  *
11675  * @example
11676  *
11677  * Use Canvas by default for all paths in the map:
11678  *
11679  * ```js
11680  * var map = L.map('map', {
11681  *      renderer: L.canvas()
11682  * });
11683  * ```
11684  *
11685  * Use a Canvas renderer with extra padding for specific vector geometries:
11686  *
11687  * ```js
11688  * var map = L.map('map');
11689  * var myRenderer = L.canvas({ padding: 0.5 });
11690  * var line = L.polyline( coordinates, { renderer: myRenderer } );
11691  * var circle = L.circle( center, { renderer: myRenderer } );
11692  * ```
11693  */
11694
11695 var Canvas = Renderer.extend({
11696         getEvents: function () {
11697                 var events = Renderer.prototype.getEvents.call(this);
11698                 events.viewprereset = this._onViewPreReset;
11699                 return events;
11700         },
11701
11702         _onViewPreReset: function () {
11703                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
11704                 this._postponeUpdatePaths = true;
11705         },
11706
11707         onAdd: function () {
11708                 Renderer.prototype.onAdd.call(this);
11709
11710                 // Redraw vectors since canvas is cleared upon removal,
11711                 // in case of removing the renderer itself from the map.
11712                 this._draw();
11713         },
11714
11715         _initContainer: function () {
11716                 var container = this._container = document.createElement('canvas');
11717
11718                 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
11719                 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
11720                 on(container, 'mouseout', this._handleMouseOut, this);
11721
11722                 this._ctx = container.getContext('2d');
11723         },
11724
11725         _destroyContainer: function () {
11726                 delete this._ctx;
11727                 remove(this._container);
11728                 off(this._container);
11729                 delete this._container;
11730         },
11731
11732         _updatePaths: function () {
11733                 if (this._postponeUpdatePaths) { return; }
11734
11735                 var layer;
11736                 this._redrawBounds = null;
11737                 for (var id in this._layers) {
11738                         layer = this._layers[id];
11739                         layer._update();
11740                 }
11741                 this._redraw();
11742         },
11743
11744         _update: function () {
11745                 if (this._map._animatingZoom && this._bounds) { return; }
11746
11747                 this._drawnLayers = {};
11748
11749                 Renderer.prototype._update.call(this);
11750
11751                 var b = this._bounds,
11752                     container = this._container,
11753                     size = b.getSize(),
11754                     m = retina ? 2 : 1;
11755
11756                 setPosition(container, b.min);
11757
11758                 // set canvas size (also clearing it); use double size on retina
11759                 container.width = m * size.x;
11760                 container.height = m * size.y;
11761                 container.style.width = size.x + 'px';
11762                 container.style.height = size.y + 'px';
11763
11764                 if (retina) {
11765                         this._ctx.scale(2, 2);
11766                 }
11767
11768                 // translate so we use the same path coordinates after canvas element moves
11769                 this._ctx.translate(-b.min.x, -b.min.y);
11770
11771                 // Tell paths to redraw themselves
11772                 this.fire('update');
11773         },
11774
11775         _reset: function () {
11776                 Renderer.prototype._reset.call(this);
11777
11778                 if (this._postponeUpdatePaths) {
11779                         this._postponeUpdatePaths = false;
11780                         this._updatePaths();
11781                 }
11782         },
11783
11784         _initPath: function (layer) {
11785                 this._updateDashArray(layer);
11786                 this._layers[stamp(layer)] = layer;
11787
11788                 var order = layer._order = {
11789                         layer: layer,
11790                         prev: this._drawLast,
11791                         next: null
11792                 };
11793                 if (this._drawLast) { this._drawLast.next = order; }
11794                 this._drawLast = order;
11795                 this._drawFirst = this._drawFirst || this._drawLast;
11796         },
11797
11798         _addPath: function (layer) {
11799                 this._requestRedraw(layer);
11800         },
11801
11802         _removePath: function (layer) {
11803                 var order = layer._order;
11804                 var next = order.next;
11805                 var prev = order.prev;
11806
11807                 if (next) {
11808                         next.prev = prev;
11809                 } else {
11810                         this._drawLast = prev;
11811                 }
11812                 if (prev) {
11813                         prev.next = next;
11814                 } else {
11815                         this._drawFirst = next;
11816                 }
11817
11818                 delete layer._order;
11819
11820                 delete this._layers[L.stamp(layer)];
11821
11822                 this._requestRedraw(layer);
11823         },
11824
11825         _updatePath: function (layer) {
11826                 // Redraw the union of the layer's old pixel
11827                 // bounds and the new pixel bounds.
11828                 this._extendRedrawBounds(layer);
11829                 layer._project();
11830                 layer._update();
11831                 // The redraw will extend the redraw bounds
11832                 // with the new pixel bounds.
11833                 this._requestRedraw(layer);
11834         },
11835
11836         _updateStyle: function (layer) {
11837                 this._updateDashArray(layer);
11838                 this._requestRedraw(layer);
11839         },
11840
11841         _updateDashArray: function (layer) {
11842                 if (layer.options.dashArray) {
11843                         var parts = layer.options.dashArray.split(','),
11844                             dashArray = [],
11845                             i;
11846                         for (i = 0; i < parts.length; i++) {
11847                                 dashArray.push(Number(parts[i]));
11848                         }
11849                         layer.options._dashArray = dashArray;
11850                 }
11851         },
11852
11853         _requestRedraw: function (layer) {
11854                 if (!this._map) { return; }
11855
11856                 this._extendRedrawBounds(layer);
11857                 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
11858         },
11859
11860         _extendRedrawBounds: function (layer) {
11861                 if (layer._pxBounds) {
11862                         var padding = (layer.options.weight || 0) + 1;
11863                         this._redrawBounds = this._redrawBounds || new Bounds();
11864                         this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
11865                         this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
11866                 }
11867         },
11868
11869         _redraw: function () {
11870                 this._redrawRequest = null;
11871
11872                 if (this._redrawBounds) {
11873                         this._redrawBounds.min._floor();
11874                         this._redrawBounds.max._ceil();
11875                 }
11876
11877                 this._clear(); // clear layers in redraw bounds
11878                 this._draw(); // draw layers
11879
11880                 this._redrawBounds = null;
11881         },
11882
11883         _clear: function () {
11884                 var bounds = this._redrawBounds;
11885                 if (bounds) {
11886                         var size = bounds.getSize();
11887                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
11888                 } else {
11889                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
11890                 }
11891         },
11892
11893         _draw: function () {
11894                 var layer, bounds = this._redrawBounds;
11895                 this._ctx.save();
11896                 if (bounds) {
11897                         var size = bounds.getSize();
11898                         this._ctx.beginPath();
11899                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
11900                         this._ctx.clip();
11901                 }
11902
11903                 this._drawing = true;
11904
11905                 for (var order = this._drawFirst; order; order = order.next) {
11906                         layer = order.layer;
11907                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
11908                                 layer._updatePath();
11909                         }
11910                 }
11911
11912                 this._drawing = false;
11913
11914                 this._ctx.restore();  // Restore state before clipping.
11915         },
11916
11917         _updatePoly: function (layer, closed) {
11918                 if (!this._drawing) { return; }
11919
11920                 var i, j, len2, p,
11921                     parts = layer._parts,
11922                     len = parts.length,
11923                     ctx = this._ctx;
11924
11925                 if (!len) { return; }
11926
11927                 this._drawnLayers[layer._leaflet_id] = layer;
11928
11929                 ctx.beginPath();
11930
11931                 for (i = 0; i < len; i++) {
11932                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
11933                                 p = parts[i][j];
11934                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
11935                         }
11936                         if (closed) {
11937                                 ctx.closePath();
11938                         }
11939                 }
11940
11941                 this._fillStroke(ctx, layer);
11942
11943                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
11944         },
11945
11946         _updateCircle: function (layer) {
11947
11948                 if (!this._drawing || layer._empty()) { return; }
11949
11950                 var p = layer._point,
11951                     ctx = this._ctx,
11952                     r = layer._radius,
11953                     s = (layer._radiusY || r) / r;
11954
11955                 this._drawnLayers[layer._leaflet_id] = layer;
11956
11957                 if (s !== 1) {
11958                         ctx.save();
11959                         ctx.scale(1, s);
11960                 }
11961
11962                 ctx.beginPath();
11963                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
11964
11965                 if (s !== 1) {
11966                         ctx.restore();
11967                 }
11968
11969                 this._fillStroke(ctx, layer);
11970         },
11971
11972         _fillStroke: function (ctx, layer) {
11973                 var options = layer.options;
11974
11975                 if (options.fill) {
11976                         ctx.globalAlpha = options.fillOpacity;
11977                         ctx.fillStyle = options.fillColor || options.color;
11978                         ctx.fill(options.fillRule || 'evenodd');
11979                 }
11980
11981                 if (options.stroke && options.weight !== 0) {
11982                         if (ctx.setLineDash) {
11983                                 ctx.setLineDash(layer.options && layer.options._dashArray || []);
11984                         }
11985                         ctx.globalAlpha = options.opacity;
11986                         ctx.lineWidth = options.weight;
11987                         ctx.strokeStyle = options.color;
11988                         ctx.lineCap = options.lineCap;
11989                         ctx.lineJoin = options.lineJoin;
11990                         ctx.stroke();
11991                 }
11992         },
11993
11994         // Canvas obviously doesn't have mouse events for individual drawn objects,
11995         // so we emulate that by calculating what's under the mouse on mousemove/click manually
11996
11997         _onClick: function (e) {
11998                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
11999
12000                 for (var order = this._drawFirst; order; order = order.next) {
12001                         layer = order.layer;
12002                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12003                                 clickedLayer = layer;
12004                         }
12005                 }
12006                 if (clickedLayer)  {
12007                         fakeStop(e);
12008                         this._fireEvent([clickedLayer], e);
12009                 }
12010         },
12011
12012         _onMouseMove: function (e) {
12013                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12014
12015                 var point = this._map.mouseEventToLayerPoint(e);
12016                 this._handleMouseHover(e, point);
12017         },
12018
12019
12020         _handleMouseOut: function (e) {
12021                 var layer = this._hoveredLayer;
12022                 if (layer) {
12023                         // if we're leaving the layer, fire mouseout
12024                         removeClass(this._container, 'leaflet-interactive');
12025                         this._fireEvent([layer], e, 'mouseout');
12026                         this._hoveredLayer = null;
12027                 }
12028         },
12029
12030         _handleMouseHover: function (e, point) {
12031                 var layer, candidateHoveredLayer;
12032
12033                 for (var order = this._drawFirst; order; order = order.next) {
12034                         layer = order.layer;
12035                         if (layer.options.interactive && layer._containsPoint(point)) {
12036                                 candidateHoveredLayer = layer;
12037                         }
12038                 }
12039
12040                 if (candidateHoveredLayer !== this._hoveredLayer) {
12041                         this._handleMouseOut(e);
12042
12043                         if (candidateHoveredLayer) {
12044                                 addClass(this._container, 'leaflet-interactive'); // change cursor
12045                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12046                                 this._hoveredLayer = candidateHoveredLayer;
12047                         }
12048                 }
12049
12050                 if (this._hoveredLayer) {
12051                         this._fireEvent([this._hoveredLayer], e);
12052                 }
12053         },
12054
12055         _fireEvent: function (layers, e, type) {
12056                 this._map._fireDOMEvent(e, type || e.type, layers);
12057         },
12058
12059         _bringToFront: function (layer) {
12060                 var order = layer._order;
12061                 var next = order.next;
12062                 var prev = order.prev;
12063
12064                 if (next) {
12065                         next.prev = prev;
12066                 } else {
12067                         // Already last
12068                         return;
12069                 }
12070                 if (prev) {
12071                         prev.next = next;
12072                 } else if (next) {
12073                         // Update first entry unless this is the
12074                         // signle entry
12075                         this._drawFirst = next;
12076                 }
12077
12078                 order.prev = this._drawLast;
12079                 this._drawLast.next = order;
12080
12081                 order.next = null;
12082                 this._drawLast = order;
12083
12084                 this._requestRedraw(layer);
12085         },
12086
12087         _bringToBack: function (layer) {
12088                 var order = layer._order;
12089                 var next = order.next;
12090                 var prev = order.prev;
12091
12092                 if (prev) {
12093                         prev.next = next;
12094                 } else {
12095                         // Already first
12096                         return;
12097                 }
12098                 if (next) {
12099                         next.prev = prev;
12100                 } else if (prev) {
12101                         // Update last entry unless this is the
12102                         // signle entry
12103                         this._drawLast = prev;
12104                 }
12105
12106                 order.prev = null;
12107
12108                 order.next = this._drawFirst;
12109                 this._drawFirst.prev = order;
12110                 this._drawFirst = order;
12111
12112                 this._requestRedraw(layer);
12113         }
12114 });
12115
12116 // @factory L.canvas(options?: Renderer options)
12117 // Creates a Canvas renderer with the given options.
12118 function canvas$1(options) {
12119         return canvas ? new Canvas(options) : null;
12120 }
12121
12122 /*
12123  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12124  */
12125
12126
12127 var vmlCreate = (function () {
12128         try {
12129                 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12130                 return function (name) {
12131                         return document.createElement('<lvml:' + name + ' class="lvml">');
12132                 };
12133         } catch (e) {
12134                 return function (name) {
12135                         return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12136                 };
12137         }
12138 })();
12139
12140
12141 /*
12142  * @class SVG
12143  *
12144  * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
12145  *
12146  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12147  * with old versions of Internet Explorer.
12148  */
12149
12150 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12151 var vmlMixin = {
12152
12153         _initContainer: function () {
12154                 this._container = create$1('div', 'leaflet-vml-container');
12155         },
12156
12157         _update: function () {
12158                 if (this._map._animatingZoom) { return; }
12159                 Renderer.prototype._update.call(this);
12160                 this.fire('update');
12161         },
12162
12163         _initPath: function (layer) {
12164                 var container = layer._container = vmlCreate('shape');
12165
12166                 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12167
12168                 container.coordsize = '1 1';
12169
12170                 layer._path = vmlCreate('path');
12171                 container.appendChild(layer._path);
12172
12173                 this._updateStyle(layer);
12174                 this._layers[stamp(layer)] = layer;
12175         },
12176
12177         _addPath: function (layer) {
12178                 var container = layer._container;
12179                 this._container.appendChild(container);
12180
12181                 if (layer.options.interactive) {
12182                         layer.addInteractiveTarget(container);
12183                 }
12184         },
12185
12186         _removePath: function (layer) {
12187                 var container = layer._container;
12188                 remove(container);
12189                 layer.removeInteractiveTarget(container);
12190                 delete this._layers[stamp(layer)];
12191         },
12192
12193         _updateStyle: function (layer) {
12194                 var stroke = layer._stroke,
12195                     fill = layer._fill,
12196                     options = layer.options,
12197                     container = layer._container;
12198
12199                 container.stroked = !!options.stroke;
12200                 container.filled = !!options.fill;
12201
12202                 if (options.stroke) {
12203                         if (!stroke) {
12204                                 stroke = layer._stroke = vmlCreate('stroke');
12205                         }
12206                         container.appendChild(stroke);
12207                         stroke.weight = options.weight + 'px';
12208                         stroke.color = options.color;
12209                         stroke.opacity = options.opacity;
12210
12211                         if (options.dashArray) {
12212                                 stroke.dashStyle = isArray(options.dashArray) ?
12213                                     options.dashArray.join(' ') :
12214                                     options.dashArray.replace(/( *, *)/g, ' ');
12215                         } else {
12216                                 stroke.dashStyle = '';
12217                         }
12218                         stroke.endcap = options.lineCap.replace('butt', 'flat');
12219                         stroke.joinstyle = options.lineJoin;
12220
12221                 } else if (stroke) {
12222                         container.removeChild(stroke);
12223                         layer._stroke = null;
12224                 }
12225
12226                 if (options.fill) {
12227                         if (!fill) {
12228                                 fill = layer._fill = vmlCreate('fill');
12229                         }
12230                         container.appendChild(fill);
12231                         fill.color = options.fillColor || options.color;
12232                         fill.opacity = options.fillOpacity;
12233
12234                 } else if (fill) {
12235                         container.removeChild(fill);
12236                         layer._fill = null;
12237                 }
12238         },
12239
12240         _updateCircle: function (layer) {
12241                 var p = layer._point.round(),
12242                     r = Math.round(layer._radius),
12243                     r2 = Math.round(layer._radiusY || r);
12244
12245                 this._setPath(layer, layer._empty() ? 'M0 0' :
12246                                 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12247         },
12248
12249         _setPath: function (layer, path) {
12250                 layer._path.v = path;
12251         },
12252
12253         _bringToFront: function (layer) {
12254                 toFront(layer._container);
12255         },
12256
12257         _bringToBack: function (layer) {
12258                 toBack(layer._container);
12259         }
12260 };
12261
12262 var create$2 = vml ? vmlCreate : svgCreate;
12263
12264 /*
12265  * @class SVG
12266  * @inherits Renderer
12267  * @aka L.SVG
12268  *
12269  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12270  * Inherits `Renderer`.
12271  *
12272  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12273  * available in all web browsers, notably Android 2.x and 3.x.
12274  *
12275  * Although SVG is not available on IE7 and IE8, these browsers support
12276  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12277  * (a now deprecated technology), and the SVG renderer will fall back to VML in
12278  * this case.
12279  *
12280  * @example
12281  *
12282  * Use SVG by default for all paths in the map:
12283  *
12284  * ```js
12285  * var map = L.map('map', {
12286  *      renderer: L.svg()
12287  * });
12288  * ```
12289  *
12290  * Use a SVG renderer with extra padding for specific vector geometries:
12291  *
12292  * ```js
12293  * var map = L.map('map');
12294  * var myRenderer = L.svg({ padding: 0.5 });
12295  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12296  * var circle = L.circle( center, { renderer: myRenderer } );
12297  * ```
12298  */
12299
12300 var SVG = Renderer.extend({
12301
12302         getEvents: function () {
12303                 var events = Renderer.prototype.getEvents.call(this);
12304                 events.zoomstart = this._onZoomStart;
12305                 return events;
12306         },
12307
12308         _initContainer: function () {
12309                 this._container = create$2('svg');
12310
12311                 // makes it possible to click through svg root; we'll reset it back in individual paths
12312                 this._container.setAttribute('pointer-events', 'none');
12313
12314                 this._rootGroup = create$2('g');
12315                 this._container.appendChild(this._rootGroup);
12316         },
12317
12318         _destroyContainer: function () {
12319                 remove(this._container);
12320                 off(this._container);
12321                 delete this._container;
12322                 delete this._rootGroup;
12323         },
12324
12325         _onZoomStart: function () {
12326                 // Drag-then-pinch interactions might mess up the center and zoom.
12327                 // In this case, the easiest way to prevent this is re-do the renderer
12328                 //   bounds and padding when the zooming starts.
12329                 this._update();
12330         },
12331
12332         _update: function () {
12333                 if (this._map._animatingZoom && this._bounds) { return; }
12334
12335                 Renderer.prototype._update.call(this);
12336
12337                 var b = this._bounds,
12338                     size = b.getSize(),
12339                     container = this._container;
12340
12341                 // set size of svg-container if changed
12342                 if (!this._svgSize || !this._svgSize.equals(size)) {
12343                         this._svgSize = size;
12344                         container.setAttribute('width', size.x);
12345                         container.setAttribute('height', size.y);
12346                 }
12347
12348                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12349                 setPosition(container, b.min);
12350                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12351
12352                 this.fire('update');
12353         },
12354
12355         // methods below are called by vector layers implementations
12356
12357         _initPath: function (layer) {
12358                 var path = layer._path = create$2('path');
12359
12360                 // @namespace Path
12361                 // @option className: String = null
12362                 // Custom class name set on an element. Only for SVG renderer.
12363                 if (layer.options.className) {
12364                         addClass(path, layer.options.className);
12365                 }
12366
12367                 if (layer.options.interactive) {
12368                         addClass(path, 'leaflet-interactive');
12369                 }
12370
12371                 this._updateStyle(layer);
12372                 this._layers[stamp(layer)] = layer;
12373         },
12374
12375         _addPath: function (layer) {
12376                 if (!this._rootGroup) { this._initContainer(); }
12377                 this._rootGroup.appendChild(layer._path);
12378                 layer.addInteractiveTarget(layer._path);
12379         },
12380
12381         _removePath: function (layer) {
12382                 remove(layer._path);
12383                 layer.removeInteractiveTarget(layer._path);
12384                 delete this._layers[stamp(layer)];
12385         },
12386
12387         _updatePath: function (layer) {
12388                 layer._project();
12389                 layer._update();
12390         },
12391
12392         _updateStyle: function (layer) {
12393                 var path = layer._path,
12394                     options = layer.options;
12395
12396                 if (!path) { return; }
12397
12398                 if (options.stroke) {
12399                         path.setAttribute('stroke', options.color);
12400                         path.setAttribute('stroke-opacity', options.opacity);
12401                         path.setAttribute('stroke-width', options.weight);
12402                         path.setAttribute('stroke-linecap', options.lineCap);
12403                         path.setAttribute('stroke-linejoin', options.lineJoin);
12404
12405                         if (options.dashArray) {
12406                                 path.setAttribute('stroke-dasharray', options.dashArray);
12407                         } else {
12408                                 path.removeAttribute('stroke-dasharray');
12409                         }
12410
12411                         if (options.dashOffset) {
12412                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
12413                         } else {
12414                                 path.removeAttribute('stroke-dashoffset');
12415                         }
12416                 } else {
12417                         path.setAttribute('stroke', 'none');
12418                 }
12419
12420                 if (options.fill) {
12421                         path.setAttribute('fill', options.fillColor || options.color);
12422                         path.setAttribute('fill-opacity', options.fillOpacity);
12423                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12424                 } else {
12425                         path.setAttribute('fill', 'none');
12426                 }
12427         },
12428
12429         _updatePoly: function (layer, closed) {
12430                 this._setPath(layer, pointsToPath(layer._parts, closed));
12431         },
12432
12433         _updateCircle: function (layer) {
12434                 var p = layer._point,
12435                     r = layer._radius,
12436                     r2 = layer._radiusY || r,
12437                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12438
12439                 // drawing a circle with two half-arcs
12440                 var d = layer._empty() ? 'M0 0' :
12441                                 'M' + (p.x - r) + ',' + p.y +
12442                                 arc + (r * 2) + ',0 ' +
12443                                 arc + (-r * 2) + ',0 ';
12444
12445                 this._setPath(layer, d);
12446         },
12447
12448         _setPath: function (layer, path) {
12449                 layer._path.setAttribute('d', path);
12450         },
12451
12452         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12453         _bringToFront: function (layer) {
12454                 toFront(layer._path);
12455         },
12456
12457         _bringToBack: function (layer) {
12458                 toBack(layer._path);
12459         }
12460 });
12461
12462 if (vml) {
12463         SVG.include(vmlMixin);
12464 }
12465
12466 // @factory L.svg(options?: Renderer options)
12467 // Creates a SVG renderer with the given options.
12468 function svg$1(options) {
12469         return svg || vml ? new SVG(options) : null;
12470 }
12471
12472 Map.include({
12473         // @namespace Map; @method getRenderer(layer: Path): Renderer
12474         // Returns the instance of `Renderer` that should be used to render the given
12475         // `Path`. It will ensure that the `renderer` options of the map and paths
12476         // are respected, and that the renderers do exist on the map.
12477         getRenderer: function (layer) {
12478                 // @namespace Path; @option renderer: Renderer
12479                 // Use this specific instance of `Renderer` for this path. Takes
12480                 // precedence over the map's [default renderer](#map-renderer).
12481                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12482
12483                 if (!renderer) {
12484                         // @namespace Map; @option preferCanvas: Boolean = false
12485                         // Whether `Path`s should be rendered on a `Canvas` renderer.
12486                         // By default, all `Path`s are rendered in a `SVG` renderer.
12487                         renderer = this._renderer = (this.options.preferCanvas && canvas$1()) || svg$1();
12488                 }
12489
12490                 if (!this.hasLayer(renderer)) {
12491                         this.addLayer(renderer);
12492                 }
12493                 return renderer;
12494         },
12495
12496         _getPaneRenderer: function (name) {
12497                 if (name === 'overlayPane' || name === undefined) {
12498                         return false;
12499                 }
12500
12501                 var renderer = this._paneRenderers[name];
12502                 if (renderer === undefined) {
12503                         renderer = (SVG && svg$1({pane: name})) || (Canvas && canvas$1({pane: name}));
12504                         this._paneRenderers[name] = renderer;
12505                 }
12506                 return renderer;
12507         }
12508 });
12509
12510 /*
12511  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12512  */
12513
12514 /*
12515  * @class Rectangle
12516  * @aka L.Retangle
12517  * @inherits Polygon
12518  *
12519  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12520  *
12521  * @example
12522  *
12523  * ```js
12524  * // define rectangle geographical bounds
12525  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12526  *
12527  * // create an orange rectangle
12528  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12529  *
12530  * // zoom the map to the rectangle bounds
12531  * map.fitBounds(bounds);
12532  * ```
12533  *
12534  */
12535
12536
12537 var Rectangle = Polygon.extend({
12538         initialize: function (latLngBounds, options) {
12539                 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12540         },
12541
12542         // @method setBounds(latLngBounds: LatLngBounds): this
12543         // Redraws the rectangle with the passed bounds.
12544         setBounds: function (latLngBounds) {
12545                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12546         },
12547
12548         _boundsToLatLngs: function (latLngBounds) {
12549                 latLngBounds = toLatLngBounds(latLngBounds);
12550                 return [
12551                         latLngBounds.getSouthWest(),
12552                         latLngBounds.getNorthWest(),
12553                         latLngBounds.getNorthEast(),
12554                         latLngBounds.getSouthEast()
12555                 ];
12556         }
12557 });
12558
12559
12560 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12561 function rectangle(latLngBounds, options) {
12562         return new Rectangle(latLngBounds, options);
12563 }
12564
12565 SVG.create = create$2;
12566 SVG.pointsToPath = pointsToPath;
12567
12568 GeoJSON.geometryToLayer = geometryToLayer;
12569 GeoJSON.coordsToLatLng = coordsToLatLng;
12570 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12571 GeoJSON.latLngToCoords = latLngToCoords;
12572 GeoJSON.latLngsToCoords = latLngsToCoords;
12573 GeoJSON.getFeature = getFeature;
12574 GeoJSON.asFeature = asFeature;
12575
12576 /*
12577  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12578  * (zoom to a selected bounding box), enabled by default.
12579  */
12580
12581 // @namespace Map
12582 // @section Interaction Options
12583 Map.mergeOptions({
12584         // @option boxZoom: Boolean = true
12585         // Whether the map can be zoomed to a rectangular area specified by
12586         // dragging the mouse while pressing the shift key.
12587         boxZoom: true
12588 });
12589
12590 var BoxZoom = Handler.extend({
12591         initialize: function (map) {
12592                 this._map = map;
12593                 this._container = map._container;
12594                 this._pane = map._panes.overlayPane;
12595                 this._resetStateTimeout = 0;
12596                 map.on('unload', this._destroy, this);
12597         },
12598
12599         addHooks: function () {
12600                 on(this._container, 'mousedown', this._onMouseDown, this);
12601         },
12602
12603         removeHooks: function () {
12604                 off(this._container, 'mousedown', this._onMouseDown, this);
12605         },
12606
12607         moved: function () {
12608                 return this._moved;
12609         },
12610
12611         _destroy: function () {
12612                 remove(this._pane);
12613                 delete this._pane;
12614         },
12615
12616         _resetState: function () {
12617                 this._resetStateTimeout = 0;
12618                 this._moved = false;
12619         },
12620
12621         _clearDeferredResetState: function () {
12622                 if (this._resetStateTimeout !== 0) {
12623                         clearTimeout(this._resetStateTimeout);
12624                         this._resetStateTimeout = 0;
12625                 }
12626         },
12627
12628         _onMouseDown: function (e) {
12629                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12630
12631                 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12632                 // will interrupt the interaction and orphan a box element in the container.
12633                 this._clearDeferredResetState();
12634                 this._resetState();
12635
12636                 disableTextSelection();
12637                 disableImageDrag();
12638
12639                 this._startPoint = this._map.mouseEventToContainerPoint(e);
12640
12641                 on(document, {
12642                         contextmenu: stop,
12643                         mousemove: this._onMouseMove,
12644                         mouseup: this._onMouseUp,
12645                         keydown: this._onKeyDown
12646                 }, this);
12647         },
12648
12649         _onMouseMove: function (e) {
12650                 if (!this._moved) {
12651                         this._moved = true;
12652
12653                         this._box = create$1('div', 'leaflet-zoom-box', this._container);
12654                         addClass(this._container, 'leaflet-crosshair');
12655
12656                         this._map.fire('boxzoomstart');
12657                 }
12658
12659                 this._point = this._map.mouseEventToContainerPoint(e);
12660
12661                 var bounds = new Bounds(this._point, this._startPoint),
12662                     size = bounds.getSize();
12663
12664                 setPosition(this._box, bounds.min);
12665
12666                 this._box.style.width  = size.x + 'px';
12667                 this._box.style.height = size.y + 'px';
12668         },
12669
12670         _finish: function () {
12671                 if (this._moved) {
12672                         remove(this._box);
12673                         removeClass(this._container, 'leaflet-crosshair');
12674                 }
12675
12676                 enableTextSelection();
12677                 enableImageDrag();
12678
12679                 off(document, {
12680                         contextmenu: stop,
12681                         mousemove: this._onMouseMove,
12682                         mouseup: this._onMouseUp,
12683                         keydown: this._onKeyDown
12684                 }, this);
12685         },
12686
12687         _onMouseUp: function (e) {
12688                 if ((e.which !== 1) && (e.button !== 1)) { return; }
12689
12690                 this._finish();
12691
12692                 if (!this._moved) { return; }
12693                 // Postpone to next JS tick so internal click event handling
12694                 // still see it as "moved".
12695                 this._clearDeferredResetState();
12696                 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
12697
12698                 var bounds = new LatLngBounds(
12699                         this._map.containerPointToLatLng(this._startPoint),
12700                         this._map.containerPointToLatLng(this._point));
12701
12702                 this._map
12703                         .fitBounds(bounds)
12704                         .fire('boxzoomend', {boxZoomBounds: bounds});
12705         },
12706
12707         _onKeyDown: function (e) {
12708                 if (e.keyCode === 27) {
12709                         this._finish();
12710                 }
12711         }
12712 });
12713
12714 // @section Handlers
12715 // @property boxZoom: Handler
12716 // Box (shift-drag with mouse) zoom handler.
12717 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
12718
12719 /*
12720  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
12721  */
12722
12723 // @namespace Map
12724 // @section Interaction Options
12725
12726 Map.mergeOptions({
12727         // @option doubleClickZoom: Boolean|String = true
12728         // Whether the map can be zoomed in by double clicking on it and
12729         // zoomed out by double clicking while holding shift. If passed
12730         // `'center'`, double-click zoom will zoom to the center of the
12731         //  view regardless of where the mouse was.
12732         doubleClickZoom: true
12733 });
12734
12735 var DoubleClickZoom = Handler.extend({
12736         addHooks: function () {
12737                 this._map.on('dblclick', this._onDoubleClick, this);
12738         },
12739
12740         removeHooks: function () {
12741                 this._map.off('dblclick', this._onDoubleClick, this);
12742         },
12743
12744         _onDoubleClick: function (e) {
12745                 var map = this._map,
12746                     oldZoom = map.getZoom(),
12747                     delta = map.options.zoomDelta,
12748                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
12749
12750                 if (map.options.doubleClickZoom === 'center') {
12751                         map.setZoom(zoom);
12752                 } else {
12753                         map.setZoomAround(e.containerPoint, zoom);
12754                 }
12755         }
12756 });
12757
12758 // @section Handlers
12759 //
12760 // Map properties include interaction handlers that allow you to control
12761 // interaction behavior in runtime, enabling or disabling certain features such
12762 // as dragging or touch zoom (see `Handler` methods). For example:
12763 //
12764 // ```js
12765 // map.doubleClickZoom.disable();
12766 // ```
12767 //
12768 // @property doubleClickZoom: Handler
12769 // Double click zoom handler.
12770 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
12771
12772 /*
12773  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
12774  */
12775
12776 // @namespace Map
12777 // @section Interaction Options
12778 Map.mergeOptions({
12779         // @option dragging: Boolean = true
12780         // Whether the map be draggable with mouse/touch or not.
12781         dragging: true,
12782
12783         // @section Panning Inertia Options
12784         // @option inertia: Boolean = *
12785         // If enabled, panning of the map will have an inertia effect where
12786         // the map builds momentum while dragging and continues moving in
12787         // the same direction for some time. Feels especially nice on touch
12788         // devices. Enabled by default unless running on old Android devices.
12789         inertia: !android23,
12790
12791         // @option inertiaDeceleration: Number = 3000
12792         // The rate with which the inertial movement slows down, in pixels/second².
12793         inertiaDeceleration: 3400, // px/s^2
12794
12795         // @option inertiaMaxSpeed: Number = Infinity
12796         // Max speed of the inertial movement, in pixels/second.
12797         inertiaMaxSpeed: Infinity, // px/s
12798
12799         // @option easeLinearity: Number = 0.2
12800         easeLinearity: 0.2,
12801
12802         // TODO refactor, move to CRS
12803         // @option worldCopyJump: Boolean = false
12804         // With this option enabled, the map tracks when you pan to another "copy"
12805         // of the world and seamlessly jumps to the original one so that all overlays
12806         // like markers and vector layers are still visible.
12807         worldCopyJump: false,
12808
12809         // @option maxBoundsViscosity: Number = 0.0
12810         // If `maxBounds` is set, this option will control how solid the bounds
12811         // are when dragging the map around. The default value of `0.0` allows the
12812         // user to drag outside the bounds at normal speed, higher values will
12813         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
12814         // solid, preventing the user from dragging outside the bounds.
12815         maxBoundsViscosity: 0.0
12816 });
12817
12818 var Drag = Handler.extend({
12819         addHooks: function () {
12820                 if (!this._draggable) {
12821                         var map = this._map;
12822
12823                         this._draggable = new Draggable(map._mapPane, map._container);
12824
12825                         this._draggable.on({
12826                                 dragstart: this._onDragStart,
12827                                 drag: this._onDrag,
12828                                 dragend: this._onDragEnd
12829                         }, this);
12830
12831                         this._draggable.on('predrag', this._onPreDragLimit, this);
12832                         if (map.options.worldCopyJump) {
12833                                 this._draggable.on('predrag', this._onPreDragWrap, this);
12834                                 map.on('zoomend', this._onZoomEnd, this);
12835
12836                                 map.whenReady(this._onZoomEnd, this);
12837                         }
12838                 }
12839                 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
12840                 this._draggable.enable();
12841                 this._positions = [];
12842                 this._times = [];
12843         },
12844
12845         removeHooks: function () {
12846                 removeClass(this._map._container, 'leaflet-grab');
12847                 removeClass(this._map._container, 'leaflet-touch-drag');
12848                 this._draggable.disable();
12849         },
12850
12851         moved: function () {
12852                 return this._draggable && this._draggable._moved;
12853         },
12854
12855         moving: function () {
12856                 return this._draggable && this._draggable._moving;
12857         },
12858
12859         _onDragStart: function () {
12860                 var map = this._map;
12861
12862                 map._stop();
12863                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
12864                         var bounds = toLatLngBounds(this._map.options.maxBounds);
12865
12866                         this._offsetLimit = toBounds(
12867                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
12868                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
12869                                         .add(this._map.getSize()));
12870
12871                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
12872                 } else {
12873                         this._offsetLimit = null;
12874                 }
12875
12876                 map
12877                     .fire('movestart')
12878                     .fire('dragstart');
12879
12880                 if (map.options.inertia) {
12881                         this._positions = [];
12882                         this._times = [];
12883                 }
12884         },
12885
12886         _onDrag: function (e) {
12887                 if (this._map.options.inertia) {
12888                         var time = this._lastTime = +new Date(),
12889                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
12890
12891                         this._positions.push(pos);
12892                         this._times.push(time);
12893
12894                         if (time - this._times[0] > 50) {
12895                                 this._positions.shift();
12896                                 this._times.shift();
12897                         }
12898                 }
12899
12900                 this._map
12901                     .fire('move', e)
12902                     .fire('drag', e);
12903         },
12904
12905         _onZoomEnd: function () {
12906                 var pxCenter = this._map.getSize().divideBy(2),
12907                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
12908
12909                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
12910                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
12911         },
12912
12913         _viscousLimit: function (value, threshold) {
12914                 return value - (value - threshold) * this._viscosity;
12915         },
12916
12917         _onPreDragLimit: function () {
12918                 if (!this._viscosity || !this._offsetLimit) { return; }
12919
12920                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
12921
12922                 var limit = this._offsetLimit;
12923                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
12924                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
12925                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
12926                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
12927
12928                 this._draggable._newPos = this._draggable._startPos.add(offset);
12929         },
12930
12931         _onPreDragWrap: function () {
12932                 // TODO refactor to be able to adjust map pane position after zoom
12933                 var worldWidth = this._worldWidth,
12934                     halfWidth = Math.round(worldWidth / 2),
12935                     dx = this._initialWorldOffset,
12936                     x = this._draggable._newPos.x,
12937                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
12938                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
12939                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
12940
12941                 this._draggable._absPos = this._draggable._newPos.clone();
12942                 this._draggable._newPos.x = newX;
12943         },
12944
12945         _onDragEnd: function (e) {
12946                 var map = this._map,
12947                     options = map.options,
12948
12949                     noInertia = !options.inertia || this._times.length < 2;
12950
12951                 map.fire('dragend', e);
12952
12953                 if (noInertia) {
12954                         map.fire('moveend');
12955
12956                 } else {
12957
12958                         var direction = this._lastPos.subtract(this._positions[0]),
12959                             duration = (this._lastTime - this._times[0]) / 1000,
12960                             ease = options.easeLinearity,
12961
12962                             speedVector = direction.multiplyBy(ease / duration),
12963                             speed = speedVector.distanceTo([0, 0]),
12964
12965                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
12966                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
12967
12968                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
12969                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
12970
12971                         if (!offset.x && !offset.y) {
12972                                 map.fire('moveend');
12973
12974                         } else {
12975                                 offset = map._limitOffset(offset, map.options.maxBounds);
12976
12977                                 requestAnimFrame(function () {
12978                                         map.panBy(offset, {
12979                                                 duration: decelerationDuration,
12980                                                 easeLinearity: ease,
12981                                                 noMoveStart: true,
12982                                                 animate: true
12983                                         });
12984                                 });
12985                         }
12986                 }
12987         }
12988 });
12989
12990 // @section Handlers
12991 // @property dragging: Handler
12992 // Map dragging handler (by both mouse and touch).
12993 Map.addInitHook('addHandler', 'dragging', Drag);
12994
12995 /*
12996  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
12997  */
12998
12999 // @namespace Map
13000 // @section Keyboard Navigation Options
13001 Map.mergeOptions({
13002         // @option keyboard: Boolean = true
13003         // Makes the map focusable and allows users to navigate the map with keyboard
13004         // arrows and `+`/`-` keys.
13005         keyboard: true,
13006
13007         // @option keyboardPanDelta: Number = 80
13008         // Amount of pixels to pan when pressing an arrow key.
13009         keyboardPanDelta: 80
13010 });
13011
13012 var Keyboard = Handler.extend({
13013
13014         keyCodes: {
13015                 left:    [37],
13016                 right:   [39],
13017                 down:    [40],
13018                 up:      [38],
13019                 zoomIn:  [187, 107, 61, 171],
13020                 zoomOut: [189, 109, 54, 173]
13021         },
13022
13023         initialize: function (map) {
13024                 this._map = map;
13025
13026                 this._setPanDelta(map.options.keyboardPanDelta);
13027                 this._setZoomDelta(map.options.zoomDelta);
13028         },
13029
13030         addHooks: function () {
13031                 var container = this._map._container;
13032
13033                 // make the container focusable by tabbing
13034                 if (container.tabIndex <= 0) {
13035                         container.tabIndex = '0';
13036                 }
13037
13038                 on(container, {
13039                         focus: this._onFocus,
13040                         blur: this._onBlur,
13041                         mousedown: this._onMouseDown
13042                 }, this);
13043
13044                 this._map.on({
13045                         focus: this._addHooks,
13046                         blur: this._removeHooks
13047                 }, this);
13048         },
13049
13050         removeHooks: function () {
13051                 this._removeHooks();
13052
13053                 off(this._map._container, {
13054                         focus: this._onFocus,
13055                         blur: this._onBlur,
13056                         mousedown: this._onMouseDown
13057                 }, this);
13058
13059                 this._map.off({
13060                         focus: this._addHooks,
13061                         blur: this._removeHooks
13062                 }, this);
13063         },
13064
13065         _onMouseDown: function () {
13066                 if (this._focused) { return; }
13067
13068                 var body = document.body,
13069                     docEl = document.documentElement,
13070                     top = body.scrollTop || docEl.scrollTop,
13071                     left = body.scrollLeft || docEl.scrollLeft;
13072
13073                 this._map._container.focus();
13074
13075                 window.scrollTo(left, top);
13076         },
13077
13078         _onFocus: function () {
13079                 this._focused = true;
13080                 this._map.fire('focus');
13081         },
13082
13083         _onBlur: function () {
13084                 this._focused = false;
13085                 this._map.fire('blur');
13086         },
13087
13088         _setPanDelta: function (panDelta) {
13089                 var keys = this._panKeys = {},
13090                     codes = this.keyCodes,
13091                     i, len;
13092
13093                 for (i = 0, len = codes.left.length; i < len; i++) {
13094                         keys[codes.left[i]] = [-1 * panDelta, 0];
13095                 }
13096                 for (i = 0, len = codes.right.length; i < len; i++) {
13097                         keys[codes.right[i]] = [panDelta, 0];
13098                 }
13099                 for (i = 0, len = codes.down.length; i < len; i++) {
13100                         keys[codes.down[i]] = [0, panDelta];
13101                 }
13102                 for (i = 0, len = codes.up.length; i < len; i++) {
13103                         keys[codes.up[i]] = [0, -1 * panDelta];
13104                 }
13105         },
13106
13107         _setZoomDelta: function (zoomDelta) {
13108                 var keys = this._zoomKeys = {},
13109                     codes = this.keyCodes,
13110                     i, len;
13111
13112                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13113                         keys[codes.zoomIn[i]] = zoomDelta;
13114                 }
13115                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13116                         keys[codes.zoomOut[i]] = -zoomDelta;
13117                 }
13118         },
13119
13120         _addHooks: function () {
13121                 on(document, 'keydown', this._onKeyDown, this);
13122         },
13123
13124         _removeHooks: function () {
13125                 off(document, 'keydown', this._onKeyDown, this);
13126         },
13127
13128         _onKeyDown: function (e) {
13129                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13130
13131                 var key = e.keyCode,
13132                     map = this._map,
13133                     offset;
13134
13135                 if (key in this._panKeys) {
13136
13137                         if (map._panAnim && map._panAnim._inProgress) { return; }
13138
13139                         offset = this._panKeys[key];
13140                         if (e.shiftKey) {
13141                                 offset = toPoint(offset).multiplyBy(3);
13142                         }
13143
13144                         map.panBy(offset);
13145
13146                         if (map.options.maxBounds) {
13147                                 map.panInsideBounds(map.options.maxBounds);
13148                         }
13149
13150                 } else if (key in this._zoomKeys) {
13151                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13152
13153                 } else if (key === 27 && map._popup) {
13154                         map.closePopup();
13155
13156                 } else {
13157                         return;
13158                 }
13159
13160                 stop(e);
13161         }
13162 });
13163
13164 // @section Handlers
13165 // @section Handlers
13166 // @property keyboard: Handler
13167 // Keyboard navigation handler.
13168 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13169
13170 /*
13171  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13172  */
13173
13174 // @namespace Map
13175 // @section Interaction Options
13176 Map.mergeOptions({
13177         // @section Mousewheel options
13178         // @option scrollWheelZoom: Boolean|String = true
13179         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13180         // it will zoom to the center of the view regardless of where the mouse was.
13181         scrollWheelZoom: true,
13182
13183         // @option wheelDebounceTime: Number = 40
13184         // Limits the rate at which a wheel can fire (in milliseconds). By default
13185         // user can't zoom via wheel more often than once per 40 ms.
13186         wheelDebounceTime: 40,
13187
13188         // @option wheelPxPerZoomLevel: Number = 60
13189         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13190         // mean a change of one full zoom level. Smaller values will make wheel-zooming
13191         // faster (and vice versa).
13192         wheelPxPerZoomLevel: 60
13193 });
13194
13195 var ScrollWheelZoom = Handler.extend({
13196         addHooks: function () {
13197                 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13198
13199                 this._delta = 0;
13200         },
13201
13202         removeHooks: function () {
13203                 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13204         },
13205
13206         _onWheelScroll: function (e) {
13207                 var delta = getWheelDelta(e);
13208
13209                 var debounce = this._map.options.wheelDebounceTime;
13210
13211                 this._delta += delta;
13212                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13213
13214                 if (!this._startTime) {
13215                         this._startTime = +new Date();
13216                 }
13217
13218                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13219
13220                 clearTimeout(this._timer);
13221                 this._timer = setTimeout(bind(this._performZoom, this), left);
13222
13223                 stop(e);
13224         },
13225
13226         _performZoom: function () {
13227                 var map = this._map,
13228                     zoom = map.getZoom(),
13229                     snap = this._map.options.zoomSnap || 0;
13230
13231                 map._stop(); // stop panning and fly animations if any
13232
13233                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13234                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13235                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13236                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13237                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13238
13239                 this._delta = 0;
13240                 this._startTime = null;
13241
13242                 if (!delta) { return; }
13243
13244                 if (map.options.scrollWheelZoom === 'center') {
13245                         map.setZoom(zoom + delta);
13246                 } else {
13247                         map.setZoomAround(this._lastMousePos, zoom + delta);
13248                 }
13249         }
13250 });
13251
13252 // @section Handlers
13253 // @property scrollWheelZoom: Handler
13254 // Scroll wheel zoom handler.
13255 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13256
13257 /*
13258  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13259  */
13260
13261 // @namespace Map
13262 // @section Interaction Options
13263 Map.mergeOptions({
13264         // @section Touch interaction options
13265         // @option tap: Boolean = true
13266         // Enables mobile hacks for supporting instant taps (fixing 200ms click
13267         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13268         tap: true,
13269
13270         // @option tapTolerance: Number = 15
13271         // The max number of pixels a user can shift his finger during touch
13272         // for it to be considered a valid tap.
13273         tapTolerance: 15
13274 });
13275
13276 var Tap = Handler.extend({
13277         addHooks: function () {
13278                 on(this._map._container, 'touchstart', this._onDown, this);
13279         },
13280
13281         removeHooks: function () {
13282                 off(this._map._container, 'touchstart', this._onDown, this);
13283         },
13284
13285         _onDown: function (e) {
13286                 if (!e.touches) { return; }
13287
13288                 preventDefault(e);
13289
13290                 this._fireClick = true;
13291
13292                 // don't simulate click or track longpress if more than 1 touch
13293                 if (e.touches.length > 1) {
13294                         this._fireClick = false;
13295                         clearTimeout(this._holdTimeout);
13296                         return;
13297                 }
13298
13299                 var first = e.touches[0],
13300                     el = first.target;
13301
13302                 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13303
13304                 // if touching a link, highlight it
13305                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13306                         addClass(el, 'leaflet-active');
13307                 }
13308
13309                 // simulate long hold but setting a timeout
13310                 this._holdTimeout = setTimeout(bind(function () {
13311                         if (this._isTapValid()) {
13312                                 this._fireClick = false;
13313                                 this._onUp();
13314                                 this._simulateEvent('contextmenu', first);
13315                         }
13316                 }, this), 1000);
13317
13318                 this._simulateEvent('mousedown', first);
13319
13320                 on(document, {
13321                         touchmove: this._onMove,
13322                         touchend: this._onUp
13323                 }, this);
13324         },
13325
13326         _onUp: function (e) {
13327                 clearTimeout(this._holdTimeout);
13328
13329                 off(document, {
13330                         touchmove: this._onMove,
13331                         touchend: this._onUp
13332                 }, this);
13333
13334                 if (this._fireClick && e && e.changedTouches) {
13335
13336                         var first = e.changedTouches[0],
13337                             el = first.target;
13338
13339                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13340                                 removeClass(el, 'leaflet-active');
13341                         }
13342
13343                         this._simulateEvent('mouseup', first);
13344
13345                         // simulate click if the touch didn't move too much
13346                         if (this._isTapValid()) {
13347                                 this._simulateEvent('click', first);
13348                         }
13349                 }
13350         },
13351
13352         _isTapValid: function () {
13353                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13354         },
13355
13356         _onMove: function (e) {
13357                 var first = e.touches[0];
13358                 this._newPos = new Point(first.clientX, first.clientY);
13359                 this._simulateEvent('mousemove', first);
13360         },
13361
13362         _simulateEvent: function (type, e) {
13363                 var simulatedEvent = document.createEvent('MouseEvents');
13364
13365                 simulatedEvent._simulated = true;
13366                 e.target._simulatedClick = true;
13367
13368                 simulatedEvent.initMouseEvent(
13369                         type, true, true, window, 1,
13370                         e.screenX, e.screenY,
13371                         e.clientX, e.clientY,
13372                         false, false, false, false, 0, null);
13373
13374                 e.target.dispatchEvent(simulatedEvent);
13375         }
13376 });
13377
13378 // @section Handlers
13379 // @property tap: Handler
13380 // Mobile touch hacks (quick tap and touch hold) handler.
13381 if (touch && !pointer) {
13382         Map.addInitHook('addHandler', 'tap', Tap);
13383 }
13384
13385 /*
13386  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13387  */
13388
13389 // @namespace Map
13390 // @section Interaction Options
13391 Map.mergeOptions({
13392         // @section Touch interaction options
13393         // @option touchZoom: Boolean|String = *
13394         // Whether the map can be zoomed by touch-dragging with two fingers. If
13395         // passed `'center'`, it will zoom to the center of the view regardless of
13396         // where the touch events (fingers) were. Enabled for touch-capable web
13397         // browsers except for old Androids.
13398         touchZoom: touch && !android23,
13399
13400         // @option bounceAtZoomLimits: Boolean = true
13401         // Set it to false if you don't want the map to zoom beyond min/max zoom
13402         // and then bounce back when pinch-zooming.
13403         bounceAtZoomLimits: true
13404 });
13405
13406 var TouchZoom = Handler.extend({
13407         addHooks: function () {
13408                 addClass(this._map._container, 'leaflet-touch-zoom');
13409                 on(this._map._container, 'touchstart', this._onTouchStart, this);
13410         },
13411
13412         removeHooks: function () {
13413                 removeClass(this._map._container, 'leaflet-touch-zoom');
13414                 off(this._map._container, 'touchstart', this._onTouchStart, this);
13415         },
13416
13417         _onTouchStart: function (e) {
13418                 var map = this._map;
13419                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13420
13421                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13422                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
13423
13424                 this._centerPoint = map.getSize()._divideBy(2);
13425                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13426                 if (map.options.touchZoom !== 'center') {
13427                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13428                 }
13429
13430                 this._startDist = p1.distanceTo(p2);
13431                 this._startZoom = map.getZoom();
13432
13433                 this._moved = false;
13434                 this._zooming = true;
13435
13436                 map._stop();
13437
13438                 on(document, 'touchmove', this._onTouchMove, this);
13439                 on(document, 'touchend', this._onTouchEnd, this);
13440
13441                 preventDefault(e);
13442         },
13443
13444         _onTouchMove: function (e) {
13445                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13446
13447                 var map = this._map,
13448                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
13449                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
13450                     scale = p1.distanceTo(p2) / this._startDist;
13451
13452                 this._zoom = map.getScaleZoom(scale, this._startZoom);
13453
13454                 if (!map.options.bounceAtZoomLimits && (
13455                         (this._zoom < map.getMinZoom() && scale < 1) ||
13456                         (this._zoom > map.getMaxZoom() && scale > 1))) {
13457                         this._zoom = map._limitZoom(this._zoom);
13458                 }
13459
13460                 if (map.options.touchZoom === 'center') {
13461                         this._center = this._startLatLng;
13462                         if (scale === 1) { return; }
13463                 } else {
13464                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13465                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13466                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13467                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13468                 }
13469
13470                 if (!this._moved) {
13471                         map._moveStart(true);
13472                         this._moved = true;
13473                 }
13474
13475                 cancelAnimFrame(this._animRequest);
13476
13477                 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13478                 this._animRequest = requestAnimFrame(moveFn, this, true);
13479
13480                 preventDefault(e);
13481         },
13482
13483         _onTouchEnd: function () {
13484                 if (!this._moved || !this._zooming) {
13485                         this._zooming = false;
13486                         return;
13487                 }
13488
13489                 this._zooming = false;
13490                 cancelAnimFrame(this._animRequest);
13491
13492                 off(document, 'touchmove', this._onTouchMove);
13493                 off(document, 'touchend', this._onTouchEnd);
13494
13495                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13496                 if (this._map.options.zoomAnimation) {
13497                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13498                 } else {
13499                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13500                 }
13501         }
13502 });
13503
13504 // @section Handlers
13505 // @property touchZoom: Handler
13506 // Touch zoom handler.
13507 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13508
13509 Map.BoxZoom = BoxZoom;
13510 Map.DoubleClickZoom = DoubleClickZoom;
13511 Map.Drag = Drag;
13512 Map.Keyboard = Keyboard;
13513 Map.ScrollWheelZoom = ScrollWheelZoom;
13514 Map.Tap = Tap;
13515 Map.TouchZoom = TouchZoom;
13516
13517 // misc\r
13518 \r
13519 var oldL = window.L;\r
13520 function noConflict() {\r
13521         window.L = oldL;\r
13522         return this;\r
13523 }\r
13524 \r
13525 // Always export us to window global (see #2364)\r
13526 window.L = exports;\r
13527 \r
13528 Object.freeze = freeze;
13529
13530 exports.version = version;
13531 exports.noConflict = noConflict;
13532 exports.Control = Control;
13533 exports.control = control;
13534 exports.Browser = Browser;
13535 exports.Evented = Evented;
13536 exports.Mixin = Mixin;
13537 exports.Util = Util;
13538 exports.Class = Class;
13539 exports.Handler = Handler;
13540 exports.extend = extend;
13541 exports.bind = bind;
13542 exports.stamp = stamp;
13543 exports.setOptions = setOptions;
13544 exports.DomEvent = DomEvent;
13545 exports.DomUtil = DomUtil;
13546 exports.PosAnimation = PosAnimation;
13547 exports.Draggable = Draggable;
13548 exports.LineUtil = LineUtil;
13549 exports.PolyUtil = PolyUtil;
13550 exports.Point = Point;
13551 exports.point = toPoint;
13552 exports.Bounds = Bounds;
13553 exports.bounds = toBounds;
13554 exports.Transformation = Transformation;
13555 exports.transformation = toTransformation;
13556 exports.Projection = index;
13557 exports.LatLng = LatLng;
13558 exports.latLng = toLatLng;
13559 exports.LatLngBounds = LatLngBounds;
13560 exports.latLngBounds = toLatLngBounds;
13561 exports.CRS = CRS;
13562 exports.GeoJSON = GeoJSON;
13563 exports.geoJSON = geoJSON;
13564 exports.geoJson = geoJson;
13565 exports.Layer = Layer;
13566 exports.LayerGroup = LayerGroup;
13567 exports.layerGroup = layerGroup;
13568 exports.FeatureGroup = FeatureGroup;
13569 exports.featureGroup = featureGroup;
13570 exports.ImageOverlay = ImageOverlay;
13571 exports.imageOverlay = imageOverlay;
13572 exports.VideoOverlay = VideoOverlay;
13573 exports.videoOverlay = videoOverlay;
13574 exports.DivOverlay = DivOverlay;
13575 exports.Popup = Popup;
13576 exports.popup = popup;
13577 exports.Tooltip = Tooltip;
13578 exports.tooltip = tooltip;
13579 exports.Icon = Icon;
13580 exports.icon = icon;
13581 exports.DivIcon = DivIcon;
13582 exports.divIcon = divIcon;
13583 exports.Marker = Marker;
13584 exports.marker = marker;
13585 exports.TileLayer = TileLayer;
13586 exports.tileLayer = tileLayer;
13587 exports.GridLayer = GridLayer;
13588 exports.gridLayer = gridLayer;
13589 exports.SVG = SVG;
13590 exports.svg = svg$1;
13591 exports.Renderer = Renderer;
13592 exports.Canvas = Canvas;
13593 exports.canvas = canvas$1;
13594 exports.Path = Path;
13595 exports.CircleMarker = CircleMarker;
13596 exports.circleMarker = circleMarker;
13597 exports.Circle = Circle;
13598 exports.circle = circle;
13599 exports.Polyline = Polyline;
13600 exports.polyline = polyline;
13601 exports.Polygon = Polygon;
13602 exports.polygon = polygon;
13603 exports.Rectangle = Rectangle;
13604 exports.rectangle = rectangle;
13605 exports.Map = Map;
13606 exports.map = createMap;
13607
13608 })));
13609 //# sourceMappingURL=leaflet-src.js.map