UNPKG

437 kBJavaScriptView Raw
1/* @preserve
2 * Leaflet 1.8.0, a JS library for interactive maps. https://leafletjs.com
3 * (c) 2010-2022 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5
6(function (global, factory) {
7 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8 typeof define === 'function' && define.amd ? define(['exports'], factory) :
9 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {}));
10})(this, (function (exports) { 'use strict';
11
12 var version = "1.8.0";
13
14 /*
15 * @namespace Util
16 *
17 * Various utility functions, used by Leaflet internally.
18 */
19
20 // @function extend(dest: Object, src?: Object): Object
21 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
22 function extend(dest) {
23 var i, j, len, src;
24
25 for (j = 1, len = arguments.length; j < len; j++) {
26 src = arguments[j];
27 for (i in src) {
28 dest[i] = src[i];
29 }
30 }
31 return dest;
32 }
33
34 // @function create(proto: Object, properties?: Object): Object
35 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
36 var create$2 = Object.create || (function () {
37 function F() {}
38 return function (proto) {
39 F.prototype = proto;
40 return new F();
41 };
42 })();
43
44 // @function bind(fn: Function, …): Function
45 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
46 // Has a `L.bind()` shortcut.
47 function bind(fn, obj) {
48 var slice = Array.prototype.slice;
49
50 if (fn.bind) {
51 return fn.bind.apply(fn, slice.call(arguments, 1));
52 }
53
54 var args = slice.call(arguments, 2);
55
56 return function () {
57 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
58 };
59 }
60
61 // @property lastId: Number
62 // Last unique ID used by [`stamp()`](#util-stamp)
63 var lastId = 0;
64
65 // @function stamp(obj: Object): Number
66 // Returns the unique ID of an object, assigning it one if it doesn't have it.
67 function stamp(obj) {
68 if (!('_leaflet_id' in obj)) {
69 obj['_leaflet_id'] = ++lastId;
70 }
71 return obj._leaflet_id;
72 }
73
74 // @function throttle(fn: Function, time: Number, context: Object): Function
75 // Returns a function which executes function `fn` with the given scope `context`
76 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
77 // `fn` will be called no more than one time per given amount of `time`. The arguments
78 // received by the bound function will be any arguments passed when binding the
79 // function, followed by any arguments passed when invoking the bound function.
80 // Has an `L.throttle` shortcut.
81 function throttle(fn, time, context) {
82 var lock, args, wrapperFn, later;
83
84 later = function () {
85 // reset lock and call if queued
86 lock = false;
87 if (args) {
88 wrapperFn.apply(context, args);
89 args = false;
90 }
91 };
92
93 wrapperFn = function () {
94 if (lock) {
95 // called too soon, queue to call later
96 args = arguments;
97
98 } else {
99 // call and lock until later
100 fn.apply(context, arguments);
101 setTimeout(later, time);
102 lock = true;
103 }
104 };
105
106 return wrapperFn;
107 }
108
109 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
110 // Returns the number `num` modulo `range` in such a way so it lies within
111 // `range[0]` and `range[1]`. The returned value will be always smaller than
112 // `range[1]` unless `includeMax` is set to `true`.
113 function wrapNum(x, range, includeMax) {
114 var max = range[1],
115 min = range[0],
116 d = max - min;
117 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
118 }
119
120 // @function falseFn(): Function
121 // Returns a function which always returns `false`.
122 function falseFn() { return false; }
123
124 // @function formatNum(num: Number, precision?: Number|false): Number
125 // Returns the number `num` rounded with specified `precision`.
126 // The default `precision` value is 6 decimal places.
127 // `false` can be passed to skip any processing (can be useful to avoid round-off errors).
128 function formatNum(num, precision) {
129 if (precision === false) { return num; }
130 var pow = Math.pow(10, precision === undefined ? 6 : precision);
131 return Math.round(num * pow) / pow;
132 }
133
134 // @function trim(str: String): String
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
136 function trim(str) {
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
138 }
139
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143 return trim(str).split(/\s+/);
144 }
145
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
150 obj.options = obj.options ? create$2(obj.options) : {};
151 }
152 for (var i in options) {
153 obj.options[i] = options[i];
154 }
155 return obj.options;
156 }
157
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
164 var params = [];
165 for (var i in obj) {
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
167 }
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
169 }
170
171 var templateRe = /\{ *([\w_ -]+) *\}/g;
172
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179 return str.replace(templateRe, function (str, key) {
180 var value = data[key];
181
182 if (value === undefined) {
183 throw new Error('No value provided for variable ' + str);
184
185 } else if (typeof value === 'function') {
186 value = value(data);
187 }
188 return value;
189 });
190 }
191
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195 return (Object.prototype.toString.call(obj) === '[object Array]');
196 };
197
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201 for (var i = 0; i < array.length; i++) {
202 if (array[i] === el) { return i; }
203 }
204 return -1;
205 }
206
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
212
213 // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/
214
215 function getPrefixed(name) {
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
217 }
218
219 var lastTime = 0;
220
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223 var time = +new Date(),
224 timeToCall = Math.max(0, 16 - (time - lastTime));
225
226 lastTime = time + timeToCall;
227 return window.setTimeout(fn, timeToCall);
228 }
229
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
233
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241 if (immediate && requestFn === timeoutDefer) {
242 fn.call(context);
243 } else {
244 return requestFn.call(window, bind(fn, context));
245 }
246 }
247
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
251 if (id) {
252 cancelFn.call(window, id);
253 }
254 }
255
256 var Util = {
257 __proto__: null,
258 extend: extend,
259 create: create$2,
260 bind: bind,
261 get lastId () { return lastId; },
262 stamp: stamp,
263 throttle: throttle,
264 wrapNum: wrapNum,
265 falseFn: falseFn,
266 formatNum: formatNum,
267 trim: trim,
268 splitWords: splitWords,
269 setOptions: setOptions,
270 getParamString: getParamString,
271 template: template,
272 isArray: isArray,
273 indexOf: indexOf,
274 emptyImageUrl: emptyImageUrl,
275 requestFn: requestFn,
276 cancelFn: cancelFn,
277 requestAnimFrame: requestAnimFrame,
278 cancelAnimFrame: cancelAnimFrame
279 };
280
281 // @class Class
282 // @aka L.Class
283
284 // @section
285 // @uninheritable
286
287 // Thanks to John Resig and Dean Edwards for inspiration!
288
289 function Class() {}
290
291 Class.extend = function (props) {
292
293 // @function extend(props: Object): Function
294 // [Extends the current class](#class-inheritance) given the properties to be included.
295 // Returns a Javascript function that is a class constructor (to be called with `new`).
296 var NewClass = function () {
297
298 setOptions(this);
299
300 // call the constructor
301 if (this.initialize) {
302 this.initialize.apply(this, arguments);
303 }
304
305 // call all constructor hooks
306 this.callInitHooks();
307 };
308
309 var parentProto = NewClass.__super__ = this.prototype;
310
311 var proto = create$2(parentProto);
312 proto.constructor = NewClass;
313
314 NewClass.prototype = proto;
315
316 // inherit parent's statics
317 for (var i in this) {
318 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
319 NewClass[i] = this[i];
320 }
321 }
322
323 // mix static properties into the class
324 if (props.statics) {
325 extend(NewClass, props.statics);
326 }
327
328 // mix includes into the prototype
329 if (props.includes) {
330 checkDeprecatedMixinEvents(props.includes);
331 extend.apply(null, [proto].concat(props.includes));
332 }
333
334 // mix given properties into the prototype
335 extend(proto, props);
336 delete proto.statics;
337 delete proto.includes;
338
339 // merge options
340 if (proto.options) {
341 proto.options = parentProto.options ? create$2(parentProto.options) : {};
342 extend(proto.options, props.options);
343 }
344
345 proto._initHooks = [];
346
347 // add method for calling all hooks
348 proto.callInitHooks = function () {
349
350 if (this._initHooksCalled) { return; }
351
352 if (parentProto.callInitHooks) {
353 parentProto.callInitHooks.call(this);
354 }
355
356 this._initHooksCalled = true;
357
358 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
359 proto._initHooks[i].call(this);
360 }
361 };
362
363 return NewClass;
364 };
365
366
367 // @function include(properties: Object): this
368 // [Includes a mixin](#class-includes) into the current class.
369 Class.include = function (props) {
370 var parentOptions = this.prototype.options;
371 extend(this.prototype, props);
372 if (props.options) {
373 this.prototype.options = parentOptions;
374 this.mergeOptions(props.options);
375 }
376 return this;
377 };
378
379 // @function mergeOptions(options: Object): this
380 // [Merges `options`](#class-options) into the defaults of the class.
381 Class.mergeOptions = function (options) {
382 extend(this.prototype.options, options);
383 return this;
384 };
385
386 // @function addInitHook(fn: Function): this
387 // Adds a [constructor hook](#class-constructor-hooks) to the class.
388 Class.addInitHook = function (fn) { // (Function) || (String, args...)
389 var args = Array.prototype.slice.call(arguments, 1);
390
391 var init = typeof fn === 'function' ? fn : function () {
392 this[fn].apply(this, args);
393 };
394
395 this.prototype._initHooks = this.prototype._initHooks || [];
396 this.prototype._initHooks.push(init);
397 return this;
398 };
399
400 function checkDeprecatedMixinEvents(includes) {
401 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
402
403 includes = isArray(includes) ? includes : [includes];
404
405 for (var i = 0; i < includes.length; i++) {
406 if (includes[i] === L.Mixin.Events) {
407 console.warn('Deprecated include of L.Mixin.Events: ' +
408 'this property will be removed in future releases, ' +
409 'please inherit from L.Evented instead.', new Error().stack);
410 }
411 }
412 }
413
414 /*
415 * @class Evented
416 * @aka L.Evented
417 * @inherits Class
418 *
419 * 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).
420 *
421 * @example
422 *
423 * ```js
424 * map.on('click', function(e) {
425 * alert(e.latlng);
426 * } );
427 * ```
428 *
429 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
430 *
431 * ```js
432 * function onClick(e) { ... }
433 *
434 * map.on('click', onClick);
435 * map.off('click', onClick);
436 * ```
437 */
438
439 var Events = {
440 /* @method on(type: String, fn: Function, context?: Object): this
441 * 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'`).
442 *
443 * @alternative
444 * @method on(eventMap: Object): this
445 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
446 */
447 on: function (types, fn, context) {
448
449 // types can be a map of types/handlers
450 if (typeof types === 'object') {
451 for (var type in types) {
452 // we don't process space-separated events here for performance;
453 // it's a hot path since Layer uses the on(obj) syntax
454 this._on(type, types[type], fn);
455 }
456
457 } else {
458 // types can be a string of space-separated words
459 types = splitWords(types);
460
461 for (var i = 0, len = types.length; i < len; i++) {
462 this._on(types[i], fn, context);
463 }
464 }
465
466 return this;
467 },
468
469 /* @method off(type: String, fn?: Function, context?: Object): this
470 * 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.
471 *
472 * @alternative
473 * @method off(eventMap: Object): this
474 * Removes a set of type/listener pairs.
475 *
476 * @alternative
477 * @method off: this
478 * Removes all listeners to all events on the object. This includes implicitly attached events.
479 */
480 off: function (types, fn, context) {
481
482 if (!arguments.length) {
483 // clear all listeners if called without arguments
484 delete this._events;
485
486 } else if (typeof types === 'object') {
487 for (var type in types) {
488 this._off(type, types[type], fn);
489 }
490
491 } else {
492 types = splitWords(types);
493
494 var removeAll = arguments.length === 1;
495 for (var i = 0, len = types.length; i < len; i++) {
496 if (removeAll) {
497 this._off(types[i]);
498 } else {
499 this._off(types[i], fn, context);
500 }
501 }
502 }
503
504 return this;
505 },
506
507 // attach listener (without syntactic sugar now)
508 _on: function (type, fn, context) {
509 if (typeof fn !== 'function') {
510 console.warn('wrong listener type: ' + typeof fn);
511 return;
512 }
513 this._events = this._events || {};
514
515 /* get/init listeners for type */
516 var typeListeners = this._events[type];
517 if (!typeListeners) {
518 typeListeners = [];
519 this._events[type] = typeListeners;
520 }
521
522 if (context === this) {
523 // Less memory footprint.
524 context = undefined;
525 }
526 var newListener = {fn: fn, ctx: context},
527 listeners = typeListeners;
528
529 // check if fn already there
530 for (var i = 0, len = listeners.length; i < len; i++) {
531 if (listeners[i].fn === fn && listeners[i].ctx === context) {
532 return;
533 }
534 }
535
536 listeners.push(newListener);
537 },
538
539 _off: function (type, fn, context) {
540 var listeners,
541 i,
542 len;
543
544 if (!this._events) { return; }
545
546 listeners = this._events[type];
547
548 if (!listeners) {
549 return;
550 }
551
552 if (arguments.length === 1) { // remove all
553 if (this._firingCount) {
554 // Set all removed listeners to noop
555 // so they are not called if remove happens in fire
556 for (i = 0, len = listeners.length; i < len; i++) {
557 listeners[i].fn = falseFn;
558 }
559 }
560 // clear all listeners for a type if function isn't specified
561 delete this._events[type];
562 return;
563 }
564
565 if (context === this) {
566 context = undefined;
567 }
568
569 if (typeof fn !== 'function') {
570 console.warn('wrong listener type: ' + typeof fn);
571 return;
572 }
573 // find fn and remove it
574 for (i = 0, len = listeners.length; i < len; i++) {
575 var l = listeners[i];
576 if (l.ctx !== context) { continue; }
577 if (l.fn === fn) {
578 if (this._firingCount) {
579 // set the removed listener to noop so that's not called if remove happens in fire
580 l.fn = falseFn;
581
582 /* copy array in case events are being fired */
583 this._events[type] = listeners = listeners.slice();
584 }
585 listeners.splice(i, 1);
586
587 return;
588 }
589 }
590 console.warn('listener not found');
591 },
592
593 // @method fire(type: String, data?: Object, propagate?: Boolean): this
594 // Fires an event of the specified type. You can optionally provide a data
595 // object — the first argument of the listener function will contain its
596 // properties. The event can optionally be propagated to event parents.
597 fire: function (type, data, propagate) {
598 if (!this.listens(type, propagate)) { return this; }
599
600 var event = extend({}, data, {
601 type: type,
602 target: this,
603 sourceTarget: data && data.sourceTarget || this
604 });
605
606 if (this._events) {
607 var listeners = this._events[type];
608
609 if (listeners) {
610 this._firingCount = (this._firingCount + 1) || 1;
611 for (var i = 0, len = listeners.length; i < len; i++) {
612 var l = listeners[i];
613 l.fn.call(l.ctx || this, event);
614 }
615
616 this._firingCount--;
617 }
618 }
619
620 if (propagate) {
621 // propagate the event to parents (set with addEventParent)
622 this._propagateEvent(event);
623 }
624
625 return this;
626 },
627
628 // @method listens(type: String, propagate?: Boolean): Boolean
629 // Returns `true` if a particular event type has any listeners attached to it.
630 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
631 listens: function (type, propagate) {
632 if (typeof type !== 'string') {
633 console.warn('"string" type argument expected');
634 }
635 var listeners = this._events && this._events[type];
636 if (listeners && listeners.length) { return true; }
637
638 if (propagate) {
639 // also check parents for listeners if event propagates
640 for (var id in this._eventParents) {
641 if (this._eventParents[id].listens(type, propagate)) { return true; }
642 }
643 }
644 return false;
645 },
646
647 // @method once(…): this
648 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
649 once: function (types, fn, context) {
650
651 if (typeof types === 'object') {
652 for (var type in types) {
653 this.once(type, types[type], fn);
654 }
655 return this;
656 }
657
658 var handler = bind(function () {
659 this
660 .off(types, fn, context)
661 .off(types, handler, context);
662 }, this);
663
664 // add a listener that's executed once and removed after that
665 return this
666 .on(types, fn, context)
667 .on(types, handler, context);
668 },
669
670 // @method addEventParent(obj: Evented): this
671 // Adds an event parent - an `Evented` that will receive propagated events
672 addEventParent: function (obj) {
673 this._eventParents = this._eventParents || {};
674 this._eventParents[stamp(obj)] = obj;
675 return this;
676 },
677
678 // @method removeEventParent(obj: Evented): this
679 // Removes an event parent, so it will stop receiving propagated events
680 removeEventParent: function (obj) {
681 if (this._eventParents) {
682 delete this._eventParents[stamp(obj)];
683 }
684 return this;
685 },
686
687 _propagateEvent: function (e) {
688 for (var id in this._eventParents) {
689 this._eventParents[id].fire(e.type, extend({
690 layer: e.target,
691 propagatedFrom: e.target
692 }, e), true);
693 }
694 }
695 };
696
697 // aliases; we should ditch those eventually
698
699 // @method addEventListener(…): this
700 // Alias to [`on(…)`](#evented-on)
701 Events.addEventListener = Events.on;
702
703 // @method removeEventListener(…): this
704 // Alias to [`off(…)`](#evented-off)
705
706 // @method clearAllEventListeners(…): this
707 // Alias to [`off()`](#evented-off)
708 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
709
710 // @method addOneTimeEventListener(…): this
711 // Alias to [`once(…)`](#evented-once)
712 Events.addOneTimeEventListener = Events.once;
713
714 // @method fireEvent(…): this
715 // Alias to [`fire(…)`](#evented-fire)
716 Events.fireEvent = Events.fire;
717
718 // @method hasEventListeners(…): Boolean
719 // Alias to [`listens(…)`](#evented-listens)
720 Events.hasEventListeners = Events.listens;
721
722 var Evented = Class.extend(Events);
723
724 /*
725 * @class Point
726 * @aka L.Point
727 *
728 * Represents a point with `x` and `y` coordinates in pixels.
729 *
730 * @example
731 *
732 * ```js
733 * var point = L.point(200, 300);
734 * ```
735 *
736 * 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:
737 *
738 * ```js
739 * map.panBy([200, 300]);
740 * map.panBy(L.point(200, 300));
741 * ```
742 *
743 * Note that `Point` does not inherit from Leaflet's `Class` object,
744 * which means new classes can't inherit from it, and new methods
745 * can't be added to it with the `include` function.
746 */
747
748 function Point(x, y, round) {
749 // @property x: Number; The `x` coordinate of the point
750 this.x = (round ? Math.round(x) : x);
751 // @property y: Number; The `y` coordinate of the point
752 this.y = (round ? Math.round(y) : y);
753 }
754
755 var trunc = Math.trunc || function (v) {
756 return v > 0 ? Math.floor(v) : Math.ceil(v);
757 };
758
759 Point.prototype = {
760
761 // @method clone(): Point
762 // Returns a copy of the current point.
763 clone: function () {
764 return new Point(this.x, this.y);
765 },
766
767 // @method add(otherPoint: Point): Point
768 // Returns the result of addition of the current and the given points.
769 add: function (point) {
770 // non-destructive, returns a new point
771 return this.clone()._add(toPoint(point));
772 },
773
774 _add: function (point) {
775 // destructive, used directly for performance in situations where it's safe to modify existing point
776 this.x += point.x;
777 this.y += point.y;
778 return this;
779 },
780
781 // @method subtract(otherPoint: Point): Point
782 // Returns the result of subtraction of the given point from the current.
783 subtract: function (point) {
784 return this.clone()._subtract(toPoint(point));
785 },
786
787 _subtract: function (point) {
788 this.x -= point.x;
789 this.y -= point.y;
790 return this;
791 },
792
793 // @method divideBy(num: Number): Point
794 // Returns the result of division of the current point by the given number.
795 divideBy: function (num) {
796 return this.clone()._divideBy(num);
797 },
798
799 _divideBy: function (num) {
800 this.x /= num;
801 this.y /= num;
802 return this;
803 },
804
805 // @method multiplyBy(num: Number): Point
806 // Returns the result of multiplication of the current point by the given number.
807 multiplyBy: function (num) {
808 return this.clone()._multiplyBy(num);
809 },
810
811 _multiplyBy: function (num) {
812 this.x *= num;
813 this.y *= num;
814 return this;
815 },
816
817 // @method scaleBy(scale: Point): Point
818 // Multiply each coordinate of the current point by each coordinate of
819 // `scale`. In linear algebra terms, multiply the point by the
820 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
821 // defined by `scale`.
822 scaleBy: function (point) {
823 return new Point(this.x * point.x, this.y * point.y);
824 },
825
826 // @method unscaleBy(scale: Point): Point
827 // Inverse of `scaleBy`. Divide each coordinate of the current point by
828 // each coordinate of `scale`.
829 unscaleBy: function (point) {
830 return new Point(this.x / point.x, this.y / point.y);
831 },
832
833 // @method round(): Point
834 // Returns a copy of the current point with rounded coordinates.
835 round: function () {
836 return this.clone()._round();
837 },
838
839 _round: function () {
840 this.x = Math.round(this.x);
841 this.y = Math.round(this.y);
842 return this;
843 },
844
845 // @method floor(): Point
846 // Returns a copy of the current point with floored coordinates (rounded down).
847 floor: function () {
848 return this.clone()._floor();
849 },
850
851 _floor: function () {
852 this.x = Math.floor(this.x);
853 this.y = Math.floor(this.y);
854 return this;
855 },
856
857 // @method ceil(): Point
858 // Returns a copy of the current point with ceiled coordinates (rounded up).
859 ceil: function () {
860 return this.clone()._ceil();
861 },
862
863 _ceil: function () {
864 this.x = Math.ceil(this.x);
865 this.y = Math.ceil(this.y);
866 return this;
867 },
868
869 // @method trunc(): Point
870 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
871 trunc: function () {
872 return this.clone()._trunc();
873 },
874
875 _trunc: function () {
876 this.x = trunc(this.x);
877 this.y = trunc(this.y);
878 return this;
879 },
880
881 // @method distanceTo(otherPoint: Point): Number
882 // Returns the cartesian distance between the current and the given points.
883 distanceTo: function (point) {
884 point = toPoint(point);
885
886 var x = point.x - this.x,
887 y = point.y - this.y;
888
889 return Math.sqrt(x * x + y * y);
890 },
891
892 // @method equals(otherPoint: Point): Boolean
893 // Returns `true` if the given point has the same coordinates.
894 equals: function (point) {
895 point = toPoint(point);
896
897 return point.x === this.x &&
898 point.y === this.y;
899 },
900
901 // @method contains(otherPoint: Point): Boolean
902 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
903 contains: function (point) {
904 point = toPoint(point);
905
906 return Math.abs(point.x) <= Math.abs(this.x) &&
907 Math.abs(point.y) <= Math.abs(this.y);
908 },
909
910 // @method toString(): String
911 // Returns a string representation of the point for debugging purposes.
912 toString: function () {
913 return 'Point(' +
914 formatNum(this.x) + ', ' +
915 formatNum(this.y) + ')';
916 }
917 };
918
919 // @factory L.point(x: Number, y: Number, round?: Boolean)
920 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
921
922 // @alternative
923 // @factory L.point(coords: Number[])
924 // Expects an array of the form `[x, y]` instead.
925
926 // @alternative
927 // @factory L.point(coords: Object)
928 // Expects a plain object of the form `{x: Number, y: Number}` instead.
929 function toPoint(x, y, round) {
930 if (x instanceof Point) {
931 return x;
932 }
933 if (isArray(x)) {
934 return new Point(x[0], x[1]);
935 }
936 if (x === undefined || x === null) {
937 return x;
938 }
939 if (typeof x === 'object' && 'x' in x && 'y' in x) {
940 return new Point(x.x, x.y);
941 }
942 return new Point(x, y, round);
943 }
944
945 /*
946 * @class Bounds
947 * @aka L.Bounds
948 *
949 * Represents a rectangular area in pixel coordinates.
950 *
951 * @example
952 *
953 * ```js
954 * var p1 = L.point(10, 10),
955 * p2 = L.point(40, 60),
956 * bounds = L.bounds(p1, p2);
957 * ```
958 *
959 * 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:
960 *
961 * ```js
962 * otherBounds.intersects([[10, 10], [40, 60]]);
963 * ```
964 *
965 * Note that `Bounds` does not inherit from Leaflet's `Class` object,
966 * which means new classes can't inherit from it, and new methods
967 * can't be added to it with the `include` function.
968 */
969
970 function Bounds(a, b) {
971 if (!a) { return; }
972
973 var points = b ? [a, b] : a;
974
975 for (var i = 0, len = points.length; i < len; i++) {
976 this.extend(points[i]);
977 }
978 }
979
980 Bounds.prototype = {
981 // @method extend(point: Point): this
982 // Extends the bounds to contain the given point.
983 extend: function (point) { // (Point)
984 point = toPoint(point);
985
986 // @property min: Point
987 // The top left corner of the rectangle.
988 // @property max: Point
989 // The bottom right corner of the rectangle.
990 if (!this.min && !this.max) {
991 this.min = point.clone();
992 this.max = point.clone();
993 } else {
994 this.min.x = Math.min(point.x, this.min.x);
995 this.max.x = Math.max(point.x, this.max.x);
996 this.min.y = Math.min(point.y, this.min.y);
997 this.max.y = Math.max(point.y, this.max.y);
998 }
999 return this;
1000 },
1001
1002 // @method getCenter(round?: Boolean): Point
1003 // Returns the center point of the bounds.
1004 getCenter: function (round) {
1005 return new Point(
1006 (this.min.x + this.max.x) / 2,
1007 (this.min.y + this.max.y) / 2, round);
1008 },
1009
1010 // @method getBottomLeft(): Point
1011 // Returns the bottom-left point of the bounds.
1012 getBottomLeft: function () {
1013 return new Point(this.min.x, this.max.y);
1014 },
1015
1016 // @method getTopRight(): Point
1017 // Returns the top-right point of the bounds.
1018 getTopRight: function () { // -> Point
1019 return new Point(this.max.x, this.min.y);
1020 },
1021
1022 // @method getTopLeft(): Point
1023 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
1024 getTopLeft: function () {
1025 return this.min; // left, top
1026 },
1027
1028 // @method getBottomRight(): Point
1029 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1030 getBottomRight: function () {
1031 return this.max; // right, bottom
1032 },
1033
1034 // @method getSize(): Point
1035 // Returns the size of the given bounds
1036 getSize: function () {
1037 return this.max.subtract(this.min);
1038 },
1039
1040 // @method contains(otherBounds: Bounds): Boolean
1041 // Returns `true` if the rectangle contains the given one.
1042 // @alternative
1043 // @method contains(point: Point): Boolean
1044 // Returns `true` if the rectangle contains the given point.
1045 contains: function (obj) {
1046 var min, max;
1047
1048 if (typeof obj[0] === 'number' || obj instanceof Point) {
1049 obj = toPoint(obj);
1050 } else {
1051 obj = toBounds(obj);
1052 }
1053
1054 if (obj instanceof Bounds) {
1055 min = obj.min;
1056 max = obj.max;
1057 } else {
1058 min = max = obj;
1059 }
1060
1061 return (min.x >= this.min.x) &&
1062 (max.x <= this.max.x) &&
1063 (min.y >= this.min.y) &&
1064 (max.y <= this.max.y);
1065 },
1066
1067 // @method intersects(otherBounds: Bounds): Boolean
1068 // Returns `true` if the rectangle intersects the given bounds. Two bounds
1069 // intersect if they have at least one point in common.
1070 intersects: function (bounds) { // (Bounds) -> Boolean
1071 bounds = toBounds(bounds);
1072
1073 var min = this.min,
1074 max = this.max,
1075 min2 = bounds.min,
1076 max2 = bounds.max,
1077 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1078 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1079
1080 return xIntersects && yIntersects;
1081 },
1082
1083 // @method overlaps(otherBounds: Bounds): Boolean
1084 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1085 // overlap if their intersection is an area.
1086 overlaps: function (bounds) { // (Bounds) -> Boolean
1087 bounds = toBounds(bounds);
1088
1089 var min = this.min,
1090 max = this.max,
1091 min2 = bounds.min,
1092 max2 = bounds.max,
1093 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1094 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1095
1096 return xOverlaps && yOverlaps;
1097 },
1098
1099 isValid: function () {
1100 return !!(this.min && this.max);
1101 }
1102 };
1103
1104
1105 // @factory L.bounds(corner1: Point, corner2: Point)
1106 // Creates a Bounds object from two corners coordinate pairs.
1107 // @alternative
1108 // @factory L.bounds(points: Point[])
1109 // Creates a Bounds object from the given array of points.
1110 function toBounds(a, b) {
1111 if (!a || a instanceof Bounds) {
1112 return a;
1113 }
1114 return new Bounds(a, b);
1115 }
1116
1117 /*
1118 * @class LatLngBounds
1119 * @aka L.LatLngBounds
1120 *
1121 * Represents a rectangular geographical area on a map.
1122 *
1123 * @example
1124 *
1125 * ```js
1126 * var corner1 = L.latLng(40.712, -74.227),
1127 * corner2 = L.latLng(40.774, -74.125),
1128 * bounds = L.latLngBounds(corner1, corner2);
1129 * ```
1130 *
1131 * 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:
1132 *
1133 * ```js
1134 * map.fitBounds([
1135 * [40.712, -74.227],
1136 * [40.774, -74.125]
1137 * ]);
1138 * ```
1139 *
1140 * 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.
1141 *
1142 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
1143 * which means new classes can't inherit from it, and new methods
1144 * can't be added to it with the `include` function.
1145 */
1146
1147 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1148 if (!corner1) { return; }
1149
1150 var latlngs = corner2 ? [corner1, corner2] : corner1;
1151
1152 for (var i = 0, len = latlngs.length; i < len; i++) {
1153 this.extend(latlngs[i]);
1154 }
1155 }
1156
1157 LatLngBounds.prototype = {
1158
1159 // @method extend(latlng: LatLng): this
1160 // Extend the bounds to contain the given point
1161
1162 // @alternative
1163 // @method extend(otherBounds: LatLngBounds): this
1164 // Extend the bounds to contain the given bounds
1165 extend: function (obj) {
1166 var sw = this._southWest,
1167 ne = this._northEast,
1168 sw2, ne2;
1169
1170 if (obj instanceof LatLng) {
1171 sw2 = obj;
1172 ne2 = obj;
1173
1174 } else if (obj instanceof LatLngBounds) {
1175 sw2 = obj._southWest;
1176 ne2 = obj._northEast;
1177
1178 if (!sw2 || !ne2) { return this; }
1179
1180 } else {
1181 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1182 }
1183
1184 if (!sw && !ne) {
1185 this._southWest = new LatLng(sw2.lat, sw2.lng);
1186 this._northEast = new LatLng(ne2.lat, ne2.lng);
1187 } else {
1188 sw.lat = Math.min(sw2.lat, sw.lat);
1189 sw.lng = Math.min(sw2.lng, sw.lng);
1190 ne.lat = Math.max(ne2.lat, ne.lat);
1191 ne.lng = Math.max(ne2.lng, ne.lng);
1192 }
1193
1194 return this;
1195 },
1196
1197 // @method pad(bufferRatio: Number): LatLngBounds
1198 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1199 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1200 // Negative values will retract the bounds.
1201 pad: function (bufferRatio) {
1202 var sw = this._southWest,
1203 ne = this._northEast,
1204 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1205 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1206
1207 return new LatLngBounds(
1208 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1209 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1210 },
1211
1212 // @method getCenter(): LatLng
1213 // Returns the center point of the bounds.
1214 getCenter: function () {
1215 return new LatLng(
1216 (this._southWest.lat + this._northEast.lat) / 2,
1217 (this._southWest.lng + this._northEast.lng) / 2);
1218 },
1219
1220 // @method getSouthWest(): LatLng
1221 // Returns the south-west point of the bounds.
1222 getSouthWest: function () {
1223 return this._southWest;
1224 },
1225
1226 // @method getNorthEast(): LatLng
1227 // Returns the north-east point of the bounds.
1228 getNorthEast: function () {
1229 return this._northEast;
1230 },
1231
1232 // @method getNorthWest(): LatLng
1233 // Returns the north-west point of the bounds.
1234 getNorthWest: function () {
1235 return new LatLng(this.getNorth(), this.getWest());
1236 },
1237
1238 // @method getSouthEast(): LatLng
1239 // Returns the south-east point of the bounds.
1240 getSouthEast: function () {
1241 return new LatLng(this.getSouth(), this.getEast());
1242 },
1243
1244 // @method getWest(): Number
1245 // Returns the west longitude of the bounds
1246 getWest: function () {
1247 return this._southWest.lng;
1248 },
1249
1250 // @method getSouth(): Number
1251 // Returns the south latitude of the bounds
1252 getSouth: function () {
1253 return this._southWest.lat;
1254 },
1255
1256 // @method getEast(): Number
1257 // Returns the east longitude of the bounds
1258 getEast: function () {
1259 return this._northEast.lng;
1260 },
1261
1262 // @method getNorth(): Number
1263 // Returns the north latitude of the bounds
1264 getNorth: function () {
1265 return this._northEast.lat;
1266 },
1267
1268 // @method contains(otherBounds: LatLngBounds): Boolean
1269 // Returns `true` if the rectangle contains the given one.
1270
1271 // @alternative
1272 // @method contains (latlng: LatLng): Boolean
1273 // Returns `true` if the rectangle contains the given point.
1274 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1275 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1276 obj = toLatLng(obj);
1277 } else {
1278 obj = toLatLngBounds(obj);
1279 }
1280
1281 var sw = this._southWest,
1282 ne = this._northEast,
1283 sw2, ne2;
1284
1285 if (obj instanceof LatLngBounds) {
1286 sw2 = obj.getSouthWest();
1287 ne2 = obj.getNorthEast();
1288 } else {
1289 sw2 = ne2 = obj;
1290 }
1291
1292 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1293 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1294 },
1295
1296 // @method intersects(otherBounds: LatLngBounds): Boolean
1297 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1298 intersects: function (bounds) {
1299 bounds = toLatLngBounds(bounds);
1300
1301 var sw = this._southWest,
1302 ne = this._northEast,
1303 sw2 = bounds.getSouthWest(),
1304 ne2 = bounds.getNorthEast(),
1305
1306 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1307 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1308
1309 return latIntersects && lngIntersects;
1310 },
1311
1312 // @method overlaps(otherBounds: LatLngBounds): Boolean
1313 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1314 overlaps: function (bounds) {
1315 bounds = toLatLngBounds(bounds);
1316
1317 var sw = this._southWest,
1318 ne = this._northEast,
1319 sw2 = bounds.getSouthWest(),
1320 ne2 = bounds.getNorthEast(),
1321
1322 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1323 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1324
1325 return latOverlaps && lngOverlaps;
1326 },
1327
1328 // @method toBBoxString(): String
1329 // 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.
1330 toBBoxString: function () {
1331 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1332 },
1333
1334 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1335 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
1336 equals: function (bounds, maxMargin) {
1337 if (!bounds) { return false; }
1338
1339 bounds = toLatLngBounds(bounds);
1340
1341 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1342 this._northEast.equals(bounds.getNorthEast(), maxMargin);
1343 },
1344
1345 // @method isValid(): Boolean
1346 // Returns `true` if the bounds are properly initialized.
1347 isValid: function () {
1348 return !!(this._southWest && this._northEast);
1349 }
1350 };
1351
1352 // TODO International date line?
1353
1354 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1355 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1356
1357 // @alternative
1358 // @factory L.latLngBounds(latlngs: LatLng[])
1359 // 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).
1360 function toLatLngBounds(a, b) {
1361 if (a instanceof LatLngBounds) {
1362 return a;
1363 }
1364 return new LatLngBounds(a, b);
1365 }
1366
1367 /* @class LatLng
1368 * @aka L.LatLng
1369 *
1370 * Represents a geographical point with a certain latitude and longitude.
1371 *
1372 * @example
1373 *
1374 * ```
1375 * var latlng = L.latLng(50.5, 30.5);
1376 * ```
1377 *
1378 * 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:
1379 *
1380 * ```
1381 * map.panTo([50, 30]);
1382 * map.panTo({lon: 30, lat: 50});
1383 * map.panTo({lat: 50, lng: 30});
1384 * map.panTo(L.latLng(50, 30));
1385 * ```
1386 *
1387 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1388 * which means new classes can't inherit from it, and new methods
1389 * can't be added to it with the `include` function.
1390 */
1391
1392 function LatLng(lat, lng, alt) {
1393 if (isNaN(lat) || isNaN(lng)) {
1394 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1395 }
1396
1397 // @property lat: Number
1398 // Latitude in degrees
1399 this.lat = +lat;
1400
1401 // @property lng: Number
1402 // Longitude in degrees
1403 this.lng = +lng;
1404
1405 // @property alt: Number
1406 // Altitude in meters (optional)
1407 if (alt !== undefined) {
1408 this.alt = +alt;
1409 }
1410 }
1411
1412 LatLng.prototype = {
1413 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1414 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
1415 equals: function (obj, maxMargin) {
1416 if (!obj) { return false; }
1417
1418 obj = toLatLng(obj);
1419
1420 var margin = Math.max(
1421 Math.abs(this.lat - obj.lat),
1422 Math.abs(this.lng - obj.lng));
1423
1424 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1425 },
1426
1427 // @method toString(): String
1428 // Returns a string representation of the point (for debugging purposes).
1429 toString: function (precision) {
1430 return 'LatLng(' +
1431 formatNum(this.lat, precision) + ', ' +
1432 formatNum(this.lng, precision) + ')';
1433 },
1434
1435 // @method distanceTo(otherLatLng: LatLng): Number
1436 // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
1437 distanceTo: function (other) {
1438 return Earth.distance(this, toLatLng(other));
1439 },
1440
1441 // @method wrap(): LatLng
1442 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1443 wrap: function () {
1444 return Earth.wrapLatLng(this);
1445 },
1446
1447 // @method toBounds(sizeInMeters: Number): LatLngBounds
1448 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1449 toBounds: function (sizeInMeters) {
1450 var latAccuracy = 180 * sizeInMeters / 40075017,
1451 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1452
1453 return toLatLngBounds(
1454 [this.lat - latAccuracy, this.lng - lngAccuracy],
1455 [this.lat + latAccuracy, this.lng + lngAccuracy]);
1456 },
1457
1458 clone: function () {
1459 return new LatLng(this.lat, this.lng, this.alt);
1460 }
1461 };
1462
1463
1464
1465 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1466 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1467
1468 // @alternative
1469 // @factory L.latLng(coords: Array): LatLng
1470 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1471
1472 // @alternative
1473 // @factory L.latLng(coords: Object): LatLng
1474 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1475
1476 function toLatLng(a, b, c) {
1477 if (a instanceof LatLng) {
1478 return a;
1479 }
1480 if (isArray(a) && typeof a[0] !== 'object') {
1481 if (a.length === 3) {
1482 return new LatLng(a[0], a[1], a[2]);
1483 }
1484 if (a.length === 2) {
1485 return new LatLng(a[0], a[1]);
1486 }
1487 return null;
1488 }
1489 if (a === undefined || a === null) {
1490 return a;
1491 }
1492 if (typeof a === 'object' && 'lat' in a) {
1493 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1494 }
1495 if (b === undefined) {
1496 return null;
1497 }
1498 return new LatLng(a, b, c);
1499 }
1500
1501 /*
1502 * @namespace CRS
1503 * @crs L.CRS.Base
1504 * Object that defines coordinate reference systems for projecting
1505 * geographical points into pixel (screen) coordinates and back (and to
1506 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1507 * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
1508 *
1509 * Leaflet defines the most usual CRSs by default. If you want to use a
1510 * CRS not defined by default, take a look at the
1511 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1512 *
1513 * Note that the CRS instances do not inherit from Leaflet's `Class` object,
1514 * and can't be instantiated. Also, new classes can't inherit from them,
1515 * and methods can't be added to them with the `include` function.
1516 */
1517
1518 var CRS = {
1519 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1520 // Projects geographical coordinates into pixel coordinates for a given zoom.
1521 latLngToPoint: function (latlng, zoom) {
1522 var projectedPoint = this.projection.project(latlng),
1523 scale = this.scale(zoom);
1524
1525 return this.transformation._transform(projectedPoint, scale);
1526 },
1527
1528 // @method pointToLatLng(point: Point, zoom: Number): LatLng
1529 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1530 // zoom into geographical coordinates.
1531 pointToLatLng: function (point, zoom) {
1532 var scale = this.scale(zoom),
1533 untransformedPoint = this.transformation.untransform(point, scale);
1534
1535 return this.projection.unproject(untransformedPoint);
1536 },
1537
1538 // @method project(latlng: LatLng): Point
1539 // Projects geographical coordinates into coordinates in units accepted for
1540 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1541 project: function (latlng) {
1542 return this.projection.project(latlng);
1543 },
1544
1545 // @method unproject(point: Point): LatLng
1546 // Given a projected coordinate returns the corresponding LatLng.
1547 // The inverse of `project`.
1548 unproject: function (point) {
1549 return this.projection.unproject(point);
1550 },
1551
1552 // @method scale(zoom: Number): Number
1553 // Returns the scale used when transforming projected coordinates into
1554 // pixel coordinates for a particular zoom. For example, it returns
1555 // `256 * 2^zoom` for Mercator-based CRS.
1556 scale: function (zoom) {
1557 return 256 * Math.pow(2, zoom);
1558 },
1559
1560 // @method zoom(scale: Number): Number
1561 // Inverse of `scale()`, returns the zoom level corresponding to a scale
1562 // factor of `scale`.
1563 zoom: function (scale) {
1564 return Math.log(scale / 256) / Math.LN2;
1565 },
1566
1567 // @method getProjectedBounds(zoom: Number): Bounds
1568 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1569 getProjectedBounds: function (zoom) {
1570 if (this.infinite) { return null; }
1571
1572 var b = this.projection.bounds,
1573 s = this.scale(zoom),
1574 min = this.transformation.transform(b.min, s),
1575 max = this.transformation.transform(b.max, s);
1576
1577 return new Bounds(min, max);
1578 },
1579
1580 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1581 // Returns the distance between two geographical coordinates.
1582
1583 // @property code: String
1584 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1585 //
1586 // @property wrapLng: Number[]
1587 // An array of two numbers defining whether the longitude (horizontal) coordinate
1588 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1589 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1590 //
1591 // @property wrapLat: Number[]
1592 // Like `wrapLng`, but for the latitude (vertical) axis.
1593
1594 // wrapLng: [min, max],
1595 // wrapLat: [min, max],
1596
1597 // @property infinite: Boolean
1598 // If true, the coordinate space will be unbounded (infinite in both axes)
1599 infinite: false,
1600
1601 // @method wrapLatLng(latlng: LatLng): LatLng
1602 // Returns a `LatLng` where lat and lng has been wrapped according to the
1603 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1604 wrapLatLng: function (latlng) {
1605 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1606 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1607 alt = latlng.alt;
1608
1609 return new LatLng(lat, lng, alt);
1610 },
1611
1612 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1613 // Returns a `LatLngBounds` with the same size as the given one, ensuring
1614 // that its center is within the CRS's bounds.
1615 // Only accepts actual `L.LatLngBounds` instances, not arrays.
1616 wrapLatLngBounds: function (bounds) {
1617 var center = bounds.getCenter(),
1618 newCenter = this.wrapLatLng(center),
1619 latShift = center.lat - newCenter.lat,
1620 lngShift = center.lng - newCenter.lng;
1621
1622 if (latShift === 0 && lngShift === 0) {
1623 return bounds;
1624 }
1625
1626 var sw = bounds.getSouthWest(),
1627 ne = bounds.getNorthEast(),
1628 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1629 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1630
1631 return new LatLngBounds(newSw, newNe);
1632 }
1633 };
1634
1635 /*
1636 * @namespace CRS
1637 * @crs L.CRS.Earth
1638 *
1639 * Serves as the base for CRS that are global such that they cover the earth.
1640 * Can only be used as the base for other CRS and cannot be used directly,
1641 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1642 * meters.
1643 */
1644
1645 var Earth = extend({}, CRS, {
1646 wrapLng: [-180, 180],
1647
1648 // Mean Earth Radius, as recommended for use by
1649 // the International Union of Geodesy and Geophysics,
1650 // see https://rosettacode.org/wiki/Haversine_formula
1651 R: 6371000,
1652
1653 // distance between two geographical points using spherical law of cosines approximation
1654 distance: function (latlng1, latlng2) {
1655 var rad = Math.PI / 180,
1656 lat1 = latlng1.lat * rad,
1657 lat2 = latlng2.lat * rad,
1658 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1659 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1660 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1661 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1662 return this.R * c;
1663 }
1664 });
1665
1666 /*
1667 * @namespace Projection
1668 * @projection L.Projection.SphericalMercator
1669 *
1670 * Spherical Mercator projection — the most common projection for online maps,
1671 * used by almost all free and commercial tile providers. Assumes that Earth is
1672 * a sphere. Used by the `EPSG:3857` CRS.
1673 */
1674
1675 var earthRadius = 6378137;
1676
1677 var SphericalMercator = {
1678
1679 R: earthRadius,
1680 MAX_LATITUDE: 85.0511287798,
1681
1682 project: function (latlng) {
1683 var d = Math.PI / 180,
1684 max = this.MAX_LATITUDE,
1685 lat = Math.max(Math.min(max, latlng.lat), -max),
1686 sin = Math.sin(lat * d);
1687
1688 return new Point(
1689 this.R * latlng.lng * d,
1690 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1691 },
1692
1693 unproject: function (point) {
1694 var d = 180 / Math.PI;
1695
1696 return new LatLng(
1697 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1698 point.x * d / this.R);
1699 },
1700
1701 bounds: (function () {
1702 var d = earthRadius * Math.PI;
1703 return new Bounds([-d, -d], [d, d]);
1704 })()
1705 };
1706
1707 /*
1708 * @class Transformation
1709 * @aka L.Transformation
1710 *
1711 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1712 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1713 * the reverse. Used by Leaflet in its projections code.
1714 *
1715 * @example
1716 *
1717 * ```js
1718 * var transformation = L.transformation(2, 5, -1, 10),
1719 * p = L.point(1, 2),
1720 * p2 = transformation.transform(p), // L.point(7, 8)
1721 * p3 = transformation.untransform(p2); // L.point(1, 2)
1722 * ```
1723 */
1724
1725
1726 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1727 // Creates a `Transformation` object with the given coefficients.
1728 function Transformation(a, b, c, d) {
1729 if (isArray(a)) {
1730 // use array properties
1731 this._a = a[0];
1732 this._b = a[1];
1733 this._c = a[2];
1734 this._d = a[3];
1735 return;
1736 }
1737 this._a = a;
1738 this._b = b;
1739 this._c = c;
1740 this._d = d;
1741 }
1742
1743 Transformation.prototype = {
1744 // @method transform(point: Point, scale?: Number): Point
1745 // Returns a transformed point, optionally multiplied by the given scale.
1746 // Only accepts actual `L.Point` instances, not arrays.
1747 transform: function (point, scale) { // (Point, Number) -> Point
1748 return this._transform(point.clone(), scale);
1749 },
1750
1751 // destructive transform (faster)
1752 _transform: function (point, scale) {
1753 scale = scale || 1;
1754 point.x = scale * (this._a * point.x + this._b);
1755 point.y = scale * (this._c * point.y + this._d);
1756 return point;
1757 },
1758
1759 // @method untransform(point: Point, scale?: Number): Point
1760 // Returns the reverse transformation of the given point, optionally divided
1761 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1762 untransform: function (point, scale) {
1763 scale = scale || 1;
1764 return new Point(
1765 (point.x / scale - this._b) / this._a,
1766 (point.y / scale - this._d) / this._c);
1767 }
1768 };
1769
1770 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1771
1772 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1773 // Instantiates a Transformation object with the given coefficients.
1774
1775 // @alternative
1776 // @factory L.transformation(coefficients: Array): Transformation
1777 // Expects an coefficients array of the form
1778 // `[a: Number, b: Number, c: Number, d: Number]`.
1779
1780 function toTransformation(a, b, c, d) {
1781 return new Transformation(a, b, c, d);
1782 }
1783
1784 /*
1785 * @namespace CRS
1786 * @crs L.CRS.EPSG3857
1787 *
1788 * The most common CRS for online maps, used by almost all free and commercial
1789 * tile providers. Uses Spherical Mercator projection. Set in by default in
1790 * Map's `crs` option.
1791 */
1792
1793 var EPSG3857 = extend({}, Earth, {
1794 code: 'EPSG:3857',
1795 projection: SphericalMercator,
1796
1797 transformation: (function () {
1798 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1799 return toTransformation(scale, 0.5, -scale, 0.5);
1800 }())
1801 });
1802
1803 var EPSG900913 = extend({}, EPSG3857, {
1804 code: 'EPSG:900913'
1805 });
1806
1807 // @namespace SVG; @section
1808 // There are several static functions which can be called without instantiating L.SVG:
1809
1810 // @function create(name: String): SVGElement
1811 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1812 // corresponding to the class name passed. For example, using 'line' will return
1813 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1814 function svgCreate(name) {
1815 return document.createElementNS('http://www.w3.org/2000/svg', name);
1816 }
1817
1818 // @function pointsToPath(rings: Point[], closed: Boolean): String
1819 // Generates a SVG path string for multiple rings, with each ring turning
1820 // into "M..L..L.." instructions
1821 function pointsToPath(rings, closed) {
1822 var str = '',
1823 i, j, len, len2, points, p;
1824
1825 for (i = 0, len = rings.length; i < len; i++) {
1826 points = rings[i];
1827
1828 for (j = 0, len2 = points.length; j < len2; j++) {
1829 p = points[j];
1830 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1831 }
1832
1833 // closes the ring for polygons; "x" is VML syntax
1834 str += closed ? (Browser.svg ? 'z' : 'x') : '';
1835 }
1836
1837 // SVG complains about empty path strings
1838 return str || 'M0 0';
1839 }
1840
1841 /*
1842 * @namespace Browser
1843 * @aka L.Browser
1844 *
1845 * A namespace with static properties for browser/feature detection used by Leaflet internally.
1846 *
1847 * @example
1848 *
1849 * ```js
1850 * if (L.Browser.ielt9) {
1851 * alert('Upgrade your browser, dude!');
1852 * }
1853 * ```
1854 */
1855
1856 var style = document.documentElement.style;
1857
1858 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1859 var ie = 'ActiveXObject' in window;
1860
1861 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1862 var ielt9 = ie && !document.addEventListener;
1863
1864 // @property edge: Boolean; `true` for the Edge web browser.
1865 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1866
1867 // @property webkit: Boolean;
1868 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1869 var webkit = userAgentContains('webkit');
1870
1871 // @property android: Boolean
1872 // **Deprecated.** `true` for any browser running on an Android platform.
1873 var android = userAgentContains('android');
1874
1875 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
1876 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1877
1878 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1879 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1880 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)
1881 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1882
1883 // @property opera: Boolean; `true` for the Opera browser
1884 var opera = !!window.opera;
1885
1886 // @property chrome: Boolean; `true` for the Chrome browser.
1887 var chrome = !edge && userAgentContains('chrome');
1888
1889 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1890 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1891
1892 // @property safari: Boolean; `true` for the Safari browser.
1893 var safari = !chrome && userAgentContains('safari');
1894
1895 var phantom = userAgentContains('phantom');
1896
1897 // @property opera12: Boolean
1898 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1899 var opera12 = 'OTransition' in style;
1900
1901 // @property win: Boolean; `true` when the browser is running in a Windows platform
1902 var win = navigator.platform.indexOf('Win') === 0;
1903
1904 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1905 var ie3d = ie && ('transition' in style);
1906
1907 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1908 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1909
1910 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1911 var gecko3d = 'MozPerspective' in style;
1912
1913 // @property any3d: Boolean
1914 // `true` for all browsers supporting CSS transforms.
1915 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1916
1917 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1918 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1919
1920 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1921 var mobileWebkit = mobile && webkit;
1922
1923 // @property mobileWebkit3d: Boolean
1924 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1925 var mobileWebkit3d = mobile && webkit3d;
1926
1927 // @property msPointer: Boolean
1928 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1929 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1930
1931 // @property pointer: Boolean
1932 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1933 var pointer = !!(window.PointerEvent || msPointer);
1934
1935 // @property touchNative: Boolean
1936 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1937 // **This does not necessarily mean** that the browser is running in a computer with
1938 // a touchscreen, it only means that the browser is capable of understanding
1939 // touch events.
1940 var touchNative = 'ontouchstart' in window || !!window.TouchEvent;
1941
1942 // @property touch: Boolean
1943 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.
1944 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.
1945 var touch = !window.L_NO_TOUCH && (touchNative || pointer);
1946
1947 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1948 var mobileOpera = mobile && opera;
1949
1950 // @property mobileGecko: Boolean
1951 // `true` for gecko-based browsers running in a mobile device.
1952 var mobileGecko = mobile && gecko;
1953
1954 // @property retina: Boolean
1955 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1956 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1957
1958 // @property passiveEvents: Boolean
1959 // `true` for browsers that support passive events.
1960 var passiveEvents = (function () {
1961 var supportsPassiveOption = false;
1962 try {
1963 var opts = Object.defineProperty({}, 'passive', {
1964 get: function () { // eslint-disable-line getter-return
1965 supportsPassiveOption = true;
1966 }
1967 });
1968 window.addEventListener('testPassiveEventSupport', falseFn, opts);
1969 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
1970 } catch (e) {
1971 // Errors can safely be ignored since this is only a browser support test.
1972 }
1973 return supportsPassiveOption;
1974 }());
1975
1976 // @property canvas: Boolean
1977 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1978 var canvas$1 = (function () {
1979 return !!document.createElement('canvas').getContext;
1980 }());
1981
1982 // @property svg: Boolean
1983 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1984 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1985
1986 var inlineSvg = !!svg$1 && (function () {
1987 var div = document.createElement('div');
1988 div.innerHTML = '<svg/>';
1989 return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';
1990 })();
1991
1992 // @property vml: Boolean
1993 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1994 var vml = !svg$1 && (function () {
1995 try {
1996 var div = document.createElement('div');
1997 div.innerHTML = '<v:shape adj="1"/>';
1998
1999 var shape = div.firstChild;
2000 shape.style.behavior = 'url(#default#VML)';
2001
2002 return shape && (typeof shape.adj === 'object');
2003
2004 } catch (e) {
2005 return false;
2006 }
2007 }());
2008
2009 function userAgentContains(str) {
2010 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
2011 }
2012
2013
2014 var Browser = {
2015 ie: ie,
2016 ielt9: ielt9,
2017 edge: edge,
2018 webkit: webkit,
2019 android: android,
2020 android23: android23,
2021 androidStock: androidStock,
2022 opera: opera,
2023 chrome: chrome,
2024 gecko: gecko,
2025 safari: safari,
2026 phantom: phantom,
2027 opera12: opera12,
2028 win: win,
2029 ie3d: ie3d,
2030 webkit3d: webkit3d,
2031 gecko3d: gecko3d,
2032 any3d: any3d,
2033 mobile: mobile,
2034 mobileWebkit: mobileWebkit,
2035 mobileWebkit3d: mobileWebkit3d,
2036 msPointer: msPointer,
2037 pointer: pointer,
2038 touch: touch,
2039 touchNative: touchNative,
2040 mobileOpera: mobileOpera,
2041 mobileGecko: mobileGecko,
2042 retina: retina,
2043 passiveEvents: passiveEvents,
2044 canvas: canvas$1,
2045 svg: svg$1,
2046 vml: vml,
2047 inlineSvg: inlineSvg
2048 };
2049
2050 /*
2051 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2052 */
2053
2054 var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown';
2055 var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove';
2056 var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup';
2057 var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
2058 var pEvent = {
2059 touchstart : POINTER_DOWN,
2060 touchmove : POINTER_MOVE,
2061 touchend : POINTER_UP,
2062 touchcancel : POINTER_CANCEL
2063 };
2064 var handle = {
2065 touchstart : _onPointerStart,
2066 touchmove : _handlePointer,
2067 touchend : _handlePointer,
2068 touchcancel : _handlePointer
2069 };
2070 var _pointers = {};
2071 var _pointerDocListener = false;
2072
2073 // Provides a touch events wrapper for (ms)pointer events.
2074 // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2075
2076 function addPointerListener(obj, type, handler) {
2077 if (type === 'touchstart') {
2078 _addPointerDocListener();
2079 }
2080 if (!handle[type]) {
2081 console.warn('wrong event specified:', type);
2082 return L.Util.falseFn;
2083 }
2084 handler = handle[type].bind(this, handler);
2085 obj.addEventListener(pEvent[type], handler, false);
2086 return handler;
2087 }
2088
2089 function removePointerListener(obj, type, handler) {
2090 if (!pEvent[type]) {
2091 console.warn('wrong event specified:', type);
2092 return;
2093 }
2094 obj.removeEventListener(pEvent[type], handler, false);
2095 }
2096
2097 function _globalPointerDown(e) {
2098 _pointers[e.pointerId] = e;
2099 }
2100
2101 function _globalPointerMove(e) {
2102 if (_pointers[e.pointerId]) {
2103 _pointers[e.pointerId] = e;
2104 }
2105 }
2106
2107 function _globalPointerUp(e) {
2108 delete _pointers[e.pointerId];
2109 }
2110
2111 function _addPointerDocListener() {
2112 // need to keep track of what pointers and how many are active to provide e.touches emulation
2113 if (!_pointerDocListener) {
2114 // we listen document as any drags that end by moving the touch off the screen get fired there
2115 document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2116 document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2117 document.addEventListener(POINTER_UP, _globalPointerUp, true);
2118 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2119
2120 _pointerDocListener = true;
2121 }
2122 }
2123
2124 function _handlePointer(handler, e) {
2125 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }
2126
2127 e.touches = [];
2128 for (var i in _pointers) {
2129 e.touches.push(_pointers[i]);
2130 }
2131 e.changedTouches = [e];
2132
2133 handler(e);
2134 }
2135
2136 function _onPointerStart(handler, e) {
2137 // IE10 specific: MsTouch needs preventDefault. See #2000
2138 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
2139 preventDefault(e);
2140 }
2141 _handlePointer(handler, e);
2142 }
2143
2144 /*
2145 * Extends the event handling code with double tap support for mobile browsers.
2146 *
2147 * Note: currently most browsers fire native dblclick, with only a few exceptions
2148 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)
2149 */
2150
2151 function makeDblclick(event) {
2152 // in modern browsers `type` cannot be just overridden:
2153 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only
2154 var newEvent = {},
2155 prop, i;
2156 for (i in event) {
2157 prop = event[i];
2158 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;
2159 }
2160 event = newEvent;
2161 newEvent.type = 'dblclick';
2162 newEvent.detail = 2;
2163 newEvent.isTrusted = false;
2164 newEvent._simulated = true; // for debug purposes
2165 return newEvent;
2166 }
2167
2168 var delay = 200;
2169 function addDoubleTapListener(obj, handler) {
2170 // Most browsers handle double tap natively
2171 obj.addEventListener('dblclick', handler);
2172
2173 // On some platforms the browser doesn't fire native dblclicks for touch events.
2174 // It seems that in all such cases `detail` property of `click` event is always `1`.
2175 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.
2176 var last = 0,
2177 detail;
2178 function simDblclick(e) {
2179 if (e.detail !== 1) {
2180 detail = e.detail; // keep in sync to avoid false dblclick in some cases
2181 return;
2182 }
2183
2184 if (e.pointerType === 'mouse' ||
2185 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {
2186
2187 return;
2188 }
2189
2190 var now = Date.now();
2191 if (now - last <= delay) {
2192 detail++;
2193 if (detail === 2) {
2194 handler(makeDblclick(e));
2195 }
2196 } else {
2197 detail = 1;
2198 }
2199 last = now;
2200 }
2201
2202 obj.addEventListener('click', simDblclick);
2203
2204 return {
2205 dblclick: handler,
2206 simDblclick: simDblclick
2207 };
2208 }
2209
2210 function removeDoubleTapListener(obj, handlers) {
2211 obj.removeEventListener('dblclick', handlers.dblclick);
2212 obj.removeEventListener('click', handlers.simDblclick);
2213 }
2214
2215 /*
2216 * @namespace DomUtil
2217 *
2218 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2219 * tree, used by Leaflet internally.
2220 *
2221 * Most functions expecting or returning a `HTMLElement` also work for
2222 * SVG elements. The only difference is that classes refer to CSS classes
2223 * in HTML and SVG classes in SVG.
2224 */
2225
2226
2227 // @property TRANSFORM: String
2228 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2229 var TRANSFORM = testProp(
2230 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2231
2232 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2233 // the same for the transitionend event, in particular the Android 4.1 stock browser
2234
2235 // @property TRANSITION: String
2236 // Vendor-prefixed transition style name.
2237 var TRANSITION = testProp(
2238 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2239
2240 // @property TRANSITION_END: String
2241 // Vendor-prefixed transitionend event name.
2242 var TRANSITION_END =
2243 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2244
2245
2246 // @function get(id: String|HTMLElement): HTMLElement
2247 // Returns an element given its DOM id, or returns the element itself
2248 // if it was passed directly.
2249 function get(id) {
2250 return typeof id === 'string' ? document.getElementById(id) : id;
2251 }
2252
2253 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2254 // Returns the value for a certain style attribute on an element,
2255 // including computed values or values set through CSS.
2256 function getStyle(el, style) {
2257 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2258
2259 if ((!value || value === 'auto') && document.defaultView) {
2260 var css = document.defaultView.getComputedStyle(el, null);
2261 value = css ? css[style] : null;
2262 }
2263 return value === 'auto' ? null : value;
2264 }
2265
2266 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2267 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2268 function create$1(tagName, className, container) {
2269 var el = document.createElement(tagName);
2270 el.className = className || '';
2271
2272 if (container) {
2273 container.appendChild(el);
2274 }
2275 return el;
2276 }
2277
2278 // @function remove(el: HTMLElement)
2279 // Removes `el` from its parent element
2280 function remove(el) {
2281 var parent = el.parentNode;
2282 if (parent) {
2283 parent.removeChild(el);
2284 }
2285 }
2286
2287 // @function empty(el: HTMLElement)
2288 // Removes all of `el`'s children elements from `el`
2289 function empty(el) {
2290 while (el.firstChild) {
2291 el.removeChild(el.firstChild);
2292 }
2293 }
2294
2295 // @function toFront(el: HTMLElement)
2296 // Makes `el` the last child of its parent, so it renders in front of the other children.
2297 function toFront(el) {
2298 var parent = el.parentNode;
2299 if (parent && parent.lastChild !== el) {
2300 parent.appendChild(el);
2301 }
2302 }
2303
2304 // @function toBack(el: HTMLElement)
2305 // Makes `el` the first child of its parent, so it renders behind the other children.
2306 function toBack(el) {
2307 var parent = el.parentNode;
2308 if (parent && parent.firstChild !== el) {
2309 parent.insertBefore(el, parent.firstChild);
2310 }
2311 }
2312
2313 // @function hasClass(el: HTMLElement, name: String): Boolean
2314 // Returns `true` if the element's class attribute contains `name`.
2315 function hasClass(el, name) {
2316 if (el.classList !== undefined) {
2317 return el.classList.contains(name);
2318 }
2319 var className = getClass(el);
2320 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2321 }
2322
2323 // @function addClass(el: HTMLElement, name: String)
2324 // Adds `name` to the element's class attribute.
2325 function addClass(el, name) {
2326 if (el.classList !== undefined) {
2327 var classes = splitWords(name);
2328 for (var i = 0, len = classes.length; i < len; i++) {
2329 el.classList.add(classes[i]);
2330 }
2331 } else if (!hasClass(el, name)) {
2332 var className = getClass(el);
2333 setClass(el, (className ? className + ' ' : '') + name);
2334 }
2335 }
2336
2337 // @function removeClass(el: HTMLElement, name: String)
2338 // Removes `name` from the element's class attribute.
2339 function removeClass(el, name) {
2340 if (el.classList !== undefined) {
2341 el.classList.remove(name);
2342 } else {
2343 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2344 }
2345 }
2346
2347 // @function setClass(el: HTMLElement, name: String)
2348 // Sets the element's class.
2349 function setClass(el, name) {
2350 if (el.className.baseVal === undefined) {
2351 el.className = name;
2352 } else {
2353 // in case of SVG element
2354 el.className.baseVal = name;
2355 }
2356 }
2357
2358 // @function getClass(el: HTMLElement): String
2359 // Returns the element's class.
2360 function getClass(el) {
2361 // Check if the element is an SVGElementInstance and use the correspondingElement instead
2362 // (Required for linked SVG elements in IE11.)
2363 if (el.correspondingElement) {
2364 el = el.correspondingElement;
2365 }
2366 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2367 }
2368
2369 // @function setOpacity(el: HTMLElement, opacity: Number)
2370 // Set the opacity of an element (including old IE support).
2371 // `opacity` must be a number from `0` to `1`.
2372 function setOpacity(el, value) {
2373 if ('opacity' in el.style) {
2374 el.style.opacity = value;
2375 } else if ('filter' in el.style) {
2376 _setOpacityIE(el, value);
2377 }
2378 }
2379
2380 function _setOpacityIE(el, value) {
2381 var filter = false,
2382 filterName = 'DXImageTransform.Microsoft.Alpha';
2383
2384 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2385 try {
2386 filter = el.filters.item(filterName);
2387 } catch (e) {
2388 // don't set opacity to 1 if we haven't already set an opacity,
2389 // it isn't needed and breaks transparent pngs.
2390 if (value === 1) { return; }
2391 }
2392
2393 value = Math.round(value * 100);
2394
2395 if (filter) {
2396 filter.Enabled = (value !== 100);
2397 filter.Opacity = value;
2398 } else {
2399 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2400 }
2401 }
2402
2403 // @function testProp(props: String[]): String|false
2404 // Goes through the array of style names and returns the first name
2405 // that is a valid style name for an element. If no such name is found,
2406 // it returns false. Useful for vendor-prefixed styles like `transform`.
2407 function testProp(props) {
2408 var style = document.documentElement.style;
2409
2410 for (var i = 0; i < props.length; i++) {
2411 if (props[i] in style) {
2412 return props[i];
2413 }
2414 }
2415 return false;
2416 }
2417
2418 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2419 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2420 // and optionally scaled by `scale`. Does not have an effect if the
2421 // browser doesn't support 3D CSS transforms.
2422 function setTransform(el, offset, scale) {
2423 var pos = offset || new Point(0, 0);
2424
2425 el.style[TRANSFORM] =
2426 (Browser.ie3d ?
2427 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2428 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2429 (scale ? ' scale(' + scale + ')' : '');
2430 }
2431
2432 // @function setPosition(el: HTMLElement, position: Point)
2433 // Sets the position of `el` to coordinates specified by `position`,
2434 // using CSS translate or top/left positioning depending on the browser
2435 // (used by Leaflet internally to position its layers).
2436 function setPosition(el, point) {
2437
2438 /*eslint-disable */
2439 el._leaflet_pos = point;
2440 /* eslint-enable */
2441
2442 if (Browser.any3d) {
2443 setTransform(el, point);
2444 } else {
2445 el.style.left = point.x + 'px';
2446 el.style.top = point.y + 'px';
2447 }
2448 }
2449
2450 // @function getPosition(el: HTMLElement): Point
2451 // Returns the coordinates of an element previously positioned with setPosition.
2452 function getPosition(el) {
2453 // this method is only used for elements previously positioned using setPosition,
2454 // so it's safe to cache the position for performance
2455
2456 return el._leaflet_pos || new Point(0, 0);
2457 }
2458
2459 // @function disableTextSelection()
2460 // Prevents the user from generating `selectstart` DOM events, usually generated
2461 // when the user drags the mouse through a page with text. Used internally
2462 // by Leaflet to override the behaviour of any click-and-drag interaction on
2463 // the map. Affects drag interactions on the whole document.
2464
2465 // @function enableTextSelection()
2466 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2467 var disableTextSelection;
2468 var enableTextSelection;
2469 var _userSelect;
2470 if ('onselectstart' in document) {
2471 disableTextSelection = function () {
2472 on(window, 'selectstart', preventDefault);
2473 };
2474 enableTextSelection = function () {
2475 off(window, 'selectstart', preventDefault);
2476 };
2477 } else {
2478 var userSelectProperty = testProp(
2479 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2480
2481 disableTextSelection = function () {
2482 if (userSelectProperty) {
2483 var style = document.documentElement.style;
2484 _userSelect = style[userSelectProperty];
2485 style[userSelectProperty] = 'none';
2486 }
2487 };
2488 enableTextSelection = function () {
2489 if (userSelectProperty) {
2490 document.documentElement.style[userSelectProperty] = _userSelect;
2491 _userSelect = undefined;
2492 }
2493 };
2494 }
2495
2496 // @function disableImageDrag()
2497 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2498 // for `dragstart` DOM events, usually generated when the user drags an image.
2499 function disableImageDrag() {
2500 on(window, 'dragstart', preventDefault);
2501 }
2502
2503 // @function enableImageDrag()
2504 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2505 function enableImageDrag() {
2506 off(window, 'dragstart', preventDefault);
2507 }
2508
2509 var _outlineElement, _outlineStyle;
2510 // @function preventOutline(el: HTMLElement)
2511 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2512 // of the element `el` invisible. Used internally by Leaflet to prevent
2513 // focusable elements from displaying an outline when the user performs a
2514 // drag interaction on them.
2515 function preventOutline(element) {
2516 while (element.tabIndex === -1) {
2517 element = element.parentNode;
2518 }
2519 if (!element.style) { return; }
2520 restoreOutline();
2521 _outlineElement = element;
2522 _outlineStyle = element.style.outline;
2523 element.style.outline = 'none';
2524 on(window, 'keydown', restoreOutline);
2525 }
2526
2527 // @function restoreOutline()
2528 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2529 function restoreOutline() {
2530 if (!_outlineElement) { return; }
2531 _outlineElement.style.outline = _outlineStyle;
2532 _outlineElement = undefined;
2533 _outlineStyle = undefined;
2534 off(window, 'keydown', restoreOutline);
2535 }
2536
2537 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2538 // Finds the closest parent node which size (width and height) is not null.
2539 function getSizedParentNode(element) {
2540 do {
2541 element = element.parentNode;
2542 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2543 return element;
2544 }
2545
2546 // @function getScale(el: HTMLElement): Object
2547 // Computes the CSS scale currently applied on the element.
2548 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2549 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2550 function getScale(element) {
2551 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2552
2553 return {
2554 x: rect.width / element.offsetWidth || 1,
2555 y: rect.height / element.offsetHeight || 1,
2556 boundingClientRect: rect
2557 };
2558 }
2559
2560 var DomUtil = {
2561 __proto__: null,
2562 TRANSFORM: TRANSFORM,
2563 TRANSITION: TRANSITION,
2564 TRANSITION_END: TRANSITION_END,
2565 get: get,
2566 getStyle: getStyle,
2567 create: create$1,
2568 remove: remove,
2569 empty: empty,
2570 toFront: toFront,
2571 toBack: toBack,
2572 hasClass: hasClass,
2573 addClass: addClass,
2574 removeClass: removeClass,
2575 setClass: setClass,
2576 getClass: getClass,
2577 setOpacity: setOpacity,
2578 testProp: testProp,
2579 setTransform: setTransform,
2580 setPosition: setPosition,
2581 getPosition: getPosition,
2582 get disableTextSelection () { return disableTextSelection; },
2583 get enableTextSelection () { return enableTextSelection; },
2584 disableImageDrag: disableImageDrag,
2585 enableImageDrag: enableImageDrag,
2586 preventOutline: preventOutline,
2587 restoreOutline: restoreOutline,
2588 getSizedParentNode: getSizedParentNode,
2589 getScale: getScale
2590 };
2591
2592 /*
2593 * @namespace DomEvent
2594 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2595 */
2596
2597 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2598
2599 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2600 // Adds a listener function (`fn`) to a particular DOM event type of the
2601 // element `el`. You can optionally specify the context of the listener
2602 // (object the `this` keyword will point to). You can also pass several
2603 // space-separated types (e.g. `'click dblclick'`).
2604
2605 // @alternative
2606 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2607 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2608 function on(obj, types, fn, context) {
2609
2610 if (types && typeof types === 'object') {
2611 for (var type in types) {
2612 addOne(obj, type, types[type], fn);
2613 }
2614 } else {
2615 types = splitWords(types);
2616
2617 for (var i = 0, len = types.length; i < len; i++) {
2618 addOne(obj, types[i], fn, context);
2619 }
2620 }
2621
2622 return this;
2623 }
2624
2625 var eventsKey = '_leaflet_events';
2626
2627 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2628 // Removes a previously added listener function.
2629 // Note that if you passed a custom context to on, you must pass the same
2630 // context to `off` in order to remove the listener.
2631
2632 // @alternative
2633 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2634 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2635
2636 // @alternative
2637 // @function off(el: HTMLElement, types: String): this
2638 // Removes all previously added listeners of given types.
2639
2640 // @alternative
2641 // @function off(el: HTMLElement): this
2642 // Removes all previously added listeners from given HTMLElement
2643 function off(obj, types, fn, context) {
2644
2645 if (arguments.length === 1) {
2646 batchRemove(obj);
2647 delete obj[eventsKey];
2648
2649 } else if (types && typeof types === 'object') {
2650 for (var type in types) {
2651 removeOne(obj, type, types[type], fn);
2652 }
2653
2654 } else {
2655 types = splitWords(types);
2656
2657 if (arguments.length === 2) {
2658 batchRemove(obj, function (type) {
2659 return indexOf(types, type) !== -1;
2660 });
2661 } else {
2662 for (var i = 0, len = types.length; i < len; i++) {
2663 removeOne(obj, types[i], fn, context);
2664 }
2665 }
2666 }
2667
2668 return this;
2669 }
2670
2671 function batchRemove(obj, filterFn) {
2672 for (var id in obj[eventsKey]) {
2673 var type = id.split(/\d/)[0];
2674 if (!filterFn || filterFn(type)) {
2675 removeOne(obj, type, null, null, id);
2676 }
2677 }
2678 }
2679
2680 var mouseSubst = {
2681 mouseenter: 'mouseover',
2682 mouseleave: 'mouseout',
2683 wheel: !('onwheel' in window) && 'mousewheel'
2684 };
2685
2686 function addOne(obj, type, fn, context) {
2687 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2688
2689 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2690
2691 var handler = function (e) {
2692 return fn.call(context || obj, e || window.event);
2693 };
2694
2695 var originalHandler = handler;
2696
2697 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
2698 // Needs DomEvent.Pointer.js
2699 handler = addPointerListener(obj, type, handler);
2700
2701 } else if (Browser.touch && (type === 'dblclick')) {
2702 handler = addDoubleTapListener(obj, handler);
2703
2704 } else if ('addEventListener' in obj) {
2705
2706 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') {
2707 obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);
2708
2709 } else if (type === 'mouseenter' || type === 'mouseleave') {
2710 handler = function (e) {
2711 e = e || window.event;
2712 if (isExternalTarget(obj, e)) {
2713 originalHandler(e);
2714 }
2715 };
2716 obj.addEventListener(mouseSubst[type], handler, false);
2717
2718 } else {
2719 obj.addEventListener(type, originalHandler, false);
2720 }
2721
2722 } else {
2723 obj.attachEvent('on' + type, handler);
2724 }
2725
2726 obj[eventsKey] = obj[eventsKey] || {};
2727 obj[eventsKey][id] = handler;
2728 }
2729
2730 function removeOne(obj, type, fn, context, id) {
2731 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');
2732 var handler = obj[eventsKey] && obj[eventsKey][id];
2733
2734 if (!handler) { return this; }
2735
2736 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
2737 removePointerListener(obj, type, handler);
2738
2739 } else if (Browser.touch && (type === 'dblclick')) {
2740 removeDoubleTapListener(obj, handler);
2741
2742 } else if ('removeEventListener' in obj) {
2743
2744 obj.removeEventListener(mouseSubst[type] || type, handler, false);
2745
2746 } else {
2747 obj.detachEvent('on' + type, handler);
2748 }
2749
2750 obj[eventsKey][id] = null;
2751 }
2752
2753 // @function stopPropagation(ev: DOMEvent): this
2754 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2755 // ```js
2756 // L.DomEvent.on(div, 'click', function (ev) {
2757 // L.DomEvent.stopPropagation(ev);
2758 // });
2759 // ```
2760 function stopPropagation(e) {
2761
2762 if (e.stopPropagation) {
2763 e.stopPropagation();
2764 } else if (e.originalEvent) { // In case of Leaflet event.
2765 e.originalEvent._stopped = true;
2766 } else {
2767 e.cancelBubble = true;
2768 }
2769
2770 return this;
2771 }
2772
2773 // @function disableScrollPropagation(el: HTMLElement): this
2774 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
2775 function disableScrollPropagation(el) {
2776 addOne(el, 'wheel', stopPropagation);
2777 return this;
2778 }
2779
2780 // @function disableClickPropagation(el: HTMLElement): this
2781 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,
2782 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2783 function disableClickPropagation(el) {
2784 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);
2785 el['_leaflet_disable_click'] = true;
2786 return this;
2787 }
2788
2789 // @function preventDefault(ev: DOMEvent): this
2790 // Prevents the default action of the DOM Event `ev` from happening (such as
2791 // following a link in the href of the a element, or doing a POST request
2792 // with page reload when a `<form>` is submitted).
2793 // Use it inside listener functions.
2794 function preventDefault(e) {
2795 if (e.preventDefault) {
2796 e.preventDefault();
2797 } else {
2798 e.returnValue = false;
2799 }
2800 return this;
2801 }
2802
2803 // @function stop(ev: DOMEvent): this
2804 // Does `stopPropagation` and `preventDefault` at the same time.
2805 function stop(e) {
2806 preventDefault(e);
2807 stopPropagation(e);
2808 return this;
2809 }
2810
2811 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2812 // Gets normalized mouse position from a DOM event relative to the
2813 // `container` (border excluded) or to the whole page if not specified.
2814 function getMousePosition(e, container) {
2815 if (!container) {
2816 return new Point(e.clientX, e.clientY);
2817 }
2818
2819 var scale = getScale(container),
2820 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
2821
2822 return new Point(
2823 // offset.left/top values are in page scale (like clientX/Y),
2824 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2825 (e.clientX - offset.left) / scale.x - container.clientLeft,
2826 (e.clientY - offset.top) / scale.y - container.clientTop
2827 );
2828 }
2829
2830 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2831 // and Firefox scrolls device pixels, not CSS pixels
2832 var wheelPxFactor =
2833 (Browser.win && Browser.chrome) ? 2 * window.devicePixelRatio :
2834 Browser.gecko ? window.devicePixelRatio : 1;
2835
2836 // @function getWheelDelta(ev: DOMEvent): Number
2837 // Gets normalized wheel delta from a wheel DOM event, in vertical
2838 // pixels scrolled (negative if scrolling down).
2839 // Events from pointing devices without precise scrolling are mapped to
2840 // a best guess of 60 pixels.
2841 function getWheelDelta(e) {
2842 return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2843 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2844 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2845 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2846 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
2847 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2848 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2849 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2850 0;
2851 }
2852
2853 // check if element really left/entered the event target (for mouseenter/mouseleave)
2854 function isExternalTarget(el, e) {
2855
2856 var related = e.relatedTarget;
2857
2858 if (!related) { return true; }
2859
2860 try {
2861 while (related && (related !== el)) {
2862 related = related.parentNode;
2863 }
2864 } catch (err) {
2865 return false;
2866 }
2867 return (related !== el);
2868 }
2869
2870 var DomEvent = {
2871 __proto__: null,
2872 on: on,
2873 off: off,
2874 stopPropagation: stopPropagation,
2875 disableScrollPropagation: disableScrollPropagation,
2876 disableClickPropagation: disableClickPropagation,
2877 preventDefault: preventDefault,
2878 stop: stop,
2879 getMousePosition: getMousePosition,
2880 getWheelDelta: getWheelDelta,
2881 isExternalTarget: isExternalTarget,
2882 addListener: on,
2883 removeListener: off
2884 };
2885
2886 /*
2887 * @class PosAnimation
2888 * @aka L.PosAnimation
2889 * @inherits Evented
2890 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2891 *
2892 * @example
2893 * ```js
2894 * var fx = new L.PosAnimation();
2895 * fx.run(el, [300, 500], 0.5);
2896 * ```
2897 *
2898 * @constructor L.PosAnimation()
2899 * Creates a `PosAnimation` object.
2900 *
2901 */
2902
2903 var PosAnimation = Evented.extend({
2904
2905 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2906 // Run an animation of a given element to a new position, optionally setting
2907 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2908 // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
2909 // `0.5` by default).
2910 run: function (el, newPos, duration, easeLinearity) {
2911 this.stop();
2912
2913 this._el = el;
2914 this._inProgress = true;
2915 this._duration = duration || 0.25;
2916 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2917
2918 this._startPos = getPosition(el);
2919 this._offset = newPos.subtract(this._startPos);
2920 this._startTime = +new Date();
2921
2922 // @event start: Event
2923 // Fired when the animation starts
2924 this.fire('start');
2925
2926 this._animate();
2927 },
2928
2929 // @method stop()
2930 // Stops the animation (if currently running).
2931 stop: function () {
2932 if (!this._inProgress) { return; }
2933
2934 this._step(true);
2935 this._complete();
2936 },
2937
2938 _animate: function () {
2939 // animation loop
2940 this._animId = requestAnimFrame(this._animate, this);
2941 this._step();
2942 },
2943
2944 _step: function (round) {
2945 var elapsed = (+new Date()) - this._startTime,
2946 duration = this._duration * 1000;
2947
2948 if (elapsed < duration) {
2949 this._runFrame(this._easeOut(elapsed / duration), round);
2950 } else {
2951 this._runFrame(1);
2952 this._complete();
2953 }
2954 },
2955
2956 _runFrame: function (progress, round) {
2957 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2958 if (round) {
2959 pos._round();
2960 }
2961 setPosition(this._el, pos);
2962
2963 // @event step: Event
2964 // Fired continuously during the animation.
2965 this.fire('step');
2966 },
2967
2968 _complete: function () {
2969 cancelAnimFrame(this._animId);
2970
2971 this._inProgress = false;
2972 // @event end: Event
2973 // Fired when the animation ends.
2974 this.fire('end');
2975 },
2976
2977 _easeOut: function (t) {
2978 return 1 - Math.pow(1 - t, this._easeOutPower);
2979 }
2980 });
2981
2982 /*
2983 * @class Map
2984 * @aka L.Map
2985 * @inherits Evented
2986 *
2987 * The central class of the API — it is used to create a map on a page and manipulate it.
2988 *
2989 * @example
2990 *
2991 * ```js
2992 * // initialize the map on the "map" div with a given center and zoom
2993 * var map = L.map('map', {
2994 * center: [51.505, -0.09],
2995 * zoom: 13
2996 * });
2997 * ```
2998 *
2999 */
3000
3001 var Map = Evented.extend({
3002
3003 options: {
3004 // @section Map State Options
3005 // @option crs: CRS = L.CRS.EPSG3857
3006 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3007 // sure what it means.
3008 crs: EPSG3857,
3009
3010 // @option center: LatLng = undefined
3011 // Initial geographic center of the map
3012 center: undefined,
3013
3014 // @option zoom: Number = undefined
3015 // Initial map zoom level
3016 zoom: undefined,
3017
3018 // @option minZoom: Number = *
3019 // Minimum zoom level of the map.
3020 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3021 // the lowest of their `minZoom` options will be used instead.
3022 minZoom: undefined,
3023
3024 // @option maxZoom: Number = *
3025 // Maximum zoom level of the map.
3026 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3027 // the highest of their `maxZoom` options will be used instead.
3028 maxZoom: undefined,
3029
3030 // @option layers: Layer[] = []
3031 // Array of layers that will be added to the map initially
3032 layers: [],
3033
3034 // @option maxBounds: LatLngBounds = null
3035 // When this option is set, the map restricts the view to the given
3036 // geographical bounds, bouncing the user back if the user tries to pan
3037 // outside the view. To set the restriction dynamically, use
3038 // [`setMaxBounds`](#map-setmaxbounds) method.
3039 maxBounds: undefined,
3040
3041 // @option renderer: Renderer = *
3042 // The default method for drawing vector layers on the map. `L.SVG`
3043 // or `L.Canvas` by default depending on browser support.
3044 renderer: undefined,
3045
3046
3047 // @section Animation Options
3048 // @option zoomAnimation: Boolean = true
3049 // Whether the map zoom animation is enabled. By default it's enabled
3050 // in all browsers that support CSS3 Transitions except Android.
3051 zoomAnimation: true,
3052
3053 // @option zoomAnimationThreshold: Number = 4
3054 // Won't animate zoom if the zoom difference exceeds this value.
3055 zoomAnimationThreshold: 4,
3056
3057 // @option fadeAnimation: Boolean = true
3058 // Whether the tile fade animation is enabled. By default it's enabled
3059 // in all browsers that support CSS3 Transitions except Android.
3060 fadeAnimation: true,
3061
3062 // @option markerZoomAnimation: Boolean = true
3063 // Whether markers animate their zoom with the zoom animation, if disabled
3064 // they will disappear for the length of the animation. By default it's
3065 // enabled in all browsers that support CSS3 Transitions except Android.
3066 markerZoomAnimation: true,
3067
3068 // @option transform3DLimit: Number = 2^23
3069 // Defines the maximum size of a CSS translation transform. The default
3070 // value should not be changed unless a web browser positions layers in
3071 // the wrong place after doing a large `panBy`.
3072 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3073
3074 // @section Interaction Options
3075 // @option zoomSnap: Number = 1
3076 // Forces the map's zoom level to always be a multiple of this, particularly
3077 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3078 // By default, the zoom level snaps to the nearest integer; lower values
3079 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3080 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3081 zoomSnap: 1,
3082
3083 // @option zoomDelta: Number = 1
3084 // Controls how much the map's zoom level will change after a
3085 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3086 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3087 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3088 zoomDelta: 1,
3089
3090 // @option trackResize: Boolean = true
3091 // Whether the map automatically handles browser window resize to update itself.
3092 trackResize: true
3093 },
3094
3095 initialize: function (id, options) { // (HTMLElement or String, Object)
3096 options = setOptions(this, options);
3097
3098 // Make sure to assign internal flags at the beginning,
3099 // to avoid inconsistent state in some edge cases.
3100 this._handlers = [];
3101 this._layers = {};
3102 this._zoomBoundLayers = {};
3103 this._sizeChanged = true;
3104
3105 this._initContainer(id);
3106 this._initLayout();
3107
3108 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3109 this._onResize = bind(this._onResize, this);
3110
3111 this._initEvents();
3112
3113 if (options.maxBounds) {
3114 this.setMaxBounds(options.maxBounds);
3115 }
3116
3117 if (options.zoom !== undefined) {
3118 this._zoom = this._limitZoom(options.zoom);
3119 }
3120
3121 if (options.center && options.zoom !== undefined) {
3122 this.setView(toLatLng(options.center), options.zoom, {reset: true});
3123 }
3124
3125 this.callInitHooks();
3126
3127 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3128 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&
3129 this.options.zoomAnimation;
3130
3131 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3132 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3133 if (this._zoomAnimated) {
3134 this._createAnimProxy();
3135 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3136 }
3137
3138 this._addLayers(this.options.layers);
3139 },
3140
3141
3142 // @section Methods for modifying map state
3143
3144 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3145 // Sets the view of the map (geographical center and zoom) with the given
3146 // animation options.
3147 setView: function (center, zoom, options) {
3148
3149 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3150 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3151 options = options || {};
3152
3153 this._stop();
3154
3155 if (this._loaded && !options.reset && options !== true) {
3156
3157 if (options.animate !== undefined) {
3158 options.zoom = extend({animate: options.animate}, options.zoom);
3159 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3160 }
3161
3162 // try animating pan or zoom
3163 var moved = (this._zoom !== zoom) ?
3164 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3165 this._tryAnimatedPan(center, options.pan);
3166
3167 if (moved) {
3168 // prevent resize handler call, the view will refresh after animation anyway
3169 clearTimeout(this._sizeTimer);
3170 return this;
3171 }
3172 }
3173
3174 // animation didn't start, just reset the map view
3175 this._resetView(center, zoom);
3176
3177 return this;
3178 },
3179
3180 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3181 // Sets the zoom of the map.
3182 setZoom: function (zoom, options) {
3183 if (!this._loaded) {
3184 this._zoom = zoom;
3185 return this;
3186 }
3187 return this.setView(this.getCenter(), zoom, {zoom: options});
3188 },
3189
3190 // @method zoomIn(delta?: Number, options?: Zoom options): this
3191 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3192 zoomIn: function (delta, options) {
3193 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
3194 return this.setZoom(this._zoom + delta, options);
3195 },
3196
3197 // @method zoomOut(delta?: Number, options?: Zoom options): this
3198 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3199 zoomOut: function (delta, options) {
3200 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
3201 return this.setZoom(this._zoom - delta, options);
3202 },
3203
3204 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3205 // Zooms the map while keeping a specified geographical point on the map
3206 // stationary (e.g. used internally for scroll zoom and double-click zoom).
3207 // @alternative
3208 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3209 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3210 setZoomAround: function (latlng, zoom, options) {
3211 var scale = this.getZoomScale(zoom),
3212 viewHalf = this.getSize().divideBy(2),
3213 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3214
3215 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3216 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3217
3218 return this.setView(newCenter, zoom, {zoom: options});
3219 },
3220
3221 _getBoundsCenterZoom: function (bounds, options) {
3222
3223 options = options || {};
3224 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3225
3226 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3227 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3228
3229 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3230
3231 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3232
3233 if (zoom === Infinity) {
3234 return {
3235 center: bounds.getCenter(),
3236 zoom: zoom
3237 };
3238 }
3239
3240 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3241
3242 swPoint = this.project(bounds.getSouthWest(), zoom),
3243 nePoint = this.project(bounds.getNorthEast(), zoom),
3244 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3245
3246 return {
3247 center: center,
3248 zoom: zoom
3249 };
3250 },
3251
3252 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3253 // Sets a map view that contains the given geographical bounds with the
3254 // maximum zoom level possible.
3255 fitBounds: function (bounds, options) {
3256
3257 bounds = toLatLngBounds(bounds);
3258
3259 if (!bounds.isValid()) {
3260 throw new Error('Bounds are not valid.');
3261 }
3262
3263 var target = this._getBoundsCenterZoom(bounds, options);
3264 return this.setView(target.center, target.zoom, options);
3265 },
3266
3267 // @method fitWorld(options?: fitBounds options): this
3268 // Sets a map view that mostly contains the whole world with the maximum
3269 // zoom level possible.
3270 fitWorld: function (options) {
3271 return this.fitBounds([[-90, -180], [90, 180]], options);
3272 },
3273
3274 // @method panTo(latlng: LatLng, options?: Pan options): this
3275 // Pans the map to a given center.
3276 panTo: function (center, options) { // (LatLng)
3277 return this.setView(center, this._zoom, {pan: options});
3278 },
3279
3280 // @method panBy(offset: Point, options?: Pan options): this
3281 // Pans the map by a given number of pixels (animated).
3282 panBy: function (offset, options) {
3283 offset = toPoint(offset).round();
3284 options = options || {};
3285
3286 if (!offset.x && !offset.y) {
3287 return this.fire('moveend');
3288 }
3289 // If we pan too far, Chrome gets issues with tiles
3290 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3291 if (options.animate !== true && !this.getSize().contains(offset)) {
3292 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3293 return this;
3294 }
3295
3296 if (!this._panAnim) {
3297 this._panAnim = new PosAnimation();
3298
3299 this._panAnim.on({
3300 'step': this._onPanTransitionStep,
3301 'end': this._onPanTransitionEnd
3302 }, this);
3303 }
3304
3305 // don't fire movestart if animating inertia
3306 if (!options.noMoveStart) {
3307 this.fire('movestart');
3308 }
3309
3310 // animate pan unless animate: false specified
3311 if (options.animate !== false) {
3312 addClass(this._mapPane, 'leaflet-pan-anim');
3313
3314 var newPos = this._getMapPanePos().subtract(offset).round();
3315 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3316 } else {
3317 this._rawPanBy(offset);
3318 this.fire('move').fire('moveend');
3319 }
3320
3321 return this;
3322 },
3323
3324 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3325 // Sets the view of the map (geographical center and zoom) performing a smooth
3326 // pan-zoom animation.
3327 flyTo: function (targetCenter, targetZoom, options) {
3328
3329 options = options || {};
3330 if (options.animate === false || !Browser.any3d) {
3331 return this.setView(targetCenter, targetZoom, options);
3332 }
3333
3334 this._stop();
3335
3336 var from = this.project(this.getCenter()),
3337 to = this.project(targetCenter),
3338 size = this.getSize(),
3339 startZoom = this._zoom;
3340
3341 targetCenter = toLatLng(targetCenter);
3342 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3343
3344 var w0 = Math.max(size.x, size.y),
3345 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3346 u1 = (to.distanceTo(from)) || 1,
3347 rho = 1.42,
3348 rho2 = rho * rho;
3349
3350 function r(i) {
3351 var s1 = i ? -1 : 1,
3352 s2 = i ? w1 : w0,
3353 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3354 b1 = 2 * s2 * rho2 * u1,
3355 b = t1 / b1,
3356 sq = Math.sqrt(b * b + 1) - b;
3357
3358 // workaround for floating point precision bug when sq = 0, log = -Infinite,
3359 // thus triggering an infinite loop in flyTo
3360 var log = sq < 0.000000001 ? -18 : Math.log(sq);
3361
3362 return log;
3363 }
3364
3365 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3366 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3367 function tanh(n) { return sinh(n) / cosh(n); }
3368
3369 var r0 = r(0);
3370
3371 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3372 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3373
3374 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3375
3376 var start = Date.now(),
3377 S = (r(1) - r0) / rho,
3378 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3379
3380 function frame() {
3381 var t = (Date.now() - start) / duration,
3382 s = easeOut(t) * S;
3383
3384 if (t <= 1) {
3385 this._flyToFrame = requestAnimFrame(frame, this);
3386
3387 this._move(
3388 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3389 this.getScaleZoom(w0 / w(s), startZoom),
3390 {flyTo: true});
3391
3392 } else {
3393 this
3394 ._move(targetCenter, targetZoom)
3395 ._moveEnd(true);
3396 }
3397 }
3398
3399 this._moveStart(true, options.noMoveStart);
3400
3401 frame.call(this);
3402 return this;
3403 },
3404
3405 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3406 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3407 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3408 flyToBounds: function (bounds, options) {
3409 var target = this._getBoundsCenterZoom(bounds, options);
3410 return this.flyTo(target.center, target.zoom, options);
3411 },
3412
3413 // @method setMaxBounds(bounds: LatLngBounds): this
3414 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3415 setMaxBounds: function (bounds) {
3416 bounds = toLatLngBounds(bounds);
3417
3418 if (!bounds.isValid()) {
3419 this.options.maxBounds = null;
3420 return this.off('moveend', this._panInsideMaxBounds);
3421 } else if (this.options.maxBounds) {
3422 this.off('moveend', this._panInsideMaxBounds);
3423 }
3424
3425 this.options.maxBounds = bounds;
3426
3427 if (this._loaded) {
3428 this._panInsideMaxBounds();
3429 }
3430
3431 return this.on('moveend', this._panInsideMaxBounds);
3432 },
3433
3434 // @method setMinZoom(zoom: Number): this
3435 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3436 setMinZoom: function (zoom) {
3437 var oldZoom = this.options.minZoom;
3438 this.options.minZoom = zoom;
3439
3440 if (this._loaded && oldZoom !== zoom) {
3441 this.fire('zoomlevelschange');
3442
3443 if (this.getZoom() < this.options.minZoom) {
3444 return this.setZoom(zoom);
3445 }
3446 }
3447
3448 return this;
3449 },
3450
3451 // @method setMaxZoom(zoom: Number): this
3452 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3453 setMaxZoom: function (zoom) {
3454 var oldZoom = this.options.maxZoom;
3455 this.options.maxZoom = zoom;
3456
3457 if (this._loaded && oldZoom !== zoom) {
3458 this.fire('zoomlevelschange');
3459
3460 if (this.getZoom() > this.options.maxZoom) {
3461 return this.setZoom(zoom);
3462 }
3463 }
3464
3465 return this;
3466 },
3467
3468 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3469 // 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.
3470 panInsideBounds: function (bounds, options) {
3471 this._enforcingBounds = true;
3472 var center = this.getCenter(),
3473 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3474
3475 if (!center.equals(newCenter)) {
3476 this.panTo(newCenter, options);
3477 }
3478
3479 this._enforcingBounds = false;
3480 return this;
3481 },
3482
3483 // @method panInside(latlng: LatLng, options?: padding options): this
3484 // Pans the map the minimum amount to make the `latlng` visible. Use
3485 // padding options to fit the display to more restricted bounds.
3486 // If `latlng` is already within the (optionally padded) display bounds,
3487 // the map will not be panned.
3488 panInside: function (latlng, options) {
3489 options = options || {};
3490
3491 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3492 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3493 pixelCenter = this.project(this.getCenter()),
3494 pixelPoint = this.project(latlng),
3495 pixelBounds = this.getPixelBounds(),
3496 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
3497 paddedSize = paddedBounds.getSize();
3498
3499 if (!paddedBounds.contains(pixelPoint)) {
3500 this._enforcingBounds = true;
3501 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
3502 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
3503 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
3504 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
3505 this.panTo(this.unproject(pixelCenter), options);
3506 this._enforcingBounds = false;
3507 }
3508 return this;
3509 },
3510
3511 // @method invalidateSize(options: Zoom/pan options): this
3512 // Checks if the map container size changed and updates the map if so —
3513 // call it after you've changed the map size dynamically, also animating
3514 // pan by default. If `options.pan` is `false`, panning will not occur.
3515 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3516 // that it doesn't happen often even if the method is called many
3517 // times in a row.
3518
3519 // @alternative
3520 // @method invalidateSize(animate: Boolean): this
3521 // Checks if the map container size changed and updates the map if so —
3522 // call it after you've changed the map size dynamically, also animating
3523 // pan by default.
3524 invalidateSize: function (options) {
3525 if (!this._loaded) { return this; }
3526
3527 options = extend({
3528 animate: false,
3529 pan: true
3530 }, options === true ? {animate: true} : options);
3531
3532 var oldSize = this.getSize();
3533 this._sizeChanged = true;
3534 this._lastCenter = null;
3535
3536 var newSize = this.getSize(),
3537 oldCenter = oldSize.divideBy(2).round(),
3538 newCenter = newSize.divideBy(2).round(),
3539 offset = oldCenter.subtract(newCenter);
3540
3541 if (!offset.x && !offset.y) { return this; }
3542
3543 if (options.animate && options.pan) {
3544 this.panBy(offset);
3545
3546 } else {
3547 if (options.pan) {
3548 this._rawPanBy(offset);
3549 }
3550
3551 this.fire('move');
3552
3553 if (options.debounceMoveend) {
3554 clearTimeout(this._sizeTimer);
3555 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3556 } else {
3557 this.fire('moveend');
3558 }
3559 }
3560
3561 // @section Map state change events
3562 // @event resize: ResizeEvent
3563 // Fired when the map is resized.
3564 return this.fire('resize', {
3565 oldSize: oldSize,
3566 newSize: newSize
3567 });
3568 },
3569
3570 // @section Methods for modifying map state
3571 // @method stop(): this
3572 // Stops the currently running `panTo` or `flyTo` animation, if any.
3573 stop: function () {
3574 this.setZoom(this._limitZoom(this._zoom));
3575 if (!this.options.zoomSnap) {
3576 this.fire('viewreset');
3577 }
3578 return this._stop();
3579 },
3580
3581 // @section Geolocation methods
3582 // @method locate(options?: Locate options): this
3583 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3584 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3585 // and optionally sets the map view to the user's location with respect to
3586 // detection accuracy (or to the world view if geolocation failed).
3587 // Note that, if your page doesn't use HTTPS, this method will fail in
3588 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3589 // See `Locate options` for more details.
3590 locate: function (options) {
3591
3592 options = this._locateOptions = extend({
3593 timeout: 10000,
3594 watch: false
3595 // setView: false
3596 // maxZoom: <Number>
3597 // maximumAge: 0
3598 // enableHighAccuracy: false
3599 }, options);
3600
3601 if (!('geolocation' in navigator)) {
3602 this._handleGeolocationError({
3603 code: 0,
3604 message: 'Geolocation not supported.'
3605 });
3606 return this;
3607 }
3608
3609 var onResponse = bind(this._handleGeolocationResponse, this),
3610 onError = bind(this._handleGeolocationError, this);
3611
3612 if (options.watch) {
3613 this._locationWatchId =
3614 navigator.geolocation.watchPosition(onResponse, onError, options);
3615 } else {
3616 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3617 }
3618 return this;
3619 },
3620
3621 // @method stopLocate(): this
3622 // Stops watching location previously initiated by `map.locate({watch: true})`
3623 // and aborts resetting the map view if map.locate was called with
3624 // `{setView: true}`.
3625 stopLocate: function () {
3626 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3627 navigator.geolocation.clearWatch(this._locationWatchId);
3628 }
3629 if (this._locateOptions) {
3630 this._locateOptions.setView = false;
3631 }
3632 return this;
3633 },
3634
3635 _handleGeolocationError: function (error) {
3636 if (!this._container._leaflet_id) { return; }
3637
3638 var c = error.code,
3639 message = error.message ||
3640 (c === 1 ? 'permission denied' :
3641 (c === 2 ? 'position unavailable' : 'timeout'));
3642
3643 if (this._locateOptions.setView && !this._loaded) {
3644 this.fitWorld();
3645 }
3646
3647 // @section Location events
3648 // @event locationerror: ErrorEvent
3649 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3650 this.fire('locationerror', {
3651 code: c,
3652 message: 'Geolocation error: ' + message + '.'
3653 });
3654 },
3655
3656 _handleGeolocationResponse: function (pos) {
3657 if (!this._container._leaflet_id) { return; }
3658
3659 var lat = pos.coords.latitude,
3660 lng = pos.coords.longitude,
3661 latlng = new LatLng(lat, lng),
3662 bounds = latlng.toBounds(pos.coords.accuracy * 2),
3663 options = this._locateOptions;
3664
3665 if (options.setView) {
3666 var zoom = this.getBoundsZoom(bounds);
3667 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3668 }
3669
3670 var data = {
3671 latlng: latlng,
3672 bounds: bounds,
3673 timestamp: pos.timestamp
3674 };
3675
3676 for (var i in pos.coords) {
3677 if (typeof pos.coords[i] === 'number') {
3678 data[i] = pos.coords[i];
3679 }
3680 }
3681
3682 // @event locationfound: LocationEvent
3683 // Fired when geolocation (using the [`locate`](#map-locate) method)
3684 // went successfully.
3685 this.fire('locationfound', data);
3686 },
3687
3688 // TODO Appropriate docs section?
3689 // @section Other Methods
3690 // @method addHandler(name: String, HandlerClass: Function): this
3691 // Adds a new `Handler` to the map, given its name and constructor function.
3692 addHandler: function (name, HandlerClass) {
3693 if (!HandlerClass) { return this; }
3694
3695 var handler = this[name] = new HandlerClass(this);
3696
3697 this._handlers.push(handler);
3698
3699 if (this.options[name]) {
3700 handler.enable();
3701 }
3702
3703 return this;
3704 },
3705
3706 // @method remove(): this
3707 // Destroys the map and clears all related event listeners.
3708 remove: function () {
3709
3710 this._initEvents(true);
3711 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }
3712
3713 if (this._containerId !== this._container._leaflet_id) {
3714 throw new Error('Map container is being reused by another instance');
3715 }
3716
3717 try {
3718 // throws error in IE6-8
3719 delete this._container._leaflet_id;
3720 delete this._containerId;
3721 } catch (e) {
3722 /*eslint-disable */
3723 this._container._leaflet_id = undefined;
3724 /* eslint-enable */
3725 this._containerId = undefined;
3726 }
3727
3728 if (this._locationWatchId !== undefined) {
3729 this.stopLocate();
3730 }
3731
3732 this._stop();
3733
3734 remove(this._mapPane);
3735
3736 if (this._clearControlPos) {
3737 this._clearControlPos();
3738 }
3739 if (this._resizeRequest) {
3740 cancelAnimFrame(this._resizeRequest);
3741 this._resizeRequest = null;
3742 }
3743
3744 this._clearHandlers();
3745
3746 if (this._loaded) {
3747 // @section Map state change events
3748 // @event unload: Event
3749 // Fired when the map is destroyed with [remove](#map-remove) method.
3750 this.fire('unload');
3751 }
3752
3753 var i;
3754 for (i in this._layers) {
3755 this._layers[i].remove();
3756 }
3757 for (i in this._panes) {
3758 remove(this._panes[i]);
3759 }
3760
3761 this._layers = [];
3762 this._panes = [];
3763 delete this._mapPane;
3764 delete this._renderer;
3765
3766 return this;
3767 },
3768
3769 // @section Other Methods
3770 // @method createPane(name: String, container?: HTMLElement): HTMLElement
3771 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3772 // then returns it. The pane is created as a child of `container`, or
3773 // as a child of the main map pane if not set.
3774 createPane: function (name, container) {
3775 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3776 pane = create$1('div', className, container || this._mapPane);
3777
3778 if (name) {
3779 this._panes[name] = pane;
3780 }
3781 return pane;
3782 },
3783
3784 // @section Methods for Getting Map State
3785
3786 // @method getCenter(): LatLng
3787 // Returns the geographical center of the map view
3788 getCenter: function () {
3789 this._checkIfLoaded();
3790
3791 if (this._lastCenter && !this._moved()) {
3792 return this._lastCenter;
3793 }
3794 return this.layerPointToLatLng(this._getCenterLayerPoint());
3795 },
3796
3797 // @method getZoom(): Number
3798 // Returns the current zoom level of the map view
3799 getZoom: function () {
3800 return this._zoom;
3801 },
3802
3803 // @method getBounds(): LatLngBounds
3804 // Returns the geographical bounds visible in the current map view
3805 getBounds: function () {
3806 var bounds = this.getPixelBounds(),
3807 sw = this.unproject(bounds.getBottomLeft()),
3808 ne = this.unproject(bounds.getTopRight());
3809
3810 return new LatLngBounds(sw, ne);
3811 },
3812
3813 // @method getMinZoom(): Number
3814 // 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.
3815 getMinZoom: function () {
3816 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3817 },
3818
3819 // @method getMaxZoom(): Number
3820 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3821 getMaxZoom: function () {
3822 return this.options.maxZoom === undefined ?
3823 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3824 this.options.maxZoom;
3825 },
3826
3827 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3828 // Returns the maximum zoom level on which the given bounds fit to the map
3829 // view in its entirety. If `inside` (optional) is set to `true`, the method
3830 // instead returns the minimum zoom level on which the map view fits into
3831 // the given bounds in its entirety.
3832 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3833 bounds = toLatLngBounds(bounds);
3834 padding = toPoint(padding || [0, 0]);
3835
3836 var zoom = this.getZoom() || 0,
3837 min = this.getMinZoom(),
3838 max = this.getMaxZoom(),
3839 nw = bounds.getNorthWest(),
3840 se = bounds.getSouthEast(),
3841 size = this.getSize().subtract(padding),
3842 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3843 snap = Browser.any3d ? this.options.zoomSnap : 1,
3844 scalex = size.x / boundsSize.x,
3845 scaley = size.y / boundsSize.y,
3846 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3847
3848 zoom = this.getScaleZoom(scale, zoom);
3849
3850 if (snap) {
3851 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3852 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3853 }
3854
3855 return Math.max(min, Math.min(max, zoom));
3856 },
3857
3858 // @method getSize(): Point
3859 // Returns the current size of the map container (in pixels).
3860 getSize: function () {
3861 if (!this._size || this._sizeChanged) {
3862 this._size = new Point(
3863 this._container.clientWidth || 0,
3864 this._container.clientHeight || 0);
3865
3866 this._sizeChanged = false;
3867 }
3868 return this._size.clone();
3869 },
3870
3871 // @method getPixelBounds(): Bounds
3872 // Returns the bounds of the current map view in projected pixel
3873 // coordinates (sometimes useful in layer and overlay implementations).
3874 getPixelBounds: function (center, zoom) {
3875 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3876 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3877 },
3878
3879 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3880 // the map pane? "left point of the map layer" can be confusing, specially
3881 // since there can be negative offsets.
3882 // @method getPixelOrigin(): Point
3883 // Returns the projected pixel coordinates of the top left point of
3884 // the map layer (useful in custom layer and overlay implementations).
3885 getPixelOrigin: function () {
3886 this._checkIfLoaded();
3887 return this._pixelOrigin;
3888 },
3889
3890 // @method getPixelWorldBounds(zoom?: Number): Bounds
3891 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3892 // If `zoom` is omitted, the map's current zoom level is used.
3893 getPixelWorldBounds: function (zoom) {
3894 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3895 },
3896
3897 // @section Other Methods
3898
3899 // @method getPane(pane: String|HTMLElement): HTMLElement
3900 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3901 getPane: function (pane) {
3902 return typeof pane === 'string' ? this._panes[pane] : pane;
3903 },
3904
3905 // @method getPanes(): Object
3906 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3907 // the panes as values.
3908 getPanes: function () {
3909 return this._panes;
3910 },
3911
3912 // @method getContainer: HTMLElement
3913 // Returns the HTML element that contains the map.
3914 getContainer: function () {
3915 return this._container;
3916 },
3917
3918
3919 // @section Conversion Methods
3920
3921 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3922 // Returns the scale factor to be applied to a map transition from zoom level
3923 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3924 getZoomScale: function (toZoom, fromZoom) {
3925 // TODO replace with universal implementation after refactoring projections
3926 var crs = this.options.crs;
3927 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3928 return crs.scale(toZoom) / crs.scale(fromZoom);
3929 },
3930
3931 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3932 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3933 // level and everything is scaled by a factor of `scale`. Inverse of
3934 // [`getZoomScale`](#map-getZoomScale).
3935 getScaleZoom: function (scale, fromZoom) {
3936 var crs = this.options.crs;
3937 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3938 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3939 return isNaN(zoom) ? Infinity : zoom;
3940 },
3941
3942 // @method project(latlng: LatLng, zoom: Number): Point
3943 // Projects a geographical coordinate `LatLng` according to the projection
3944 // of the map's CRS, then scales it according to `zoom` and the CRS's
3945 // `Transformation`. The result is pixel coordinate relative to
3946 // the CRS origin.
3947 project: function (latlng, zoom) {
3948 zoom = zoom === undefined ? this._zoom : zoom;
3949 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3950 },
3951
3952 // @method unproject(point: Point, zoom: Number): LatLng
3953 // Inverse of [`project`](#map-project).
3954 unproject: function (point, zoom) {
3955 zoom = zoom === undefined ? this._zoom : zoom;
3956 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3957 },
3958
3959 // @method layerPointToLatLng(point: Point): LatLng
3960 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3961 // returns the corresponding geographical coordinate (for the current zoom level).
3962 layerPointToLatLng: function (point) {
3963 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3964 return this.unproject(projectedPoint);
3965 },
3966
3967 // @method latLngToLayerPoint(latlng: LatLng): Point
3968 // Given a geographical coordinate, returns the corresponding pixel coordinate
3969 // relative to the [origin pixel](#map-getpixelorigin).
3970 latLngToLayerPoint: function (latlng) {
3971 var projectedPoint = this.project(toLatLng(latlng))._round();
3972 return projectedPoint._subtract(this.getPixelOrigin());
3973 },
3974
3975 // @method wrapLatLng(latlng: LatLng): LatLng
3976 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3977 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3978 // CRS's bounds.
3979 // By default this means longitude is wrapped around the dateline so its
3980 // value is between -180 and +180 degrees.
3981 wrapLatLng: function (latlng) {
3982 return this.options.crs.wrapLatLng(toLatLng(latlng));
3983 },
3984
3985 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3986 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3987 // its center is within the CRS's bounds.
3988 // By default this means the center longitude is wrapped around the dateline so its
3989 // value is between -180 and +180 degrees, and the majority of the bounds
3990 // overlaps the CRS's bounds.
3991 wrapLatLngBounds: function (latlng) {
3992 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3993 },
3994
3995 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3996 // Returns the distance between two geographical coordinates according to
3997 // the map's CRS. By default this measures distance in meters.
3998 distance: function (latlng1, latlng2) {
3999 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4000 },
4001
4002 // @method containerPointToLayerPoint(point: Point): Point
4003 // Given a pixel coordinate relative to the map container, returns the corresponding
4004 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4005 containerPointToLayerPoint: function (point) { // (Point)
4006 return toPoint(point).subtract(this._getMapPanePos());
4007 },
4008
4009 // @method layerPointToContainerPoint(point: Point): Point
4010 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4011 // returns the corresponding pixel coordinate relative to the map container.
4012 layerPointToContainerPoint: function (point) { // (Point)
4013 return toPoint(point).add(this._getMapPanePos());
4014 },
4015
4016 // @method containerPointToLatLng(point: Point): LatLng
4017 // Given a pixel coordinate relative to the map container, returns
4018 // the corresponding geographical coordinate (for the current zoom level).
4019 containerPointToLatLng: function (point) {
4020 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4021 return this.layerPointToLatLng(layerPoint);
4022 },
4023
4024 // @method latLngToContainerPoint(latlng: LatLng): Point
4025 // Given a geographical coordinate, returns the corresponding pixel coordinate
4026 // relative to the map container.
4027 latLngToContainerPoint: function (latlng) {
4028 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4029 },
4030
4031 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4032 // Given a MouseEvent object, returns the pixel coordinate relative to the
4033 // map container where the event took place.
4034 mouseEventToContainerPoint: function (e) {
4035 return getMousePosition(e, this._container);
4036 },
4037
4038 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4039 // Given a MouseEvent object, returns the pixel coordinate relative to
4040 // the [origin pixel](#map-getpixelorigin) where the event took place.
4041 mouseEventToLayerPoint: function (e) {
4042 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4043 },
4044
4045 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4046 // Given a MouseEvent object, returns geographical coordinate where the
4047 // event took place.
4048 mouseEventToLatLng: function (e) { // (MouseEvent)
4049 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4050 },
4051
4052
4053 // map initialization methods
4054
4055 _initContainer: function (id) {
4056 var container = this._container = get(id);
4057
4058 if (!container) {
4059 throw new Error('Map container not found.');
4060 } else if (container._leaflet_id) {
4061 throw new Error('Map container is already initialized.');
4062 }
4063
4064 on(container, 'scroll', this._onScroll, this);
4065 this._containerId = stamp(container);
4066 },
4067
4068 _initLayout: function () {
4069 var container = this._container;
4070
4071 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;
4072
4073 addClass(container, 'leaflet-container' +
4074 (Browser.touch ? ' leaflet-touch' : '') +
4075 (Browser.retina ? ' leaflet-retina' : '') +
4076 (Browser.ielt9 ? ' leaflet-oldie' : '') +
4077 (Browser.safari ? ' leaflet-safari' : '') +
4078 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4079
4080 var position = getStyle(container, 'position');
4081
4082 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4083 container.style.position = 'relative';
4084 }
4085
4086 this._initPanes();
4087
4088 if (this._initControlPos) {
4089 this._initControlPos();
4090 }
4091 },
4092
4093 _initPanes: function () {
4094 var panes = this._panes = {};
4095 this._paneRenderers = {};
4096
4097 // @section
4098 //
4099 // Panes are DOM elements used to control the ordering of layers on the map. You
4100 // can access panes with [`map.getPane`](#map-getpane) or
4101 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4102 // [`map.createPane`](#map-createpane) method.
4103 //
4104 // Every map has the following default panes that differ only in zIndex.
4105 //
4106 // @pane mapPane: HTMLElement = 'auto'
4107 // Pane that contains all other map panes
4108
4109 this._mapPane = this.createPane('mapPane', this._container);
4110 setPosition(this._mapPane, new Point(0, 0));
4111
4112 // @pane tilePane: HTMLElement = 200
4113 // Pane for `GridLayer`s and `TileLayer`s
4114 this.createPane('tilePane');
4115 // @pane overlayPane: HTMLElement = 400
4116 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4117 this.createPane('overlayPane');
4118 // @pane shadowPane: HTMLElement = 500
4119 // Pane for overlay shadows (e.g. `Marker` shadows)
4120 this.createPane('shadowPane');
4121 // @pane markerPane: HTMLElement = 600
4122 // Pane for `Icon`s of `Marker`s
4123 this.createPane('markerPane');
4124 // @pane tooltipPane: HTMLElement = 650
4125 // Pane for `Tooltip`s.
4126 this.createPane('tooltipPane');
4127 // @pane popupPane: HTMLElement = 700
4128 // Pane for `Popup`s.
4129 this.createPane('popupPane');
4130
4131 if (!this.options.markerZoomAnimation) {
4132 addClass(panes.markerPane, 'leaflet-zoom-hide');
4133 addClass(panes.shadowPane, 'leaflet-zoom-hide');
4134 }
4135 },
4136
4137
4138 // private methods that modify map state
4139
4140 // @section Map state change events
4141 _resetView: function (center, zoom) {
4142 setPosition(this._mapPane, new Point(0, 0));
4143
4144 var loading = !this._loaded;
4145 this._loaded = true;
4146 zoom = this._limitZoom(zoom);
4147
4148 this.fire('viewprereset');
4149
4150 var zoomChanged = this._zoom !== zoom;
4151 this
4152 ._moveStart(zoomChanged, false)
4153 ._move(center, zoom)
4154 ._moveEnd(zoomChanged);
4155
4156 // @event viewreset: Event
4157 // Fired when the map needs to redraw its content (this usually happens
4158 // on map zoom or load). Very useful for creating custom overlays.
4159 this.fire('viewreset');
4160
4161 // @event load: Event
4162 // Fired when the map is initialized (when its center and zoom are set
4163 // for the first time).
4164 if (loading) {
4165 this.fire('load');
4166 }
4167 },
4168
4169 _moveStart: function (zoomChanged, noMoveStart) {
4170 // @event zoomstart: Event
4171 // Fired when the map zoom is about to change (e.g. before zoom animation).
4172 // @event movestart: Event
4173 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4174 if (zoomChanged) {
4175 this.fire('zoomstart');
4176 }
4177 if (!noMoveStart) {
4178 this.fire('movestart');
4179 }
4180 return this;
4181 },
4182
4183 _move: function (center, zoom, data, supressEvent) {
4184 if (zoom === undefined) {
4185 zoom = this._zoom;
4186 }
4187 var zoomChanged = this._zoom !== zoom;
4188
4189 this._zoom = zoom;
4190 this._lastCenter = center;
4191 this._pixelOrigin = this._getNewPixelOrigin(center);
4192
4193 if (!supressEvent) {
4194 // @event zoom: Event
4195 // Fired repeatedly during any change in zoom level,
4196 // including zoom and fly animations.
4197 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
4198 this.fire('zoom', data);
4199 }
4200
4201 // @event move: Event
4202 // Fired repeatedly during any movement of the map,
4203 // including pan and fly animations.
4204 this.fire('move', data);
4205 } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530
4206 this.fire('zoom', data);
4207 }
4208 return this;
4209 },
4210
4211 _moveEnd: function (zoomChanged) {
4212 // @event zoomend: Event
4213 // Fired when the map zoom changed, after any animations.
4214 if (zoomChanged) {
4215 this.fire('zoomend');
4216 }
4217
4218 // @event moveend: Event
4219 // Fired when the center of the map stops changing
4220 // (e.g. user stopped dragging the map or after non-centered zoom).
4221 return this.fire('moveend');
4222 },
4223
4224 _stop: function () {
4225 cancelAnimFrame(this._flyToFrame);
4226 if (this._panAnim) {
4227 this._panAnim.stop();
4228 }
4229 return this;
4230 },
4231
4232 _rawPanBy: function (offset) {
4233 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4234 },
4235
4236 _getZoomSpan: function () {
4237 return this.getMaxZoom() - this.getMinZoom();
4238 },
4239
4240 _panInsideMaxBounds: function () {
4241 if (!this._enforcingBounds) {
4242 this.panInsideBounds(this.options.maxBounds);
4243 }
4244 },
4245
4246 _checkIfLoaded: function () {
4247 if (!this._loaded) {
4248 throw new Error('Set map center and zoom first.');
4249 }
4250 },
4251
4252 // DOM event handling
4253
4254 // @section Interaction events
4255 _initEvents: function (remove) {
4256 this._targets = {};
4257 this._targets[stamp(this._container)] = this;
4258
4259 var onOff = remove ? off : on;
4260
4261 // @event click: MouseEvent
4262 // Fired when the user clicks (or taps) the map.
4263 // @event dblclick: MouseEvent
4264 // Fired when the user double-clicks (or double-taps) the map.
4265 // @event mousedown: MouseEvent
4266 // Fired when the user pushes the mouse button on the map.
4267 // @event mouseup: MouseEvent
4268 // Fired when the user releases the mouse button on the map.
4269 // @event mouseover: MouseEvent
4270 // Fired when the mouse enters the map.
4271 // @event mouseout: MouseEvent
4272 // Fired when the mouse leaves the map.
4273 // @event mousemove: MouseEvent
4274 // Fired while the mouse moves over the map.
4275 // @event contextmenu: MouseEvent
4276 // Fired when the user pushes the right mouse button on the map, prevents
4277 // default browser context menu from showing if there are listeners on
4278 // this event. Also fired on mobile when the user holds a single touch
4279 // for a second (also called long press).
4280 // @event keypress: KeyboardEvent
4281 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4282 // @event keydown: KeyboardEvent
4283 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4284 // the `keydown` event is fired for keys that produce a character value and for keys
4285 // that do not produce a character value.
4286 // @event keyup: KeyboardEvent
4287 // Fired when the user releases a key from the keyboard while the map is focused.
4288 onOff(this._container, 'click dblclick mousedown mouseup ' +
4289 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4290
4291 if (this.options.trackResize) {
4292 onOff(window, 'resize', this._onResize, this);
4293 }
4294
4295 if (Browser.any3d && this.options.transform3DLimit) {
4296 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4297 }
4298 },
4299
4300 _onResize: function () {
4301 cancelAnimFrame(this._resizeRequest);
4302 this._resizeRequest = requestAnimFrame(
4303 function () { this.invalidateSize({debounceMoveend: true}); }, this);
4304 },
4305
4306 _onScroll: function () {
4307 this._container.scrollTop = 0;
4308 this._container.scrollLeft = 0;
4309 },
4310
4311 _onMoveEnd: function () {
4312 var pos = this._getMapPanePos();
4313 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4314 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4315 // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/
4316 this._resetView(this.getCenter(), this.getZoom());
4317 }
4318 },
4319
4320 _findEventTargets: function (e, type) {
4321 var targets = [],
4322 target,
4323 isHover = type === 'mouseout' || type === 'mouseover',
4324 src = e.target || e.srcElement,
4325 dragging = false;
4326
4327 while (src) {
4328 target = this._targets[stamp(src)];
4329 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
4330 // Prevent firing click after you just dragged an object.
4331 dragging = true;
4332 break;
4333 }
4334 if (target && target.listens(type, true)) {
4335 if (isHover && !isExternalTarget(src, e)) { break; }
4336 targets.push(target);
4337 if (isHover) { break; }
4338 }
4339 if (src === this._container) { break; }
4340 src = src.parentNode;
4341 }
4342 if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
4343 targets = [this];
4344 }
4345 return targets;
4346 },
4347
4348 _isClickDisabled: function (el) {
4349 while (el !== this._container) {
4350 if (el['_leaflet_disable_click']) { return true; }
4351 el = el.parentNode;
4352 }
4353 },
4354
4355 _handleDOMEvent: function (e) {
4356 var el = (e.target || e.srcElement);
4357 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
4358 return;
4359 }
4360
4361 var type = e.type;
4362
4363 if (type === 'mousedown') {
4364 // prevents outline when clicking on keyboard-focusable element
4365 preventOutline(el);
4366 }
4367
4368 this._fireDOMEvent(e, type);
4369 },
4370
4371 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4372
4373 _fireDOMEvent: function (e, type, canvasTargets) {
4374
4375 if (e.type === 'click') {
4376 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4377 // @event preclick: MouseEvent
4378 // Fired before mouse click on the map (sometimes useful when you
4379 // want something to happen on click before any existing click
4380 // handlers start running).
4381 var synth = extend({}, e);
4382 synth.type = 'preclick';
4383 this._fireDOMEvent(synth, synth.type, canvasTargets);
4384 }
4385
4386 // Find the layer the event is propagating from and its parents.
4387 var targets = this._findEventTargets(e, type);
4388
4389 if (canvasTargets) {
4390 var filtered = []; // pick only targets with listeners
4391 for (var i = 0; i < canvasTargets.length; i++) {
4392 if (canvasTargets[i].listens(type, true)) {
4393 filtered.push(canvasTargets[i]);
4394 }
4395 }
4396 targets = filtered.concat(targets);
4397 }
4398
4399 if (!targets.length) { return; }
4400
4401 if (type === 'contextmenu') {
4402 preventDefault(e);
4403 }
4404
4405 var target = targets[0];
4406 var data = {
4407 originalEvent: e
4408 };
4409
4410 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4411 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4412 data.containerPoint = isMarker ?
4413 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4414 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4415 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4416 }
4417
4418 for (i = 0; i < targets.length; i++) {
4419 targets[i].fire(type, data, true);
4420 if (data.originalEvent._stopped ||
4421 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4422 }
4423 },
4424
4425 _draggableMoved: function (obj) {
4426 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4427 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4428 },
4429
4430 _clearHandlers: function () {
4431 for (var i = 0, len = this._handlers.length; i < len; i++) {
4432 this._handlers[i].disable();
4433 }
4434 },
4435
4436 // @section Other Methods
4437
4438 // @method whenReady(fn: Function, context?: Object): this
4439 // Runs the given function `fn` when the map gets initialized with
4440 // a view (center and zoom) and at least one layer, or immediately
4441 // if it's already initialized, optionally passing a function context.
4442 whenReady: function (callback, context) {
4443 if (this._loaded) {
4444 callback.call(context || this, {target: this});
4445 } else {
4446 this.on('load', callback, context);
4447 }
4448 return this;
4449 },
4450
4451
4452 // private methods for getting map state
4453
4454 _getMapPanePos: function () {
4455 return getPosition(this._mapPane) || new Point(0, 0);
4456 },
4457
4458 _moved: function () {
4459 var pos = this._getMapPanePos();
4460 return pos && !pos.equals([0, 0]);
4461 },
4462
4463 _getTopLeftPoint: function (center, zoom) {
4464 var pixelOrigin = center && zoom !== undefined ?
4465 this._getNewPixelOrigin(center, zoom) :
4466 this.getPixelOrigin();
4467 return pixelOrigin.subtract(this._getMapPanePos());
4468 },
4469
4470 _getNewPixelOrigin: function (center, zoom) {
4471 var viewHalf = this.getSize()._divideBy(2);
4472 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4473 },
4474
4475 _latLngToNewLayerPoint: function (latlng, zoom, center) {
4476 var topLeft = this._getNewPixelOrigin(center, zoom);
4477 return this.project(latlng, zoom)._subtract(topLeft);
4478 },
4479
4480 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4481 var topLeft = this._getNewPixelOrigin(center, zoom);
4482 return toBounds([
4483 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4484 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4485 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4486 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4487 ]);
4488 },
4489
4490 // layer point of the current center
4491 _getCenterLayerPoint: function () {
4492 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4493 },
4494
4495 // offset of the specified place to the current center in pixels
4496 _getCenterOffset: function (latlng) {
4497 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4498 },
4499
4500 // adjust center for view to get inside bounds
4501 _limitCenter: function (center, zoom, bounds) {
4502
4503 if (!bounds) { return center; }
4504
4505 var centerPoint = this.project(center, zoom),
4506 viewHalf = this.getSize().divideBy(2),
4507 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4508 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4509
4510 // If offset is less than a pixel, ignore.
4511 // This prevents unstable projections from getting into
4512 // an infinite loop of tiny offsets.
4513 if (offset.round().equals([0, 0])) {
4514 return center;
4515 }
4516
4517 return this.unproject(centerPoint.add(offset), zoom);
4518 },
4519
4520 // adjust offset for view to get inside bounds
4521 _limitOffset: function (offset, bounds) {
4522 if (!bounds) { return offset; }
4523
4524 var viewBounds = this.getPixelBounds(),
4525 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4526
4527 return offset.add(this._getBoundsOffset(newBounds, bounds));
4528 },
4529
4530 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4531 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4532 var projectedMaxBounds = toBounds(
4533 this.project(maxBounds.getNorthEast(), zoom),
4534 this.project(maxBounds.getSouthWest(), zoom)
4535 ),
4536 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4537 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4538
4539 dx = this._rebound(minOffset.x, -maxOffset.x),
4540 dy = this._rebound(minOffset.y, -maxOffset.y);
4541
4542 return new Point(dx, dy);
4543 },
4544
4545 _rebound: function (left, right) {
4546 return left + right > 0 ?
4547 Math.round(left - right) / 2 :
4548 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4549 },
4550
4551 _limitZoom: function (zoom) {
4552 var min = this.getMinZoom(),
4553 max = this.getMaxZoom(),
4554 snap = Browser.any3d ? this.options.zoomSnap : 1;
4555 if (snap) {
4556 zoom = Math.round(zoom / snap) * snap;
4557 }
4558 return Math.max(min, Math.min(max, zoom));
4559 },
4560
4561 _onPanTransitionStep: function () {
4562 this.fire('move');
4563 },
4564
4565 _onPanTransitionEnd: function () {
4566 removeClass(this._mapPane, 'leaflet-pan-anim');
4567 this.fire('moveend');
4568 },
4569
4570 _tryAnimatedPan: function (center, options) {
4571 // difference between the new and current centers in pixels
4572 var offset = this._getCenterOffset(center)._trunc();
4573
4574 // don't animate too far unless animate: true specified in options
4575 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4576
4577 this.panBy(offset, options);
4578
4579 return true;
4580 },
4581
4582 _createAnimProxy: function () {
4583
4584 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4585 this._panes.mapPane.appendChild(proxy);
4586
4587 this.on('zoomanim', function (e) {
4588 var prop = TRANSFORM,
4589 transform = this._proxy.style[prop];
4590
4591 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4592
4593 // workaround for case when transform is the same and so transitionend event is not fired
4594 if (transform === this._proxy.style[prop] && this._animatingZoom) {
4595 this._onZoomTransitionEnd();
4596 }
4597 }, this);
4598
4599 this.on('load moveend', this._animMoveEnd, this);
4600
4601 this._on('unload', this._destroyAnimProxy, this);
4602 },
4603
4604 _destroyAnimProxy: function () {
4605 remove(this._proxy);
4606 this.off('load moveend', this._animMoveEnd, this);
4607 delete this._proxy;
4608 },
4609
4610 _animMoveEnd: function () {
4611 var c = this.getCenter(),
4612 z = this.getZoom();
4613 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4614 },
4615
4616 _catchTransitionEnd: function (e) {
4617 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4618 this._onZoomTransitionEnd();
4619 }
4620 },
4621
4622 _nothingToAnimate: function () {
4623 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4624 },
4625
4626 _tryAnimatedZoom: function (center, zoom, options) {
4627
4628 if (this._animatingZoom) { return true; }
4629
4630 options = options || {};
4631
4632 // don't animate if disabled, not supported or zoom difference is too large
4633 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4634 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4635
4636 // offset is the pixel coords of the zoom origin relative to the current center
4637 var scale = this.getZoomScale(zoom),
4638 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4639
4640 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4641 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4642
4643 requestAnimFrame(function () {
4644 this
4645 ._moveStart(true, false)
4646 ._animateZoom(center, zoom, true);
4647 }, this);
4648
4649 return true;
4650 },
4651
4652 _animateZoom: function (center, zoom, startAnim, noUpdate) {
4653 if (!this._mapPane) { return; }
4654
4655 if (startAnim) {
4656 this._animatingZoom = true;
4657
4658 // remember what center/zoom to set after animation
4659 this._animateToCenter = center;
4660 this._animateToZoom = zoom;
4661
4662 addClass(this._mapPane, 'leaflet-zoom-anim');
4663 }
4664
4665 // @section Other Events
4666 // @event zoomanim: ZoomAnimEvent
4667 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4668 this.fire('zoomanim', {
4669 center: center,
4670 zoom: zoom,
4671 noUpdate: noUpdate
4672 });
4673
4674 if (!this._tempFireZoomEvent) {
4675 this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
4676 }
4677
4678 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
4679
4680 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4681 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4682 },
4683
4684 _onZoomTransitionEnd: function () {
4685 if (!this._animatingZoom) { return; }
4686
4687 if (this._mapPane) {
4688 removeClass(this._mapPane, 'leaflet-zoom-anim');
4689 }
4690
4691 this._animatingZoom = false;
4692
4693 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
4694
4695 if (this._tempFireZoomEvent) {
4696 this.fire('zoom');
4697 }
4698 delete this._tempFireZoomEvent;
4699
4700 this.fire('move');
4701
4702 this._moveEnd(true);
4703 }
4704 });
4705
4706 // @section
4707
4708 // @factory L.map(id: String, options?: Map options)
4709 // Instantiates a map object given the DOM ID of a `<div>` element
4710 // and optionally an object literal with `Map options`.
4711 //
4712 // @alternative
4713 // @factory L.map(el: HTMLElement, options?: Map options)
4714 // Instantiates a map object given an instance of a `<div>` HTML element
4715 // and optionally an object literal with `Map options`.
4716 function createMap(id, options) {
4717 return new Map(id, options);
4718 }
4719
4720 /*
4721 * @class Control
4722 * @aka L.Control
4723 * @inherits Class
4724 *
4725 * L.Control is a base class for implementing map controls. Handles positioning.
4726 * All other controls extend from this class.
4727 */
4728
4729 var Control = Class.extend({
4730 // @section
4731 // @aka Control Options
4732 options: {
4733 // @option position: String = 'topright'
4734 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4735 // `'topright'`, `'bottomleft'` or `'bottomright'`
4736 position: 'topright'
4737 },
4738
4739 initialize: function (options) {
4740 setOptions(this, options);
4741 },
4742
4743 /* @section
4744 * Classes extending L.Control will inherit the following methods:
4745 *
4746 * @method getPosition: string
4747 * Returns the position of the control.
4748 */
4749 getPosition: function () {
4750 return this.options.position;
4751 },
4752
4753 // @method setPosition(position: string): this
4754 // Sets the position of the control.
4755 setPosition: function (position) {
4756 var map = this._map;
4757
4758 if (map) {
4759 map.removeControl(this);
4760 }
4761
4762 this.options.position = position;
4763
4764 if (map) {
4765 map.addControl(this);
4766 }
4767
4768 return this;
4769 },
4770
4771 // @method getContainer: HTMLElement
4772 // Returns the HTMLElement that contains the control.
4773 getContainer: function () {
4774 return this._container;
4775 },
4776
4777 // @method addTo(map: Map): this
4778 // Adds the control to the given map.
4779 addTo: function (map) {
4780 this.remove();
4781 this._map = map;
4782
4783 var container = this._container = this.onAdd(map),
4784 pos = this.getPosition(),
4785 corner = map._controlCorners[pos];
4786
4787 addClass(container, 'leaflet-control');
4788
4789 if (pos.indexOf('bottom') !== -1) {
4790 corner.insertBefore(container, corner.firstChild);
4791 } else {
4792 corner.appendChild(container);
4793 }
4794
4795 this._map.on('unload', this.remove, this);
4796
4797 return this;
4798 },
4799
4800 // @method remove: this
4801 // Removes the control from the map it is currently active on.
4802 remove: function () {
4803 if (!this._map) {
4804 return this;
4805 }
4806
4807 remove(this._container);
4808
4809 if (this.onRemove) {
4810 this.onRemove(this._map);
4811 }
4812
4813 this._map.off('unload', this.remove, this);
4814 this._map = null;
4815
4816 return this;
4817 },
4818
4819 _refocusOnMap: function (e) {
4820 // if map exists and event is not a keyboard event
4821 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4822 this._map.getContainer().focus();
4823 }
4824 }
4825 });
4826
4827 var control = function (options) {
4828 return new Control(options);
4829 };
4830
4831 /* @section Extension methods
4832 * @uninheritable
4833 *
4834 * Every control should extend from `L.Control` and (re-)implement the following methods.
4835 *
4836 * @method onAdd(map: Map): HTMLElement
4837 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4838 *
4839 * @method onRemove(map: Map)
4840 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4841 */
4842
4843 /* @namespace Map
4844 * @section Methods for Layers and Controls
4845 */
4846 Map.include({
4847 // @method addControl(control: Control): this
4848 // Adds the given control to the map
4849 addControl: function (control) {
4850 control.addTo(this);
4851 return this;
4852 },
4853
4854 // @method removeControl(control: Control): this
4855 // Removes the given control from the map
4856 removeControl: function (control) {
4857 control.remove();
4858 return this;
4859 },
4860
4861 _initControlPos: function () {
4862 var corners = this._controlCorners = {},
4863 l = 'leaflet-',
4864 container = this._controlContainer =
4865 create$1('div', l + 'control-container', this._container);
4866
4867 function createCorner(vSide, hSide) {
4868 var className = l + vSide + ' ' + l + hSide;
4869
4870 corners[vSide + hSide] = create$1('div', className, container);
4871 }
4872
4873 createCorner('top', 'left');
4874 createCorner('top', 'right');
4875 createCorner('bottom', 'left');
4876 createCorner('bottom', 'right');
4877 },
4878
4879 _clearControlPos: function () {
4880 for (var i in this._controlCorners) {
4881 remove(this._controlCorners[i]);
4882 }
4883 remove(this._controlContainer);
4884 delete this._controlCorners;
4885 delete this._controlContainer;
4886 }
4887 });
4888
4889 /*
4890 * @class Control.Layers
4891 * @aka L.Control.Layers
4892 * @inherits Control
4893 *
4894 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`.
4895 *
4896 * @example
4897 *
4898 * ```js
4899 * var baseLayers = {
4900 * "Mapbox": mapbox,
4901 * "OpenStreetMap": osm
4902 * };
4903 *
4904 * var overlays = {
4905 * "Marker": marker,
4906 * "Roads": roadsLayer
4907 * };
4908 *
4909 * L.control.layers(baseLayers, overlays).addTo(map);
4910 * ```
4911 *
4912 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4913 *
4914 * ```js
4915 * {
4916 * "<someName1>": layer1,
4917 * "<someName2>": layer2
4918 * }
4919 * ```
4920 *
4921 * The layer names can contain HTML, which allows you to add additional styling to the items:
4922 *
4923 * ```js
4924 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4925 * ```
4926 */
4927
4928 var Layers = Control.extend({
4929 // @section
4930 // @aka Control.Layers options
4931 options: {
4932 // @option collapsed: Boolean = true
4933 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.
4934 collapsed: true,
4935 position: 'topright',
4936
4937 // @option autoZIndex: Boolean = true
4938 // 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.
4939 autoZIndex: true,
4940
4941 // @option hideSingleBase: Boolean = false
4942 // If `true`, the base layers in the control will be hidden when there is only one.
4943 hideSingleBase: false,
4944
4945 // @option sortLayers: Boolean = false
4946 // Whether to sort the layers. When `false`, layers will keep the order
4947 // in which they were added to the control.
4948 sortLayers: false,
4949
4950 // @option sortFunction: Function = *
4951 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4952 // that will be used for sorting the layers, when `sortLayers` is `true`.
4953 // The function receives both the `L.Layer` instances and their names, as in
4954 // `sortFunction(layerA, layerB, nameA, nameB)`.
4955 // By default, it sorts layers alphabetically by their name.
4956 sortFunction: function (layerA, layerB, nameA, nameB) {
4957 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4958 }
4959 },
4960
4961 initialize: function (baseLayers, overlays, options) {
4962 setOptions(this, options);
4963
4964 this._layerControlInputs = [];
4965 this._layers = [];
4966 this._lastZIndex = 0;
4967 this._handlingClick = false;
4968
4969 for (var i in baseLayers) {
4970 this._addLayer(baseLayers[i], i);
4971 }
4972
4973 for (i in overlays) {
4974 this._addLayer(overlays[i], i, true);
4975 }
4976 },
4977
4978 onAdd: function (map) {
4979 this._initLayout();
4980 this._update();
4981
4982 this._map = map;
4983 map.on('zoomend', this._checkDisabledLayers, this);
4984
4985 for (var i = 0; i < this._layers.length; i++) {
4986 this._layers[i].layer.on('add remove', this._onLayerChange, this);
4987 }
4988
4989 return this._container;
4990 },
4991
4992 addTo: function (map) {
4993 Control.prototype.addTo.call(this, map);
4994 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4995 return this._expandIfNotCollapsed();
4996 },
4997
4998 onRemove: function () {
4999 this._map.off('zoomend', this._checkDisabledLayers, this);
5000
5001 for (var i = 0; i < this._layers.length; i++) {
5002 this._layers[i].layer.off('add remove', this._onLayerChange, this);
5003 }
5004 },
5005
5006 // @method addBaseLayer(layer: Layer, name: String): this
5007 // Adds a base layer (radio button entry) with the given name to the control.
5008 addBaseLayer: function (layer, name) {
5009 this._addLayer(layer, name);
5010 return (this._map) ? this._update() : this;
5011 },
5012
5013 // @method addOverlay(layer: Layer, name: String): this
5014 // Adds an overlay (checkbox entry) with the given name to the control.
5015 addOverlay: function (layer, name) {
5016 this._addLayer(layer, name, true);
5017 return (this._map) ? this._update() : this;
5018 },
5019
5020 // @method removeLayer(layer: Layer): this
5021 // Remove the given layer from the control.
5022 removeLayer: function (layer) {
5023 layer.off('add remove', this._onLayerChange, this);
5024
5025 var obj = this._getLayer(stamp(layer));
5026 if (obj) {
5027 this._layers.splice(this._layers.indexOf(obj), 1);
5028 }
5029 return (this._map) ? this._update() : this;
5030 },
5031
5032 // @method expand(): this
5033 // Expand the control container if collapsed.
5034 expand: function () {
5035 addClass(this._container, 'leaflet-control-layers-expanded');
5036 this._section.style.height = null;
5037 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5038 if (acceptableHeight < this._section.clientHeight) {
5039 addClass(this._section, 'leaflet-control-layers-scrollbar');
5040 this._section.style.height = acceptableHeight + 'px';
5041 } else {
5042 removeClass(this._section, 'leaflet-control-layers-scrollbar');
5043 }
5044 this._checkDisabledLayers();
5045 return this;
5046 },
5047
5048 // @method collapse(): this
5049 // Collapse the control container if expanded.
5050 collapse: function () {
5051 removeClass(this._container, 'leaflet-control-layers-expanded');
5052 return this;
5053 },
5054
5055 _initLayout: function () {
5056 var className = 'leaflet-control-layers',
5057 container = this._container = create$1('div', className),
5058 collapsed = this.options.collapsed;
5059
5060 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5061 container.setAttribute('aria-haspopup', true);
5062
5063 disableClickPropagation(container);
5064 disableScrollPropagation(container);
5065
5066 var section = this._section = create$1('section', className + '-list');
5067
5068 if (collapsed) {
5069 this._map.on('click', this.collapse, this);
5070
5071 on(container, {
5072 mouseenter: function () {
5073 on(section, 'click', preventDefault);
5074 this.expand();
5075 setTimeout(function () {
5076 off(section, 'click', preventDefault);
5077 });
5078 },
5079 mouseleave: this.collapse
5080 }, this);
5081 }
5082
5083 var link = this._layersLink = create$1('a', className + '-toggle', container);
5084 link.href = '#';
5085 link.title = 'Layers';
5086 link.setAttribute('role', 'button');
5087
5088 on(link, 'click', preventDefault); // prevent link function
5089 on(link, 'focus', this.expand, this);
5090
5091 if (!collapsed) {
5092 this.expand();
5093 }
5094
5095 this._baseLayersList = create$1('div', className + '-base', section);
5096 this._separator = create$1('div', className + '-separator', section);
5097 this._overlaysList = create$1('div', className + '-overlays', section);
5098
5099 container.appendChild(section);
5100 },
5101
5102 _getLayer: function (id) {
5103 for (var i = 0; i < this._layers.length; i++) {
5104
5105 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5106 return this._layers[i];
5107 }
5108 }
5109 },
5110
5111 _addLayer: function (layer, name, overlay) {
5112 if (this._map) {
5113 layer.on('add remove', this._onLayerChange, this);
5114 }
5115
5116 this._layers.push({
5117 layer: layer,
5118 name: name,
5119 overlay: overlay
5120 });
5121
5122 if (this.options.sortLayers) {
5123 this._layers.sort(bind(function (a, b) {
5124 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5125 }, this));
5126 }
5127
5128 if (this.options.autoZIndex && layer.setZIndex) {
5129 this._lastZIndex++;
5130 layer.setZIndex(this._lastZIndex);
5131 }
5132
5133 this._expandIfNotCollapsed();
5134 },
5135
5136 _update: function () {
5137 if (!this._container) { return this; }
5138
5139 empty(this._baseLayersList);
5140 empty(this._overlaysList);
5141
5142 this._layerControlInputs = [];
5143 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5144
5145 for (i = 0; i < this._layers.length; i++) {
5146 obj = this._layers[i];
5147 this._addItem(obj);
5148 overlaysPresent = overlaysPresent || obj.overlay;
5149 baseLayersPresent = baseLayersPresent || !obj.overlay;
5150 baseLayersCount += !obj.overlay ? 1 : 0;
5151 }
5152
5153 // Hide base layers section if there's only one layer.
5154 if (this.options.hideSingleBase) {
5155 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5156 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5157 }
5158
5159 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5160
5161 return this;
5162 },
5163
5164 _onLayerChange: function (e) {
5165 if (!this._handlingClick) {
5166 this._update();
5167 }
5168
5169 var obj = this._getLayer(stamp(e.target));
5170
5171 // @namespace Map
5172 // @section Layer events
5173 // @event baselayerchange: LayersControlEvent
5174 // Fired when the base layer is changed through the [layers control](#control-layers).
5175 // @event overlayadd: LayersControlEvent
5176 // Fired when an overlay is selected through the [layers control](#control-layers).
5177 // @event overlayremove: LayersControlEvent
5178 // Fired when an overlay is deselected through the [layers control](#control-layers).
5179 // @namespace Control.Layers
5180 var type = obj.overlay ?
5181 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5182 (e.type === 'add' ? 'baselayerchange' : null);
5183
5184 if (type) {
5185 this._map.fire(type, obj);
5186 }
5187 },
5188
5189 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)
5190 _createRadioElement: function (name, checked) {
5191
5192 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5193 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5194
5195 var radioFragment = document.createElement('div');
5196 radioFragment.innerHTML = radioHtml;
5197
5198 return radioFragment.firstChild;
5199 },
5200
5201 _addItem: function (obj) {
5202 var label = document.createElement('label'),
5203 checked = this._map.hasLayer(obj.layer),
5204 input;
5205
5206 if (obj.overlay) {
5207 input = document.createElement('input');
5208 input.type = 'checkbox';
5209 input.className = 'leaflet-control-layers-selector';
5210 input.defaultChecked = checked;
5211 } else {
5212 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5213 }
5214
5215 this._layerControlInputs.push(input);
5216 input.layerId = stamp(obj.layer);
5217
5218 on(input, 'click', this._onInputClick, this);
5219
5220 var name = document.createElement('span');
5221 name.innerHTML = ' ' + obj.name;
5222
5223 // Helps from preventing layer control flicker when checkboxes are disabled
5224 // https://github.com/Leaflet/Leaflet/issues/2771
5225 var holder = document.createElement('span');
5226
5227 label.appendChild(holder);
5228 holder.appendChild(input);
5229 holder.appendChild(name);
5230
5231 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5232 container.appendChild(label);
5233
5234 this._checkDisabledLayers();
5235 return label;
5236 },
5237
5238 _onInputClick: function () {
5239 var inputs = this._layerControlInputs,
5240 input, layer;
5241 var addedLayers = [],
5242 removedLayers = [];
5243
5244 this._handlingClick = true;
5245
5246 for (var i = inputs.length - 1; i >= 0; i--) {
5247 input = inputs[i];
5248 layer = this._getLayer(input.layerId).layer;
5249
5250 if (input.checked) {
5251 addedLayers.push(layer);
5252 } else if (!input.checked) {
5253 removedLayers.push(layer);
5254 }
5255 }
5256
5257 // Bugfix issue 2318: Should remove all old layers before readding new ones
5258 for (i = 0; i < removedLayers.length; i++) {
5259 if (this._map.hasLayer(removedLayers[i])) {
5260 this._map.removeLayer(removedLayers[i]);
5261 }
5262 }
5263 for (i = 0; i < addedLayers.length; i++) {
5264 if (!this._map.hasLayer(addedLayers[i])) {
5265 this._map.addLayer(addedLayers[i]);
5266 }
5267 }
5268
5269 this._handlingClick = false;
5270
5271 this._refocusOnMap();
5272 },
5273
5274 _checkDisabledLayers: function () {
5275 var inputs = this._layerControlInputs,
5276 input,
5277 layer,
5278 zoom = this._map.getZoom();
5279
5280 for (var i = inputs.length - 1; i >= 0; i--) {
5281 input = inputs[i];
5282 layer = this._getLayer(input.layerId).layer;
5283 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5284 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5285
5286 }
5287 },
5288
5289 _expandIfNotCollapsed: function () {
5290 if (this._map && !this.options.collapsed) {
5291 this.expand();
5292 }
5293 return this;
5294 }
5295
5296 });
5297
5298
5299 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5300 // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
5301 var layers = function (baseLayers, overlays, options) {
5302 return new Layers(baseLayers, overlays, options);
5303 };
5304
5305 /*
5306 * @class Control.Zoom
5307 * @aka L.Control.Zoom
5308 * @inherits Control
5309 *
5310 * 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`.
5311 */
5312
5313 var Zoom = Control.extend({
5314 // @section
5315 // @aka Control.Zoom options
5316 options: {
5317 position: 'topleft',
5318
5319 // @option zoomInText: String = '<span aria-hidden="true">+</span>'
5320 // The text set on the 'zoom in' button.
5321 zoomInText: '<span aria-hidden="true">+</span>',
5322
5323 // @option zoomInTitle: String = 'Zoom in'
5324 // The title set on the 'zoom in' button.
5325 zoomInTitle: 'Zoom in',
5326
5327 // @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'
5328 // The text set on the 'zoom out' button.
5329 zoomOutText: '<span aria-hidden="true">&#x2212;</span>',
5330
5331 // @option zoomOutTitle: String = 'Zoom out'
5332 // The title set on the 'zoom out' button.
5333 zoomOutTitle: 'Zoom out'
5334 },
5335
5336 onAdd: function (map) {
5337 var zoomName = 'leaflet-control-zoom',
5338 container = create$1('div', zoomName + ' leaflet-bar'),
5339 options = this.options;
5340
5341 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
5342 zoomName + '-in', container, this._zoomIn);
5343 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5344 zoomName + '-out', container, this._zoomOut);
5345
5346 this._updateDisabled();
5347 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5348
5349 return container;
5350 },
5351
5352 onRemove: function (map) {
5353 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5354 },
5355
5356 disable: function () {
5357 this._disabled = true;
5358 this._updateDisabled();
5359 return this;
5360 },
5361
5362 enable: function () {
5363 this._disabled = false;
5364 this._updateDisabled();
5365 return this;
5366 },
5367
5368 _zoomIn: function (e) {
5369 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5370 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5371 }
5372 },
5373
5374 _zoomOut: function (e) {
5375 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5376 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5377 }
5378 },
5379
5380 _createButton: function (html, title, className, container, fn) {
5381 var link = create$1('a', className, container);
5382 link.innerHTML = html;
5383 link.href = '#';
5384 link.title = title;
5385
5386 /*
5387 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5388 */
5389 link.setAttribute('role', 'button');
5390 link.setAttribute('aria-label', title);
5391
5392 disableClickPropagation(link);
5393 on(link, 'click', stop);
5394 on(link, 'click', fn, this);
5395 on(link, 'click', this._refocusOnMap, this);
5396
5397 return link;
5398 },
5399
5400 _updateDisabled: function () {
5401 var map = this._map,
5402 className = 'leaflet-disabled';
5403
5404 removeClass(this._zoomInButton, className);
5405 removeClass(this._zoomOutButton, className);
5406 this._zoomInButton.setAttribute('aria-disabled', 'false');
5407 this._zoomOutButton.setAttribute('aria-disabled', 'false');
5408
5409 if (this._disabled || map._zoom === map.getMinZoom()) {
5410 addClass(this._zoomOutButton, className);
5411 this._zoomOutButton.setAttribute('aria-disabled', 'true');
5412 }
5413 if (this._disabled || map._zoom === map.getMaxZoom()) {
5414 addClass(this._zoomInButton, className);
5415 this._zoomInButton.setAttribute('aria-disabled', 'true');
5416 }
5417 }
5418 });
5419
5420 // @namespace Map
5421 // @section Control options
5422 // @option zoomControl: Boolean = true
5423 // Whether a [zoom control](#control-zoom) is added to the map by default.
5424 Map.mergeOptions({
5425 zoomControl: true
5426 });
5427
5428 Map.addInitHook(function () {
5429 if (this.options.zoomControl) {
5430 // @section Controls
5431 // @property zoomControl: Control.Zoom
5432 // The default zoom control (only available if the
5433 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5434 this.zoomControl = new Zoom();
5435 this.addControl(this.zoomControl);
5436 }
5437 });
5438
5439 // @namespace Control.Zoom
5440 // @factory L.control.zoom(options: Control.Zoom options)
5441 // Creates a zoom control
5442 var zoom = function (options) {
5443 return new Zoom(options);
5444 };
5445
5446 /*
5447 * @class Control.Scale
5448 * @aka L.Control.Scale
5449 * @inherits Control
5450 *
5451 * 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`.
5452 *
5453 * @example
5454 *
5455 * ```js
5456 * L.control.scale().addTo(map);
5457 * ```
5458 */
5459
5460 var Scale = Control.extend({
5461 // @section
5462 // @aka Control.Scale options
5463 options: {
5464 position: 'bottomleft',
5465
5466 // @option maxWidth: Number = 100
5467 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5468 maxWidth: 100,
5469
5470 // @option metric: Boolean = True
5471 // Whether to show the metric scale line (m/km).
5472 metric: true,
5473
5474 // @option imperial: Boolean = True
5475 // Whether to show the imperial scale line (mi/ft).
5476 imperial: true
5477
5478 // @option updateWhenIdle: Boolean = false
5479 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5480 },
5481
5482 onAdd: function (map) {
5483 var className = 'leaflet-control-scale',
5484 container = create$1('div', className),
5485 options = this.options;
5486
5487 this._addScales(options, className + '-line', container);
5488
5489 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5490 map.whenReady(this._update, this);
5491
5492 return container;
5493 },
5494
5495 onRemove: function (map) {
5496 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5497 },
5498
5499 _addScales: function (options, className, container) {
5500 if (options.metric) {
5501 this._mScale = create$1('div', className, container);
5502 }
5503 if (options.imperial) {
5504 this._iScale = create$1('div', className, container);
5505 }
5506 },
5507
5508 _update: function () {
5509 var map = this._map,
5510 y = map.getSize().y / 2;
5511
5512 var maxMeters = map.distance(
5513 map.containerPointToLatLng([0, y]),
5514 map.containerPointToLatLng([this.options.maxWidth, y]));
5515
5516 this._updateScales(maxMeters);
5517 },
5518
5519 _updateScales: function (maxMeters) {
5520 if (this.options.metric && maxMeters) {
5521 this._updateMetric(maxMeters);
5522 }
5523 if (this.options.imperial && maxMeters) {
5524 this._updateImperial(maxMeters);
5525 }
5526 },
5527
5528 _updateMetric: function (maxMeters) {
5529 var meters = this._getRoundNum(maxMeters),
5530 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5531
5532 this._updateScale(this._mScale, label, meters / maxMeters);
5533 },
5534
5535 _updateImperial: function (maxMeters) {
5536 var maxFeet = maxMeters * 3.2808399,
5537 maxMiles, miles, feet;
5538
5539 if (maxFeet > 5280) {
5540 maxMiles = maxFeet / 5280;
5541 miles = this._getRoundNum(maxMiles);
5542 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5543
5544 } else {
5545 feet = this._getRoundNum(maxFeet);
5546 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5547 }
5548 },
5549
5550 _updateScale: function (scale, text, ratio) {
5551 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5552 scale.innerHTML = text;
5553 },
5554
5555 _getRoundNum: function (num) {
5556 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5557 d = num / pow10;
5558
5559 d = d >= 10 ? 10 :
5560 d >= 5 ? 5 :
5561 d >= 3 ? 3 :
5562 d >= 2 ? 2 : 1;
5563
5564 return pow10 * d;
5565 }
5566 });
5567
5568
5569 // @factory L.control.scale(options?: Control.Scale options)
5570 // Creates an scale control with the given options.
5571 var scale = function (options) {
5572 return new Scale(options);
5573 };
5574
5575 var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>';
5576
5577
5578 /*
5579 * @class Control.Attribution
5580 * @aka L.Control.Attribution
5581 * @inherits Control
5582 *
5583 * 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.
5584 */
5585
5586 var Attribution = Control.extend({
5587 // @section
5588 // @aka Control.Attribution options
5589 options: {
5590 position: 'bottomright',
5591
5592 // @option prefix: String|false = 'Leaflet'
5593 // The HTML text shown before the attributions. Pass `false` to disable.
5594 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
5595 },
5596
5597 initialize: function (options) {
5598 setOptions(this, options);
5599
5600 this._attributions = {};
5601 },
5602
5603 onAdd: function (map) {
5604 map.attributionControl = this;
5605 this._container = create$1('div', 'leaflet-control-attribution');
5606 disableClickPropagation(this._container);
5607
5608 // TODO ugly, refactor
5609 for (var i in map._layers) {
5610 if (map._layers[i].getAttribution) {
5611 this.addAttribution(map._layers[i].getAttribution());
5612 }
5613 }
5614
5615 this._update();
5616
5617 map.on('layeradd', this._addAttribution, this);
5618
5619 return this._container;
5620 },
5621
5622 onRemove: function (map) {
5623 map.off('layeradd', this._addAttribution, this);
5624 },
5625
5626 _addAttribution: function (ev) {
5627 if (ev.layer.getAttribution) {
5628 this.addAttribution(ev.layer.getAttribution());
5629 ev.layer.once('remove', function () {
5630 this.removeAttribution(ev.layer.getAttribution());
5631 }, this);
5632 }
5633 },
5634
5635 // @method setPrefix(prefix: String|false): this
5636 // The HTML text shown before the attributions. Pass `false` to disable.
5637 setPrefix: function (prefix) {
5638 this.options.prefix = prefix;
5639 this._update();
5640 return this;
5641 },
5642
5643 // @method addAttribution(text: String): this
5644 // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5645 addAttribution: function (text) {
5646 if (!text) { return this; }
5647
5648 if (!this._attributions[text]) {
5649 this._attributions[text] = 0;
5650 }
5651 this._attributions[text]++;
5652
5653 this._update();
5654
5655 return this;
5656 },
5657
5658 // @method removeAttribution(text: String): this
5659 // Removes an attribution text.
5660 removeAttribution: function (text) {
5661 if (!text) { return this; }
5662
5663 if (this._attributions[text]) {
5664 this._attributions[text]--;
5665 this._update();
5666 }
5667
5668 return this;
5669 },
5670
5671 _update: function () {
5672 if (!this._map) { return; }
5673
5674 var attribs = [];
5675
5676 for (var i in this._attributions) {
5677 if (this._attributions[i]) {
5678 attribs.push(i);
5679 }
5680 }
5681
5682 var prefixAndAttribs = [];
5683
5684 if (this.options.prefix) {
5685 prefixAndAttribs.push(this.options.prefix);
5686 }
5687 if (attribs.length) {
5688 prefixAndAttribs.push(attribs.join(', '));
5689 }
5690
5691 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
5692 }
5693 });
5694
5695 // @namespace Map
5696 // @section Control options
5697 // @option attributionControl: Boolean = true
5698 // Whether a [attribution control](#control-attribution) is added to the map by default.
5699 Map.mergeOptions({
5700 attributionControl: true
5701 });
5702
5703 Map.addInitHook(function () {
5704 if (this.options.attributionControl) {
5705 new Attribution().addTo(this);
5706 }
5707 });
5708
5709 // @namespace Control.Attribution
5710 // @factory L.control.attribution(options: Control.Attribution options)
5711 // Creates an attribution control.
5712 var attribution = function (options) {
5713 return new Attribution(options);
5714 };
5715
5716 Control.Layers = Layers;
5717 Control.Zoom = Zoom;
5718 Control.Scale = Scale;
5719 Control.Attribution = Attribution;
5720
5721 control.layers = layers;
5722 control.zoom = zoom;
5723 control.scale = scale;
5724 control.attribution = attribution;
5725
5726 /*
5727 L.Handler is a base class for handler classes that are used internally to inject
5728 interaction features like dragging to classes like Map and Marker.
5729 */
5730
5731 // @class Handler
5732 // @aka L.Handler
5733 // Abstract class for map interaction handlers
5734
5735 var Handler = Class.extend({
5736 initialize: function (map) {
5737 this._map = map;
5738 },
5739
5740 // @method enable(): this
5741 // Enables the handler
5742 enable: function () {
5743 if (this._enabled) { return this; }
5744
5745 this._enabled = true;
5746 this.addHooks();
5747 return this;
5748 },
5749
5750 // @method disable(): this
5751 // Disables the handler
5752 disable: function () {
5753 if (!this._enabled) { return this; }
5754
5755 this._enabled = false;
5756 this.removeHooks();
5757 return this;
5758 },
5759
5760 // @method enabled(): Boolean
5761 // Returns `true` if the handler is enabled
5762 enabled: function () {
5763 return !!this._enabled;
5764 }
5765
5766 // @section Extension methods
5767 // Classes inheriting from `Handler` must implement the two following methods:
5768 // @method addHooks()
5769 // Called when the handler is enabled, should add event hooks.
5770 // @method removeHooks()
5771 // Called when the handler is disabled, should remove the event hooks added previously.
5772 });
5773
5774 // @section There is static function which can be called without instantiating L.Handler:
5775 // @function addTo(map: Map, name: String): this
5776 // Adds a new Handler to the given map with the given name.
5777 Handler.addTo = function (map, name) {
5778 map.addHandler(name, this);
5779 return this;
5780 };
5781
5782 var Mixin = {Events: Events};
5783
5784 /*
5785 * @class Draggable
5786 * @aka L.Draggable
5787 * @inherits Evented
5788 *
5789 * A class for making DOM elements draggable (including touch support).
5790 * Used internally for map and marker dragging. Only works for elements
5791 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5792 *
5793 * @example
5794 * ```js
5795 * var draggable = new L.Draggable(elementToDrag);
5796 * draggable.enable();
5797 * ```
5798 */
5799
5800 var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';
5801
5802 var Draggable = Evented.extend({
5803
5804 options: {
5805 // @section
5806 // @aka Draggable options
5807 // @option clickTolerance: Number = 3
5808 // The max number of pixels a user can shift the mouse pointer during a click
5809 // for it to be considered a valid click (as opposed to a mouse drag).
5810 clickTolerance: 3
5811 },
5812
5813 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5814 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5815 initialize: function (element, dragStartTarget, preventOutline, options) {
5816 setOptions(this, options);
5817
5818 this._element = element;
5819 this._dragStartTarget = dragStartTarget || element;
5820 this._preventOutline = preventOutline;
5821 },
5822
5823 // @method enable()
5824 // Enables the dragging ability
5825 enable: function () {
5826 if (this._enabled) { return; }
5827
5828 on(this._dragStartTarget, START, this._onDown, this);
5829
5830 this._enabled = true;
5831 },
5832
5833 // @method disable()
5834 // Disables the dragging ability
5835 disable: function () {
5836 if (!this._enabled) { return; }
5837
5838 // If we're currently dragging this draggable,
5839 // disabling it counts as first ending the drag.
5840 if (Draggable._dragging === this) {
5841 this.finishDrag(true);
5842 }
5843
5844 off(this._dragStartTarget, START, this._onDown, this);
5845
5846 this._enabled = false;
5847 this._moved = false;
5848 },
5849
5850 _onDown: function (e) {
5851 // Ignore the event if disabled; this happens in IE11
5852 // under some circumstances, see #3666.
5853 if (!this._enabled) { return; }
5854
5855 this._moved = false;
5856
5857 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5858
5859 if (e.touches && e.touches.length !== 1) {
5860 // Finish dragging to avoid conflict with touchZoom
5861 if (Draggable._dragging === this) {
5862 this.finishDrag();
5863 }
5864 return;
5865 }
5866
5867 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5868 Draggable._dragging = this; // Prevent dragging multiple objects at once.
5869
5870 if (this._preventOutline) {
5871 preventOutline(this._element);
5872 }
5873
5874 disableImageDrag();
5875 disableTextSelection();
5876
5877 if (this._moving) { return; }
5878
5879 // @event down: Event
5880 // Fired when a drag is about to start.
5881 this.fire('down');
5882
5883 var first = e.touches ? e.touches[0] : e,
5884 sizedParent = getSizedParentNode(this._element);
5885
5886 this._startPoint = new Point(first.clientX, first.clientY);
5887 this._startPos = getPosition(this._element);
5888
5889 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5890 this._parentScale = getScale(sizedParent);
5891
5892 var mouseevent = e.type === 'mousedown';
5893 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
5894 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
5895 },
5896
5897 _onMove: function (e) {
5898 // Ignore the event if disabled; this happens in IE11
5899 // under some circumstances, see #3666.
5900 if (!this._enabled) { return; }
5901
5902 if (e.touches && e.touches.length > 1) {
5903 this._moved = true;
5904 return;
5905 }
5906
5907 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5908 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5909
5910 if (!offset.x && !offset.y) { return; }
5911 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5912
5913 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5914 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5915 // and we can use the cached value for the scale.
5916 offset.x /= this._parentScale.x;
5917 offset.y /= this._parentScale.y;
5918
5919 preventDefault(e);
5920
5921 if (!this._moved) {
5922 // @event dragstart: Event
5923 // Fired when a drag starts
5924 this.fire('dragstart');
5925
5926 this._moved = true;
5927
5928 addClass(document.body, 'leaflet-dragging');
5929
5930 this._lastTarget = e.target || e.srcElement;
5931 // IE and Edge do not give the <use> element, so fetch it
5932 // if necessary
5933 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
5934 this._lastTarget = this._lastTarget.correspondingUseElement;
5935 }
5936 addClass(this._lastTarget, 'leaflet-drag-target');
5937 }
5938
5939 this._newPos = this._startPos.add(offset);
5940 this._moving = true;
5941
5942 this._lastEvent = e;
5943 this._updatePosition();
5944 },
5945
5946 _updatePosition: function () {
5947 var e = {originalEvent: this._lastEvent};
5948
5949 // @event predrag: Event
5950 // Fired continuously during dragging *before* each corresponding
5951 // update of the element's position.
5952 this.fire('predrag', e);
5953 setPosition(this._element, this._newPos);
5954
5955 // @event drag: Event
5956 // Fired continuously during dragging.
5957 this.fire('drag', e);
5958 },
5959
5960 _onUp: function () {
5961 // Ignore the event if disabled; this happens in IE11
5962 // under some circumstances, see #3666.
5963 if (!this._enabled) { return; }
5964 this.finishDrag();
5965 },
5966
5967 finishDrag: function (noInertia) {
5968 removeClass(document.body, 'leaflet-dragging');
5969
5970 if (this._lastTarget) {
5971 removeClass(this._lastTarget, 'leaflet-drag-target');
5972 this._lastTarget = null;
5973 }
5974
5975 off(document, 'mousemove touchmove', this._onMove, this);
5976 off(document, 'mouseup touchend touchcancel', this._onUp, this);
5977
5978 enableImageDrag();
5979 enableTextSelection();
5980
5981 if (this._moved && this._moving) {
5982
5983 // @event dragend: DragEndEvent
5984 // Fired when the drag ends.
5985 this.fire('dragend', {
5986 noInertia: noInertia,
5987 distance: this._newPos.distanceTo(this._startPos)
5988 });
5989 }
5990
5991 this._moving = false;
5992 Draggable._dragging = false;
5993 }
5994
5995 });
5996
5997 /*
5998 * @namespace LineUtil
5999 *
6000 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6001 */
6002
6003 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6004 // Improves rendering performance dramatically by lessening the number of points to draw.
6005
6006 // @function simplify(points: Point[], tolerance: Number): Point[]
6007 // Dramatically reduces the number of points in a polyline while retaining
6008 // its shape and returns a new array of simplified points, using the
6009 // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
6010 // Used for a huge performance boost when processing/displaying Leaflet polylines for
6011 // each zoom level and also reducing visual noise. tolerance affects the amount of
6012 // simplification (lesser value means higher quality but slower and with more points).
6013 // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
6014 function simplify(points, tolerance) {
6015 if (!tolerance || !points.length) {
6016 return points.slice();
6017 }
6018
6019 var sqTolerance = tolerance * tolerance;
6020
6021 // stage 1: vertex reduction
6022 points = _reducePoints(points, sqTolerance);
6023
6024 // stage 2: Douglas-Peucker simplification
6025 points = _simplifyDP(points, sqTolerance);
6026
6027 return points;
6028 }
6029
6030 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6031 // Returns the distance between point `p` and segment `p1` to `p2`.
6032 function pointToSegmentDistance(p, p1, p2) {
6033 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6034 }
6035
6036 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6037 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6038 function closestPointOnSegment(p, p1, p2) {
6039 return _sqClosestPointOnSegment(p, p1, p2);
6040 }
6041
6042 // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
6043 function _simplifyDP(points, sqTolerance) {
6044
6045 var len = points.length,
6046 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6047 markers = new ArrayConstructor(len);
6048
6049 markers[0] = markers[len - 1] = 1;
6050
6051 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6052
6053 var i,
6054 newPoints = [];
6055
6056 for (i = 0; i < len; i++) {
6057 if (markers[i]) {
6058 newPoints.push(points[i]);
6059 }
6060 }
6061
6062 return newPoints;
6063 }
6064
6065 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6066
6067 var maxSqDist = 0,
6068 index, i, sqDist;
6069
6070 for (i = first + 1; i <= last - 1; i++) {
6071 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6072
6073 if (sqDist > maxSqDist) {
6074 index = i;
6075 maxSqDist = sqDist;
6076 }
6077 }
6078
6079 if (maxSqDist > sqTolerance) {
6080 markers[index] = 1;
6081
6082 _simplifyDPStep(points, markers, sqTolerance, first, index);
6083 _simplifyDPStep(points, markers, sqTolerance, index, last);
6084 }
6085 }
6086
6087 // reduce points that are too close to each other to a single point
6088 function _reducePoints(points, sqTolerance) {
6089 var reducedPoints = [points[0]];
6090
6091 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6092 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6093 reducedPoints.push(points[i]);
6094 prev = i;
6095 }
6096 }
6097 if (prev < len - 1) {
6098 reducedPoints.push(points[len - 1]);
6099 }
6100 return reducedPoints;
6101 }
6102
6103 var _lastCode;
6104
6105 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6106 // Clips the segment a to b by rectangular bounds with the
6107 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6108 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6109 // points that are on the screen or near, increasing performance.
6110 function clipSegment(a, b, bounds, useLastCode, round) {
6111 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6112 codeB = _getBitCode(b, bounds),
6113
6114 codeOut, p, newCode;
6115
6116 // save 2nd code to avoid calculating it on the next segment
6117 _lastCode = codeB;
6118
6119 while (true) {
6120 // if a,b is inside the clip window (trivial accept)
6121 if (!(codeA | codeB)) {
6122 return [a, b];
6123 }
6124
6125 // if a,b is outside the clip window (trivial reject)
6126 if (codeA & codeB) {
6127 return false;
6128 }
6129
6130 // other cases
6131 codeOut = codeA || codeB;
6132 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6133 newCode = _getBitCode(p, bounds);
6134
6135 if (codeOut === codeA) {
6136 a = p;
6137 codeA = newCode;
6138 } else {
6139 b = p;
6140 codeB = newCode;
6141 }
6142 }
6143 }
6144
6145 function _getEdgeIntersection(a, b, code, bounds, round) {
6146 var dx = b.x - a.x,
6147 dy = b.y - a.y,
6148 min = bounds.min,
6149 max = bounds.max,
6150 x, y;
6151
6152 if (code & 8) { // top
6153 x = a.x + dx * (max.y - a.y) / dy;
6154 y = max.y;
6155
6156 } else if (code & 4) { // bottom
6157 x = a.x + dx * (min.y - a.y) / dy;
6158 y = min.y;
6159
6160 } else if (code & 2) { // right
6161 x = max.x;
6162 y = a.y + dy * (max.x - a.x) / dx;
6163
6164 } else if (code & 1) { // left
6165 x = min.x;
6166 y = a.y + dy * (min.x - a.x) / dx;
6167 }
6168
6169 return new Point(x, y, round);
6170 }
6171
6172 function _getBitCode(p, bounds) {
6173 var code = 0;
6174
6175 if (p.x < bounds.min.x) { // left
6176 code |= 1;
6177 } else if (p.x > bounds.max.x) { // right
6178 code |= 2;
6179 }
6180
6181 if (p.y < bounds.min.y) { // bottom
6182 code |= 4;
6183 } else if (p.y > bounds.max.y) { // top
6184 code |= 8;
6185 }
6186
6187 return code;
6188 }
6189
6190 // square distance (to avoid unnecessary Math.sqrt calls)
6191 function _sqDist(p1, p2) {
6192 var dx = p2.x - p1.x,
6193 dy = p2.y - p1.y;
6194 return dx * dx + dy * dy;
6195 }
6196
6197 // return closest point on segment or distance to that point
6198 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6199 var x = p1.x,
6200 y = p1.y,
6201 dx = p2.x - x,
6202 dy = p2.y - y,
6203 dot = dx * dx + dy * dy,
6204 t;
6205
6206 if (dot > 0) {
6207 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6208
6209 if (t > 1) {
6210 x = p2.x;
6211 y = p2.y;
6212 } else if (t > 0) {
6213 x += dx * t;
6214 y += dy * t;
6215 }
6216 }
6217
6218 dx = p.x - x;
6219 dy = p.y - y;
6220
6221 return sqDist ? dx * dx + dy * dy : new Point(x, y);
6222 }
6223
6224
6225 // @function isFlat(latlngs: LatLng[]): Boolean
6226 // Returns true if `latlngs` is a flat array, false is nested.
6227 function isFlat(latlngs) {
6228 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6229 }
6230
6231 function _flat(latlngs) {
6232 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6233 return isFlat(latlngs);
6234 }
6235
6236 var LineUtil = {
6237 __proto__: null,
6238 simplify: simplify,
6239 pointToSegmentDistance: pointToSegmentDistance,
6240 closestPointOnSegment: closestPointOnSegment,
6241 clipSegment: clipSegment,
6242 _getEdgeIntersection: _getEdgeIntersection,
6243 _getBitCode: _getBitCode,
6244 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6245 isFlat: isFlat,
6246 _flat: _flat
6247 };
6248
6249 /*
6250 * @namespace PolyUtil
6251 * Various utility functions for polygon geometries.
6252 */
6253
6254 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6255 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
6256 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6257 * performance. Note that polygon points needs different algorithm for clipping
6258 * than polyline, so there's a separate method for it.
6259 */
6260 function clipPolygon(points, bounds, round) {
6261 var clippedPoints,
6262 edges = [1, 4, 2, 8],
6263 i, j, k,
6264 a, b,
6265 len, edge, p;
6266
6267 for (i = 0, len = points.length; i < len; i++) {
6268 points[i]._code = _getBitCode(points[i], bounds);
6269 }
6270
6271 // for each edge (left, bottom, right, top)
6272 for (k = 0; k < 4; k++) {
6273 edge = edges[k];
6274 clippedPoints = [];
6275
6276 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6277 a = points[i];
6278 b = points[j];
6279
6280 // if a is inside the clip window
6281 if (!(a._code & edge)) {
6282 // if b is outside the clip window (a->b goes out of screen)
6283 if (b._code & edge) {
6284 p = _getEdgeIntersection(b, a, edge, bounds, round);
6285 p._code = _getBitCode(p, bounds);
6286 clippedPoints.push(p);
6287 }
6288 clippedPoints.push(a);
6289
6290 // else if b is inside the clip window (a->b enters the screen)
6291 } else if (!(b._code & edge)) {
6292 p = _getEdgeIntersection(b, a, edge, bounds, round);
6293 p._code = _getBitCode(p, bounds);
6294 clippedPoints.push(p);
6295 }
6296 }
6297 points = clippedPoints;
6298 }
6299
6300 return points;
6301 }
6302
6303 var PolyUtil = {
6304 __proto__: null,
6305 clipPolygon: clipPolygon
6306 };
6307
6308 /*
6309 * @namespace Projection
6310 * @section
6311 * Leaflet comes with a set of already defined Projections out of the box:
6312 *
6313 * @projection L.Projection.LonLat
6314 *
6315 * Equirectangular, or Plate Carree projection — the most simple projection,
6316 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6317 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6318 * `EPSG:4326` and `Simple` CRS.
6319 */
6320
6321 var LonLat = {
6322 project: function (latlng) {
6323 return new Point(latlng.lng, latlng.lat);
6324 },
6325
6326 unproject: function (point) {
6327 return new LatLng(point.y, point.x);
6328 },
6329
6330 bounds: new Bounds([-180, -90], [180, 90])
6331 };
6332
6333 /*
6334 * @namespace Projection
6335 * @projection L.Projection.Mercator
6336 *
6337 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6338 */
6339
6340 var Mercator = {
6341 R: 6378137,
6342 R_MINOR: 6356752.314245179,
6343
6344 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6345
6346 project: function (latlng) {
6347 var d = Math.PI / 180,
6348 r = this.R,
6349 y = latlng.lat * d,
6350 tmp = this.R_MINOR / r,
6351 e = Math.sqrt(1 - tmp * tmp),
6352 con = e * Math.sin(y);
6353
6354 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6355 y = -r * Math.log(Math.max(ts, 1E-10));
6356
6357 return new Point(latlng.lng * d * r, y);
6358 },
6359
6360 unproject: function (point) {
6361 var d = 180 / Math.PI,
6362 r = this.R,
6363 tmp = this.R_MINOR / r,
6364 e = Math.sqrt(1 - tmp * tmp),
6365 ts = Math.exp(-point.y / r),
6366 phi = Math.PI / 2 - 2 * Math.atan(ts);
6367
6368 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6369 con = e * Math.sin(phi);
6370 con = Math.pow((1 - con) / (1 + con), e / 2);
6371 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6372 phi += dphi;
6373 }
6374
6375 return new LatLng(phi * d, point.x * d / r);
6376 }
6377 };
6378
6379 /*
6380 * @class Projection
6381
6382 * An object with methods for projecting geographical coordinates of the world onto
6383 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
6384
6385 * @property bounds: Bounds
6386 * The bounds (specified in CRS units) where the projection is valid
6387
6388 * @method project(latlng: LatLng): Point
6389 * Projects geographical coordinates into a 2D point.
6390 * Only accepts actual `L.LatLng` instances, not arrays.
6391
6392 * @method unproject(point: Point): LatLng
6393 * The inverse of `project`. Projects a 2D point into a geographical location.
6394 * Only accepts actual `L.Point` instances, not arrays.
6395
6396 * Note that the projection instances do not inherit from Leaflet's `Class` object,
6397 * and can't be instantiated. Also, new classes can't inherit from them,
6398 * and methods can't be added to them with the `include` function.
6399
6400 */
6401
6402 var index = {
6403 __proto__: null,
6404 LonLat: LonLat,
6405 Mercator: Mercator,
6406 SphericalMercator: SphericalMercator
6407 };
6408
6409 /*
6410 * @namespace CRS
6411 * @crs L.CRS.EPSG3395
6412 *
6413 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6414 */
6415 var EPSG3395 = extend({}, Earth, {
6416 code: 'EPSG:3395',
6417 projection: Mercator,
6418
6419 transformation: (function () {
6420 var scale = 0.5 / (Math.PI * Mercator.R);
6421 return toTransformation(scale, 0.5, -scale, 0.5);
6422 }())
6423 });
6424
6425 /*
6426 * @namespace CRS
6427 * @crs L.CRS.EPSG4326
6428 *
6429 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6430 *
6431 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6432 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
6433 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6434 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6435 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6436 */
6437
6438 var EPSG4326 = extend({}, Earth, {
6439 code: 'EPSG:4326',
6440 projection: LonLat,
6441 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6442 });
6443
6444 /*
6445 * @namespace CRS
6446 * @crs L.CRS.Simple
6447 *
6448 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6449 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6450 * axis should still be inverted (going from bottom to top). `distance()` returns
6451 * simple euclidean distance.
6452 */
6453
6454 var Simple = extend({}, CRS, {
6455 projection: LonLat,
6456 transformation: toTransformation(1, 0, -1, 0),
6457
6458 scale: function (zoom) {
6459 return Math.pow(2, zoom);
6460 },
6461
6462 zoom: function (scale) {
6463 return Math.log(scale) / Math.LN2;
6464 },
6465
6466 distance: function (latlng1, latlng2) {
6467 var dx = latlng2.lng - latlng1.lng,
6468 dy = latlng2.lat - latlng1.lat;
6469
6470 return Math.sqrt(dx * dx + dy * dy);
6471 },
6472
6473 infinite: true
6474 });
6475
6476 CRS.Earth = Earth;
6477 CRS.EPSG3395 = EPSG3395;
6478 CRS.EPSG3857 = EPSG3857;
6479 CRS.EPSG900913 = EPSG900913;
6480 CRS.EPSG4326 = EPSG4326;
6481 CRS.Simple = Simple;
6482
6483 /*
6484 * @class Layer
6485 * @inherits Evented
6486 * @aka L.Layer
6487 * @aka ILayer
6488 *
6489 * A set of methods from the Layer base class that all Leaflet layers use.
6490 * Inherits all methods, options and events from `L.Evented`.
6491 *
6492 * @example
6493 *
6494 * ```js
6495 * var layer = L.marker(latlng).addTo(map);
6496 * layer.addTo(map);
6497 * layer.remove();
6498 * ```
6499 *
6500 * @event add: Event
6501 * Fired after the layer is added to a map
6502 *
6503 * @event remove: Event
6504 * Fired after the layer is removed from a map
6505 */
6506
6507
6508 var Layer = Evented.extend({
6509
6510 // Classes extending `L.Layer` will inherit the following options:
6511 options: {
6512 // @option pane: String = 'overlayPane'
6513 // 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.
6514 pane: 'overlayPane',
6515
6516 // @option attribution: String = null
6517 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
6518 attribution: null,
6519
6520 bubblingMouseEvents: true
6521 },
6522
6523 /* @section
6524 * Classes extending `L.Layer` will inherit the following methods:
6525 *
6526 * @method addTo(map: Map|LayerGroup): this
6527 * Adds the layer to the given map or layer group.
6528 */
6529 addTo: function (map) {
6530 map.addLayer(this);
6531 return this;
6532 },
6533
6534 // @method remove: this
6535 // Removes the layer from the map it is currently active on.
6536 remove: function () {
6537 return this.removeFrom(this._map || this._mapToAdd);
6538 },
6539
6540 // @method removeFrom(map: Map): this
6541 // Removes the layer from the given map
6542 //
6543 // @alternative
6544 // @method removeFrom(group: LayerGroup): this
6545 // Removes the layer from the given `LayerGroup`
6546 removeFrom: function (obj) {
6547 if (obj) {
6548 obj.removeLayer(this);
6549 }
6550 return this;
6551 },
6552
6553 // @method getPane(name? : String): HTMLElement
6554 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6555 getPane: function (name) {
6556 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6557 },
6558
6559 addInteractiveTarget: function (targetEl) {
6560 this._map._targets[stamp(targetEl)] = this;
6561 return this;
6562 },
6563
6564 removeInteractiveTarget: function (targetEl) {
6565 delete this._map._targets[stamp(targetEl)];
6566 return this;
6567 },
6568
6569 // @method getAttribution: String
6570 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6571 getAttribution: function () {
6572 return this.options.attribution;
6573 },
6574
6575 _layerAdd: function (e) {
6576 var map = e.target;
6577
6578 // check in case layer gets added and then removed before the map is ready
6579 if (!map.hasLayer(this)) { return; }
6580
6581 this._map = map;
6582 this._zoomAnimated = map._zoomAnimated;
6583
6584 if (this.getEvents) {
6585 var events = this.getEvents();
6586 map.on(events, this);
6587 this.once('remove', function () {
6588 map.off(events, this);
6589 }, this);
6590 }
6591
6592 this.onAdd(map);
6593
6594 this.fire('add');
6595 map.fire('layeradd', {layer: this});
6596 }
6597 });
6598
6599 /* @section Extension methods
6600 * @uninheritable
6601 *
6602 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6603 *
6604 * @method onAdd(map: Map): this
6605 * 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).
6606 *
6607 * @method onRemove(map: Map): this
6608 * 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).
6609 *
6610 * @method getEvents(): Object
6611 * 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.
6612 *
6613 * @method getAttribution(): String
6614 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6615 *
6616 * @method beforeAdd(map: Map): this
6617 * 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.
6618 */
6619
6620
6621 /* @namespace Map
6622 * @section Layer events
6623 *
6624 * @event layeradd: LayerEvent
6625 * Fired when a new layer is added to the map.
6626 *
6627 * @event layerremove: LayerEvent
6628 * Fired when some layer is removed from the map
6629 *
6630 * @section Methods for Layers and Controls
6631 */
6632 Map.include({
6633 // @method addLayer(layer: Layer): this
6634 // Adds the given layer to the map
6635 addLayer: function (layer) {
6636 if (!layer._layerAdd) {
6637 throw new Error('The provided object is not a Layer.');
6638 }
6639
6640 var id = stamp(layer);
6641 if (this._layers[id]) { return this; }
6642 this._layers[id] = layer;
6643
6644 layer._mapToAdd = this;
6645
6646 if (layer.beforeAdd) {
6647 layer.beforeAdd(this);
6648 }
6649
6650 this.whenReady(layer._layerAdd, layer);
6651
6652 return this;
6653 },
6654
6655 // @method removeLayer(layer: Layer): this
6656 // Removes the given layer from the map.
6657 removeLayer: function (layer) {
6658 var id = stamp(layer);
6659
6660 if (!this._layers[id]) { return this; }
6661
6662 if (this._loaded) {
6663 layer.onRemove(this);
6664 }
6665
6666 delete this._layers[id];
6667
6668 if (this._loaded) {
6669 this.fire('layerremove', {layer: layer});
6670 layer.fire('remove');
6671 }
6672
6673 layer._map = layer._mapToAdd = null;
6674
6675 return this;
6676 },
6677
6678 // @method hasLayer(layer: Layer): Boolean
6679 // Returns `true` if the given layer is currently added to the map
6680 hasLayer: function (layer) {
6681 return stamp(layer) in this._layers;
6682 },
6683
6684 /* @method eachLayer(fn: Function, context?: Object): this
6685 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6686 * ```
6687 * map.eachLayer(function(layer){
6688 * layer.bindPopup('Hello');
6689 * });
6690 * ```
6691 */
6692 eachLayer: function (method, context) {
6693 for (var i in this._layers) {
6694 method.call(context, this._layers[i]);
6695 }
6696 return this;
6697 },
6698
6699 _addLayers: function (layers) {
6700 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6701
6702 for (var i = 0, len = layers.length; i < len; i++) {
6703 this.addLayer(layers[i]);
6704 }
6705 },
6706
6707 _addZoomLimit: function (layer) {
6708 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6709 this._zoomBoundLayers[stamp(layer)] = layer;
6710 this._updateZoomLevels();
6711 }
6712 },
6713
6714 _removeZoomLimit: function (layer) {
6715 var id = stamp(layer);
6716
6717 if (this._zoomBoundLayers[id]) {
6718 delete this._zoomBoundLayers[id];
6719 this._updateZoomLevels();
6720 }
6721 },
6722
6723 _updateZoomLevels: function () {
6724 var minZoom = Infinity,
6725 maxZoom = -Infinity,
6726 oldZoomSpan = this._getZoomSpan();
6727
6728 for (var i in this._zoomBoundLayers) {
6729 var options = this._zoomBoundLayers[i].options;
6730
6731 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6732 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6733 }
6734
6735 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6736 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6737
6738 // @section Map state change events
6739 // @event zoomlevelschange: Event
6740 // Fired when the number of zoomlevels on the map is changed due
6741 // to adding or removing a layer.
6742 if (oldZoomSpan !== this._getZoomSpan()) {
6743 this.fire('zoomlevelschange');
6744 }
6745
6746 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6747 this.setZoom(this._layersMaxZoom);
6748 }
6749 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6750 this.setZoom(this._layersMinZoom);
6751 }
6752 }
6753 });
6754
6755 /*
6756 * @class LayerGroup
6757 * @aka L.LayerGroup
6758 * @inherits Interactive layer
6759 *
6760 * Used to group several layers and handle them as one. If you add it to the map,
6761 * any layers added or removed from the group will be added/removed on the map as
6762 * well. Extends `Layer`.
6763 *
6764 * @example
6765 *
6766 * ```js
6767 * L.layerGroup([marker1, marker2])
6768 * .addLayer(polyline)
6769 * .addTo(map);
6770 * ```
6771 */
6772
6773 var LayerGroup = Layer.extend({
6774
6775 initialize: function (layers, options) {
6776 setOptions(this, options);
6777
6778 this._layers = {};
6779
6780 var i, len;
6781
6782 if (layers) {
6783 for (i = 0, len = layers.length; i < len; i++) {
6784 this.addLayer(layers[i]);
6785 }
6786 }
6787 },
6788
6789 // @method addLayer(layer: Layer): this
6790 // Adds the given layer to the group.
6791 addLayer: function (layer) {
6792 var id = this.getLayerId(layer);
6793
6794 this._layers[id] = layer;
6795
6796 if (this._map) {
6797 this._map.addLayer(layer);
6798 }
6799
6800 return this;
6801 },
6802
6803 // @method removeLayer(layer: Layer): this
6804 // Removes the given layer from the group.
6805 // @alternative
6806 // @method removeLayer(id: Number): this
6807 // Removes the layer with the given internal ID from the group.
6808 removeLayer: function (layer) {
6809 var id = layer in this._layers ? layer : this.getLayerId(layer);
6810
6811 if (this._map && this._layers[id]) {
6812 this._map.removeLayer(this._layers[id]);
6813 }
6814
6815 delete this._layers[id];
6816
6817 return this;
6818 },
6819
6820 // @method hasLayer(layer: Layer): Boolean
6821 // Returns `true` if the given layer is currently added to the group.
6822 // @alternative
6823 // @method hasLayer(id: Number): Boolean
6824 // Returns `true` if the given internal ID is currently added to the group.
6825 hasLayer: function (layer) {
6826 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
6827 return layerId in this._layers;
6828 },
6829
6830 // @method clearLayers(): this
6831 // Removes all the layers from the group.
6832 clearLayers: function () {
6833 return this.eachLayer(this.removeLayer, this);
6834 },
6835
6836 // @method invoke(methodName: String, …): this
6837 // Calls `methodName` on every layer contained in this group, passing any
6838 // additional parameters. Has no effect if the layers contained do not
6839 // implement `methodName`.
6840 invoke: function (methodName) {
6841 var args = Array.prototype.slice.call(arguments, 1),
6842 i, layer;
6843
6844 for (i in this._layers) {
6845 layer = this._layers[i];
6846
6847 if (layer[methodName]) {
6848 layer[methodName].apply(layer, args);
6849 }
6850 }
6851
6852 return this;
6853 },
6854
6855 onAdd: function (map) {
6856 this.eachLayer(map.addLayer, map);
6857 },
6858
6859 onRemove: function (map) {
6860 this.eachLayer(map.removeLayer, map);
6861 },
6862
6863 // @method eachLayer(fn: Function, context?: Object): this
6864 // Iterates over the layers of the group, optionally specifying context of the iterator function.
6865 // ```js
6866 // group.eachLayer(function (layer) {
6867 // layer.bindPopup('Hello');
6868 // });
6869 // ```
6870 eachLayer: function (method, context) {
6871 for (var i in this._layers) {
6872 method.call(context, this._layers[i]);
6873 }
6874 return this;
6875 },
6876
6877 // @method getLayer(id: Number): Layer
6878 // Returns the layer with the given internal ID.
6879 getLayer: function (id) {
6880 return this._layers[id];
6881 },
6882
6883 // @method getLayers(): Layer[]
6884 // Returns an array of all the layers added to the group.
6885 getLayers: function () {
6886 var layers = [];
6887 this.eachLayer(layers.push, layers);
6888 return layers;
6889 },
6890
6891 // @method setZIndex(zIndex: Number): this
6892 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6893 setZIndex: function (zIndex) {
6894 return this.invoke('setZIndex', zIndex);
6895 },
6896
6897 // @method getLayerId(layer: Layer): Number
6898 // Returns the internal ID for a layer
6899 getLayerId: function (layer) {
6900 return stamp(layer);
6901 }
6902 });
6903
6904
6905 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6906 // Create a layer group, optionally given an initial set of layers and an `options` object.
6907 var layerGroup = function (layers, options) {
6908 return new LayerGroup(layers, options);
6909 };
6910
6911 /*
6912 * @class FeatureGroup
6913 * @aka L.FeatureGroup
6914 * @inherits LayerGroup
6915 *
6916 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6917 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6918 * * Events are propagated to the `FeatureGroup`, so if the group has an event
6919 * handler, it will handle events from any of the layers. This includes mouse events
6920 * and custom events.
6921 * * Has `layeradd` and `layerremove` events
6922 *
6923 * @example
6924 *
6925 * ```js
6926 * L.featureGroup([marker1, marker2, polyline])
6927 * .bindPopup('Hello world!')
6928 * .on('click', function() { alert('Clicked on a member of the group!'); })
6929 * .addTo(map);
6930 * ```
6931 */
6932
6933 var FeatureGroup = LayerGroup.extend({
6934
6935 addLayer: function (layer) {
6936 if (this.hasLayer(layer)) {
6937 return this;
6938 }
6939
6940 layer.addEventParent(this);
6941
6942 LayerGroup.prototype.addLayer.call(this, layer);
6943
6944 // @event layeradd: LayerEvent
6945 // Fired when a layer is added to this `FeatureGroup`
6946 return this.fire('layeradd', {layer: layer});
6947 },
6948
6949 removeLayer: function (layer) {
6950 if (!this.hasLayer(layer)) {
6951 return this;
6952 }
6953 if (layer in this._layers) {
6954 layer = this._layers[layer];
6955 }
6956
6957 layer.removeEventParent(this);
6958
6959 LayerGroup.prototype.removeLayer.call(this, layer);
6960
6961 // @event layerremove: LayerEvent
6962 // Fired when a layer is removed from this `FeatureGroup`
6963 return this.fire('layerremove', {layer: layer});
6964 },
6965
6966 // @method setStyle(style: Path options): this
6967 // Sets the given path options to each layer of the group that has a `setStyle` method.
6968 setStyle: function (style) {
6969 return this.invoke('setStyle', style);
6970 },
6971
6972 // @method bringToFront(): this
6973 // Brings the layer group to the top of all other layers
6974 bringToFront: function () {
6975 return this.invoke('bringToFront');
6976 },
6977
6978 // @method bringToBack(): this
6979 // Brings the layer group to the back of all other layers
6980 bringToBack: function () {
6981 return this.invoke('bringToBack');
6982 },
6983
6984 // @method getBounds(): LatLngBounds
6985 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6986 getBounds: function () {
6987 var bounds = new LatLngBounds();
6988
6989 for (var id in this._layers) {
6990 var layer = this._layers[id];
6991 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6992 }
6993 return bounds;
6994 }
6995 });
6996
6997 // @factory L.featureGroup(layers?: Layer[], options?: Object)
6998 // Create a feature group, optionally given an initial set of layers and an `options` object.
6999 var featureGroup = function (layers, options) {
7000 return new FeatureGroup(layers, options);
7001 };
7002
7003 /*
7004 * @class Icon
7005 * @aka L.Icon
7006 *
7007 * Represents an icon to provide when creating a marker.
7008 *
7009 * @example
7010 *
7011 * ```js
7012 * var myIcon = L.icon({
7013 * iconUrl: 'my-icon.png',
7014 * iconRetinaUrl: 'my-icon@2x.png',
7015 * iconSize: [38, 95],
7016 * iconAnchor: [22, 94],
7017 * popupAnchor: [-3, -76],
7018 * shadowUrl: 'my-icon-shadow.png',
7019 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
7020 * shadowSize: [68, 95],
7021 * shadowAnchor: [22, 94]
7022 * });
7023 *
7024 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7025 * ```
7026 *
7027 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7028 *
7029 */
7030
7031 var Icon = Class.extend({
7032
7033 /* @section
7034 * @aka Icon options
7035 *
7036 * @option iconUrl: String = null
7037 * **(required)** The URL to the icon image (absolute or relative to your script path).
7038 *
7039 * @option iconRetinaUrl: String = null
7040 * The URL to a retina sized version of the icon image (absolute or relative to your
7041 * script path). Used for Retina screen devices.
7042 *
7043 * @option iconSize: Point = null
7044 * Size of the icon image in pixels.
7045 *
7046 * @option iconAnchor: Point = null
7047 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7048 * will be aligned so that this point is at the marker's geographical location. Centered
7049 * by default if size is specified, also can be set in CSS with negative margins.
7050 *
7051 * @option popupAnchor: Point = [0, 0]
7052 * The coordinates of the point from which popups will "open", relative to the icon anchor.
7053 *
7054 * @option tooltipAnchor: Point = [0, 0]
7055 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7056 *
7057 * @option shadowUrl: String = null
7058 * The URL to the icon shadow image. If not specified, no shadow image will be created.
7059 *
7060 * @option shadowRetinaUrl: String = null
7061 *
7062 * @option shadowSize: Point = null
7063 * Size of the shadow image in pixels.
7064 *
7065 * @option shadowAnchor: Point = null
7066 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7067 * as iconAnchor if not specified).
7068 *
7069 * @option className: String = ''
7070 * A custom class name to assign to both icon and shadow images. Empty by default.
7071 */
7072
7073 options: {
7074 popupAnchor: [0, 0],
7075 tooltipAnchor: [0, 0],
7076
7077 // @option crossOrigin: Boolean|String = false
7078 // Whether the crossOrigin attribute will be added to the tiles.
7079 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
7080 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
7081 crossOrigin: false
7082 },
7083
7084 initialize: function (options) {
7085 setOptions(this, options);
7086 },
7087
7088 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7089 // Called internally when the icon has to be shown, returns a `<img>` HTML element
7090 // styled according to the options.
7091 createIcon: function (oldIcon) {
7092 return this._createIcon('icon', oldIcon);
7093 },
7094
7095 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7096 // As `createIcon`, but for the shadow beneath it.
7097 createShadow: function (oldIcon) {
7098 return this._createIcon('shadow', oldIcon);
7099 },
7100
7101 _createIcon: function (name, oldIcon) {
7102 var src = this._getIconUrl(name);
7103
7104 if (!src) {
7105 if (name === 'icon') {
7106 throw new Error('iconUrl not set in Icon options (see the docs).');
7107 }
7108 return null;
7109 }
7110
7111 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7112 this._setIconStyles(img, name);
7113
7114 if (this.options.crossOrigin || this.options.crossOrigin === '') {
7115 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
7116 }
7117
7118 return img;
7119 },
7120
7121 _setIconStyles: function (img, name) {
7122 var options = this.options;
7123 var sizeOption = options[name + 'Size'];
7124
7125 if (typeof sizeOption === 'number') {
7126 sizeOption = [sizeOption, sizeOption];
7127 }
7128
7129 var size = toPoint(sizeOption),
7130 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7131 size && size.divideBy(2, true));
7132
7133 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7134
7135 if (anchor) {
7136 img.style.marginLeft = (-anchor.x) + 'px';
7137 img.style.marginTop = (-anchor.y) + 'px';
7138 }
7139
7140 if (size) {
7141 img.style.width = size.x + 'px';
7142 img.style.height = size.y + 'px';
7143 }
7144 },
7145
7146 _createImg: function (src, el) {
7147 el = el || document.createElement('img');
7148 el.src = src;
7149 return el;
7150 },
7151
7152 _getIconUrl: function (name) {
7153 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7154 }
7155 });
7156
7157
7158 // @factory L.icon(options: Icon options)
7159 // Creates an icon instance with the given options.
7160 function icon(options) {
7161 return new Icon(options);
7162 }
7163
7164 /*
7165 * @miniclass Icon.Default (Icon)
7166 * @aka L.Icon.Default
7167 * @section
7168 *
7169 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7170 * no icon is specified. Points to the blue marker image distributed with Leaflet
7171 * releases.
7172 *
7173 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7174 * (which is a set of `Icon options`).
7175 *
7176 * If you want to _completely_ replace the default icon, override the
7177 * `L.Marker.prototype.options.icon` with your own icon instead.
7178 */
7179
7180 var IconDefault = Icon.extend({
7181
7182 options: {
7183 iconUrl: 'marker-icon.png',
7184 iconRetinaUrl: 'marker-icon-2x.png',
7185 shadowUrl: 'marker-shadow.png',
7186 iconSize: [25, 41],
7187 iconAnchor: [12, 41],
7188 popupAnchor: [1, -34],
7189 tooltipAnchor: [16, -28],
7190 shadowSize: [41, 41]
7191 },
7192
7193 _getIconUrl: function (name) {
7194 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only
7195 IconDefault.imagePath = this._detectIconPath();
7196 }
7197
7198 // @option imagePath: String
7199 // `Icon.Default` will try to auto-detect the location of the
7200 // blue icon images. If you are placing these images in a non-standard
7201 // way, set this option to point to the right path.
7202 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7203 },
7204
7205 _stripUrl: function (path) { // separate function to use in tests
7206 var strip = function (str, re, idx) {
7207 var match = re.exec(str);
7208 return match && match[idx];
7209 };
7210 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
7211 return path && strip(path, /^(.*)marker-icon\.png$/, 1);
7212 },
7213
7214 _detectIconPath: function () {
7215 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7216 var path = getStyle(el, 'background-image') ||
7217 getStyle(el, 'backgroundImage'); // IE8
7218
7219 document.body.removeChild(el);
7220 path = this._stripUrl(path);
7221 if (path) { return path; }
7222 var link = document.querySelector('link[href$="leaflet.css"]');
7223 if (!link) { return ''; }
7224 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
7225 }
7226 });
7227
7228 /*
7229 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7230 */
7231
7232
7233 /* @namespace Marker
7234 * @section Interaction handlers
7235 *
7236 * 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:
7237 *
7238 * ```js
7239 * marker.dragging.disable();
7240 * ```
7241 *
7242 * @property dragging: Handler
7243 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7244 */
7245
7246 var MarkerDrag = Handler.extend({
7247 initialize: function (marker) {
7248 this._marker = marker;
7249 },
7250
7251 addHooks: function () {
7252 var icon = this._marker._icon;
7253
7254 if (!this._draggable) {
7255 this._draggable = new Draggable(icon, icon, true);
7256 }
7257
7258 this._draggable.on({
7259 dragstart: this._onDragStart,
7260 predrag: this._onPreDrag,
7261 drag: this._onDrag,
7262 dragend: this._onDragEnd
7263 }, this).enable();
7264
7265 addClass(icon, 'leaflet-marker-draggable');
7266 },
7267
7268 removeHooks: function () {
7269 this._draggable.off({
7270 dragstart: this._onDragStart,
7271 predrag: this._onPreDrag,
7272 drag: this._onDrag,
7273 dragend: this._onDragEnd
7274 }, this).disable();
7275
7276 if (this._marker._icon) {
7277 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7278 }
7279 },
7280
7281 moved: function () {
7282 return this._draggable && this._draggable._moved;
7283 },
7284
7285 _adjustPan: function (e) {
7286 var marker = this._marker,
7287 map = marker._map,
7288 speed = this._marker.options.autoPanSpeed,
7289 padding = this._marker.options.autoPanPadding,
7290 iconPos = getPosition(marker._icon),
7291 bounds = map.getPixelBounds(),
7292 origin = map.getPixelOrigin();
7293
7294 var panBounds = toBounds(
7295 bounds.min._subtract(origin).add(padding),
7296 bounds.max._subtract(origin).subtract(padding)
7297 );
7298
7299 if (!panBounds.contains(iconPos)) {
7300 // Compute incremental movement
7301 var movement = toPoint(
7302 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7303 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7304
7305 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7306 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7307 ).multiplyBy(speed);
7308
7309 map.panBy(movement, {animate: false});
7310
7311 this._draggable._newPos._add(movement);
7312 this._draggable._startPos._add(movement);
7313
7314 setPosition(marker._icon, this._draggable._newPos);
7315 this._onDrag(e);
7316
7317 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7318 }
7319 },
7320
7321 _onDragStart: function () {
7322 // @section Dragging events
7323 // @event dragstart: Event
7324 // Fired when the user starts dragging the marker.
7325
7326 // @event movestart: Event
7327 // Fired when the marker starts moving (because of dragging).
7328
7329 this._oldLatLng = this._marker.getLatLng();
7330
7331 // When using ES6 imports it could not be set when `Popup` was not imported as well
7332 this._marker.closePopup && this._marker.closePopup();
7333
7334 this._marker
7335 .fire('movestart')
7336 .fire('dragstart');
7337 },
7338
7339 _onPreDrag: function (e) {
7340 if (this._marker.options.autoPan) {
7341 cancelAnimFrame(this._panRequest);
7342 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7343 }
7344 },
7345
7346 _onDrag: function (e) {
7347 var marker = this._marker,
7348 shadow = marker._shadow,
7349 iconPos = getPosition(marker._icon),
7350 latlng = marker._map.layerPointToLatLng(iconPos);
7351
7352 // update shadow position
7353 if (shadow) {
7354 setPosition(shadow, iconPos);
7355 }
7356
7357 marker._latlng = latlng;
7358 e.latlng = latlng;
7359 e.oldLatLng = this._oldLatLng;
7360
7361 // @event drag: Event
7362 // Fired repeatedly while the user drags the marker.
7363 marker
7364 .fire('move', e)
7365 .fire('drag', e);
7366 },
7367
7368 _onDragEnd: function (e) {
7369 // @event dragend: DragEndEvent
7370 // Fired when the user stops dragging the marker.
7371
7372 cancelAnimFrame(this._panRequest);
7373
7374 // @event moveend: Event
7375 // Fired when the marker stops moving (because of dragging).
7376 delete this._oldLatLng;
7377 this._marker
7378 .fire('moveend')
7379 .fire('dragend', e);
7380 }
7381 });
7382
7383 /*
7384 * @class Marker
7385 * @inherits Interactive layer
7386 * @aka L.Marker
7387 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7388 *
7389 * @example
7390 *
7391 * ```js
7392 * L.marker([50.5, 30.5]).addTo(map);
7393 * ```
7394 */
7395
7396 var Marker = Layer.extend({
7397
7398 // @section
7399 // @aka Marker options
7400 options: {
7401 // @option icon: Icon = *
7402 // Icon instance to use for rendering the marker.
7403 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7404 // If not specified, a common instance of `L.Icon.Default` is used.
7405 icon: new IconDefault(),
7406
7407 // Option inherited from "Interactive layer" abstract class
7408 interactive: true,
7409
7410 // @option keyboard: Boolean = true
7411 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7412 keyboard: true,
7413
7414 // @option title: String = ''
7415 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7416 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
7417 title: '',
7418
7419 // @option alt: String = 'Marker'
7420 // Text for the `alt` attribute of the icon image.
7421 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
7422 alt: 'Marker',
7423
7424 // @option zIndexOffset: Number = 0
7425 // 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).
7426 zIndexOffset: 0,
7427
7428 // @option opacity: Number = 1.0
7429 // The opacity of the marker.
7430 opacity: 1,
7431
7432 // @option riseOnHover: Boolean = false
7433 // If `true`, the marker will get on top of others when you hover the mouse over it.
7434 riseOnHover: false,
7435
7436 // @option riseOffset: Number = 250
7437 // The z-index offset used for the `riseOnHover` feature.
7438 riseOffset: 250,
7439
7440 // @option pane: String = 'markerPane'
7441 // `Map pane` where the markers icon will be added.
7442 pane: 'markerPane',
7443
7444 // @option shadowPane: String = 'shadowPane'
7445 // `Map pane` where the markers shadow will be added.
7446 shadowPane: 'shadowPane',
7447
7448 // @option bubblingMouseEvents: Boolean = false
7449 // When `true`, a mouse event on this marker will trigger the same event on the map
7450 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7451 bubblingMouseEvents: false,
7452
7453 // @option autoPanOnFocus: Boolean = true
7454 // When `true`, the map will pan whenever the marker is focused (via
7455 // e.g. pressing `tab` on the keyboard) to ensure the marker is
7456 // visible within the map's bounds
7457 autoPanOnFocus: true,
7458
7459 // @section Draggable marker options
7460 // @option draggable: Boolean = false
7461 // Whether the marker is draggable with mouse/touch or not.
7462 draggable: false,
7463
7464 // @option autoPan: Boolean = false
7465 // Whether to pan the map when dragging this marker near its edge or not.
7466 autoPan: false,
7467
7468 // @option autoPanPadding: Point = Point(50, 50)
7469 // Distance (in pixels to the left/right and to the top/bottom) of the
7470 // map edge to start panning the map.
7471 autoPanPadding: [50, 50],
7472
7473 // @option autoPanSpeed: Number = 10
7474 // Number of pixels the map should pan by.
7475 autoPanSpeed: 10
7476 },
7477
7478 /* @section
7479 *
7480 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7481 */
7482
7483 initialize: function (latlng, options) {
7484 setOptions(this, options);
7485 this._latlng = toLatLng(latlng);
7486 },
7487
7488 onAdd: function (map) {
7489 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7490
7491 if (this._zoomAnimated) {
7492 map.on('zoomanim', this._animateZoom, this);
7493 }
7494
7495 this._initIcon();
7496 this.update();
7497 },
7498
7499 onRemove: function (map) {
7500 if (this.dragging && this.dragging.enabled()) {
7501 this.options.draggable = true;
7502 this.dragging.removeHooks();
7503 }
7504 delete this.dragging;
7505
7506 if (this._zoomAnimated) {
7507 map.off('zoomanim', this._animateZoom, this);
7508 }
7509
7510 this._removeIcon();
7511 this._removeShadow();
7512 },
7513
7514 getEvents: function () {
7515 return {
7516 zoom: this.update,
7517 viewreset: this.update
7518 };
7519 },
7520
7521 // @method getLatLng: LatLng
7522 // Returns the current geographical position of the marker.
7523 getLatLng: function () {
7524 return this._latlng;
7525 },
7526
7527 // @method setLatLng(latlng: LatLng): this
7528 // Changes the marker position to the given point.
7529 setLatLng: function (latlng) {
7530 var oldLatLng = this._latlng;
7531 this._latlng = toLatLng(latlng);
7532 this.update();
7533
7534 // @event move: Event
7535 // 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`.
7536 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7537 },
7538
7539 // @method setZIndexOffset(offset: Number): this
7540 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7541 setZIndexOffset: function (offset) {
7542 this.options.zIndexOffset = offset;
7543 return this.update();
7544 },
7545
7546 // @method getIcon: Icon
7547 // Returns the current icon used by the marker
7548 getIcon: function () {
7549 return this.options.icon;
7550 },
7551
7552 // @method setIcon(icon: Icon): this
7553 // Changes the marker icon.
7554 setIcon: function (icon) {
7555
7556 this.options.icon = icon;
7557
7558 if (this._map) {
7559 this._initIcon();
7560 this.update();
7561 }
7562
7563 if (this._popup) {
7564 this.bindPopup(this._popup, this._popup.options);
7565 }
7566
7567 return this;
7568 },
7569
7570 getElement: function () {
7571 return this._icon;
7572 },
7573
7574 update: function () {
7575
7576 if (this._icon && this._map) {
7577 var pos = this._map.latLngToLayerPoint(this._latlng).round();
7578 this._setPos(pos);
7579 }
7580
7581 return this;
7582 },
7583
7584 _initIcon: function () {
7585 var options = this.options,
7586 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7587
7588 var icon = options.icon.createIcon(this._icon),
7589 addIcon = false;
7590
7591 // if we're not reusing the icon, remove the old one and init new one
7592 if (icon !== this._icon) {
7593 if (this._icon) {
7594 this._removeIcon();
7595 }
7596 addIcon = true;
7597
7598 if (options.title) {
7599 icon.title = options.title;
7600 }
7601
7602 if (icon.tagName === 'IMG') {
7603 icon.alt = options.alt || '';
7604 }
7605 }
7606
7607 addClass(icon, classToAdd);
7608
7609 if (options.keyboard) {
7610 icon.tabIndex = '0';
7611 icon.setAttribute('role', 'button');
7612 }
7613
7614 this._icon = icon;
7615
7616 if (options.riseOnHover) {
7617 this.on({
7618 mouseover: this._bringToFront,
7619 mouseout: this._resetZIndex
7620 });
7621 }
7622
7623 if (this.options.autoPanOnFocus) {
7624 on(icon, 'focus', this._panOnFocus, this);
7625 }
7626
7627 var newShadow = options.icon.createShadow(this._shadow),
7628 addShadow = false;
7629
7630 if (newShadow !== this._shadow) {
7631 this._removeShadow();
7632 addShadow = true;
7633 }
7634
7635 if (newShadow) {
7636 addClass(newShadow, classToAdd);
7637 newShadow.alt = '';
7638 }
7639 this._shadow = newShadow;
7640
7641
7642 if (options.opacity < 1) {
7643 this._updateOpacity();
7644 }
7645
7646
7647 if (addIcon) {
7648 this.getPane().appendChild(this._icon);
7649 }
7650 this._initInteraction();
7651 if (newShadow && addShadow) {
7652 this.getPane(options.shadowPane).appendChild(this._shadow);
7653 }
7654 },
7655
7656 _removeIcon: function () {
7657 if (this.options.riseOnHover) {
7658 this.off({
7659 mouseover: this._bringToFront,
7660 mouseout: this._resetZIndex
7661 });
7662 }
7663
7664 if (this.options.autoPanOnFocus) {
7665 off(this._icon, 'focus', this._panOnFocus, this);
7666 }
7667
7668 remove(this._icon);
7669 this.removeInteractiveTarget(this._icon);
7670
7671 this._icon = null;
7672 },
7673
7674 _removeShadow: function () {
7675 if (this._shadow) {
7676 remove(this._shadow);
7677 }
7678 this._shadow = null;
7679 },
7680
7681 _setPos: function (pos) {
7682
7683 if (this._icon) {
7684 setPosition(this._icon, pos);
7685 }
7686
7687 if (this._shadow) {
7688 setPosition(this._shadow, pos);
7689 }
7690
7691 this._zIndex = pos.y + this.options.zIndexOffset;
7692
7693 this._resetZIndex();
7694 },
7695
7696 _updateZIndex: function (offset) {
7697 if (this._icon) {
7698 this._icon.style.zIndex = this._zIndex + offset;
7699 }
7700 },
7701
7702 _animateZoom: function (opt) {
7703 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7704
7705 this._setPos(pos);
7706 },
7707
7708 _initInteraction: function () {
7709
7710 if (!this.options.interactive) { return; }
7711
7712 addClass(this._icon, 'leaflet-interactive');
7713
7714 this.addInteractiveTarget(this._icon);
7715
7716 if (MarkerDrag) {
7717 var draggable = this.options.draggable;
7718 if (this.dragging) {
7719 draggable = this.dragging.enabled();
7720 this.dragging.disable();
7721 }
7722
7723 this.dragging = new MarkerDrag(this);
7724
7725 if (draggable) {
7726 this.dragging.enable();
7727 }
7728 }
7729 },
7730
7731 // @method setOpacity(opacity: Number): this
7732 // Changes the opacity of the marker.
7733 setOpacity: function (opacity) {
7734 this.options.opacity = opacity;
7735 if (this._map) {
7736 this._updateOpacity();
7737 }
7738
7739 return this;
7740 },
7741
7742 _updateOpacity: function () {
7743 var opacity = this.options.opacity;
7744
7745 if (this._icon) {
7746 setOpacity(this._icon, opacity);
7747 }
7748
7749 if (this._shadow) {
7750 setOpacity(this._shadow, opacity);
7751 }
7752 },
7753
7754 _bringToFront: function () {
7755 this._updateZIndex(this.options.riseOffset);
7756 },
7757
7758 _resetZIndex: function () {
7759 this._updateZIndex(0);
7760 },
7761
7762 _panOnFocus: function () {
7763 var map = this._map;
7764 if (!map) { return; }
7765
7766 var iconOpts = this.options.icon.options;
7767 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);
7768 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);
7769
7770 map.panInside(this._latlng, {
7771 paddingTopLeft: anchor,
7772 paddingBottomRight: size.subtract(anchor)
7773 });
7774 },
7775
7776 _getPopupAnchor: function () {
7777 return this.options.icon.options.popupAnchor;
7778 },
7779
7780 _getTooltipAnchor: function () {
7781 return this.options.icon.options.tooltipAnchor;
7782 }
7783 });
7784
7785
7786 // factory L.marker(latlng: LatLng, options? : Marker options)
7787
7788 // @factory L.marker(latlng: LatLng, options? : Marker options)
7789 // Instantiates a Marker object given a geographical point and optionally an options object.
7790 function marker(latlng, options) {
7791 return new Marker(latlng, options);
7792 }
7793
7794 /*
7795 * @class Path
7796 * @aka L.Path
7797 * @inherits Interactive layer
7798 *
7799 * An abstract class that contains options and constants shared between vector
7800 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7801 */
7802
7803 var Path = Layer.extend({
7804
7805 // @section
7806 // @aka Path options
7807 options: {
7808 // @option stroke: Boolean = true
7809 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7810 stroke: true,
7811
7812 // @option color: String = '#3388ff'
7813 // Stroke color
7814 color: '#3388ff',
7815
7816 // @option weight: Number = 3
7817 // Stroke width in pixels
7818 weight: 3,
7819
7820 // @option opacity: Number = 1.0
7821 // Stroke opacity
7822 opacity: 1,
7823
7824 // @option lineCap: String= 'round'
7825 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7826 lineCap: 'round',
7827
7828 // @option lineJoin: String = 'round'
7829 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7830 lineJoin: 'round',
7831
7832 // @option dashArray: String = null
7833 // 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).
7834 dashArray: null,
7835
7836 // @option dashOffset: String = null
7837 // 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).
7838 dashOffset: null,
7839
7840 // @option fill: Boolean = depends
7841 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7842 fill: false,
7843
7844 // @option fillColor: String = *
7845 // Fill color. Defaults to the value of the [`color`](#path-color) option
7846 fillColor: null,
7847
7848 // @option fillOpacity: Number = 0.2
7849 // Fill opacity.
7850 fillOpacity: 0.2,
7851
7852 // @option fillRule: String = 'evenodd'
7853 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7854 fillRule: 'evenodd',
7855
7856 // className: '',
7857
7858 // Option inherited from "Interactive layer" abstract class
7859 interactive: true,
7860
7861 // @option bubblingMouseEvents: Boolean = true
7862 // When `true`, a mouse event on this path will trigger the same event on the map
7863 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7864 bubblingMouseEvents: true
7865 },
7866
7867 beforeAdd: function (map) {
7868 // Renderer is set here because we need to call renderer.getEvents
7869 // before this.getEvents.
7870 this._renderer = map.getRenderer(this);
7871 },
7872
7873 onAdd: function () {
7874 this._renderer._initPath(this);
7875 this._reset();
7876 this._renderer._addPath(this);
7877 },
7878
7879 onRemove: function () {
7880 this._renderer._removePath(this);
7881 },
7882
7883 // @method redraw(): this
7884 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7885 redraw: function () {
7886 if (this._map) {
7887 this._renderer._updatePath(this);
7888 }
7889 return this;
7890 },
7891
7892 // @method setStyle(style: Path options): this
7893 // Changes the appearance of a Path based on the options in the `Path options` object.
7894 setStyle: function (style) {
7895 setOptions(this, style);
7896 if (this._renderer) {
7897 this._renderer._updateStyle(this);
7898 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
7899 this._updateBounds();
7900 }
7901 }
7902 return this;
7903 },
7904
7905 // @method bringToFront(): this
7906 // Brings the layer to the top of all path layers.
7907 bringToFront: function () {
7908 if (this._renderer) {
7909 this._renderer._bringToFront(this);
7910 }
7911 return this;
7912 },
7913
7914 // @method bringToBack(): this
7915 // Brings the layer to the bottom of all path layers.
7916 bringToBack: function () {
7917 if (this._renderer) {
7918 this._renderer._bringToBack(this);
7919 }
7920 return this;
7921 },
7922
7923 getElement: function () {
7924 return this._path;
7925 },
7926
7927 _reset: function () {
7928 // defined in child classes
7929 this._project();
7930 this._update();
7931 },
7932
7933 _clickTolerance: function () {
7934 // used when doing hit detection for Canvas layers
7935 return (this.options.stroke ? this.options.weight / 2 : 0) +
7936 (this._renderer.options.tolerance || 0);
7937 }
7938 });
7939
7940 /*
7941 * @class CircleMarker
7942 * @aka L.CircleMarker
7943 * @inherits Path
7944 *
7945 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7946 */
7947
7948 var CircleMarker = Path.extend({
7949
7950 // @section
7951 // @aka CircleMarker options
7952 options: {
7953 fill: true,
7954
7955 // @option radius: Number = 10
7956 // Radius of the circle marker, in pixels
7957 radius: 10
7958 },
7959
7960 initialize: function (latlng, options) {
7961 setOptions(this, options);
7962 this._latlng = toLatLng(latlng);
7963 this._radius = this.options.radius;
7964 },
7965
7966 // @method setLatLng(latLng: LatLng): this
7967 // Sets the position of a circle marker to a new location.
7968 setLatLng: function (latlng) {
7969 var oldLatLng = this._latlng;
7970 this._latlng = toLatLng(latlng);
7971 this.redraw();
7972
7973 // @event move: Event
7974 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
7975 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7976 },
7977
7978 // @method getLatLng(): LatLng
7979 // Returns the current geographical position of the circle marker
7980 getLatLng: function () {
7981 return this._latlng;
7982 },
7983
7984 // @method setRadius(radius: Number): this
7985 // Sets the radius of a circle marker. Units are in pixels.
7986 setRadius: function (radius) {
7987 this.options.radius = this._radius = radius;
7988 return this.redraw();
7989 },
7990
7991 // @method getRadius(): Number
7992 // Returns the current radius of the circle
7993 getRadius: function () {
7994 return this._radius;
7995 },
7996
7997 setStyle : function (options) {
7998 var radius = options && options.radius || this._radius;
7999 Path.prototype.setStyle.call(this, options);
8000 this.setRadius(radius);
8001 return this;
8002 },
8003
8004 _project: function () {
8005 this._point = this._map.latLngToLayerPoint(this._latlng);
8006 this._updateBounds();
8007 },
8008
8009 _updateBounds: function () {
8010 var r = this._radius,
8011 r2 = this._radiusY || r,
8012 w = this._clickTolerance(),
8013 p = [r + w, r2 + w];
8014 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
8015 },
8016
8017 _update: function () {
8018 if (this._map) {
8019 this._updatePath();
8020 }
8021 },
8022
8023 _updatePath: function () {
8024 this._renderer._updateCircle(this);
8025 },
8026
8027 _empty: function () {
8028 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8029 },
8030
8031 // Needed by the `Canvas` renderer for interactivity
8032 _containsPoint: function (p) {
8033 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8034 }
8035 });
8036
8037
8038 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8039 // Instantiates a circle marker object given a geographical point, and an optional options object.
8040 function circleMarker(latlng, options) {
8041 return new CircleMarker(latlng, options);
8042 }
8043
8044 /*
8045 * @class Circle
8046 * @aka L.Circle
8047 * @inherits CircleMarker
8048 *
8049 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8050 *
8051 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8052 *
8053 * @example
8054 *
8055 * ```js
8056 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8057 * ```
8058 */
8059
8060 var Circle = CircleMarker.extend({
8061
8062 initialize: function (latlng, options, legacyOptions) {
8063 if (typeof options === 'number') {
8064 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8065 options = extend({}, legacyOptions, {radius: options});
8066 }
8067 setOptions(this, options);
8068 this._latlng = toLatLng(latlng);
8069
8070 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8071
8072 // @section
8073 // @aka Circle options
8074 // @option radius: Number; Radius of the circle, in meters.
8075 this._mRadius = this.options.radius;
8076 },
8077
8078 // @method setRadius(radius: Number): this
8079 // Sets the radius of a circle. Units are in meters.
8080 setRadius: function (radius) {
8081 this._mRadius = radius;
8082 return this.redraw();
8083 },
8084
8085 // @method getRadius(): Number
8086 // Returns the current radius of a circle. Units are in meters.
8087 getRadius: function () {
8088 return this._mRadius;
8089 },
8090
8091 // @method getBounds(): LatLngBounds
8092 // Returns the `LatLngBounds` of the path.
8093 getBounds: function () {
8094 var half = [this._radius, this._radiusY || this._radius];
8095
8096 return new LatLngBounds(
8097 this._map.layerPointToLatLng(this._point.subtract(half)),
8098 this._map.layerPointToLatLng(this._point.add(half)));
8099 },
8100
8101 setStyle: Path.prototype.setStyle,
8102
8103 _project: function () {
8104
8105 var lng = this._latlng.lng,
8106 lat = this._latlng.lat,
8107 map = this._map,
8108 crs = map.options.crs;
8109
8110 if (crs.distance === Earth.distance) {
8111 var d = Math.PI / 180,
8112 latR = (this._mRadius / Earth.R) / d,
8113 top = map.project([lat + latR, lng]),
8114 bottom = map.project([lat - latR, lng]),
8115 p = top.add(bottom).divideBy(2),
8116 lat2 = map.unproject(p).lat,
8117 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8118 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8119
8120 if (isNaN(lngR) || lngR === 0) {
8121 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8122 }
8123
8124 this._point = p.subtract(map.getPixelOrigin());
8125 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8126 this._radiusY = p.y - top.y;
8127
8128 } else {
8129 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8130
8131 this._point = map.latLngToLayerPoint(this._latlng);
8132 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8133 }
8134
8135 this._updateBounds();
8136 }
8137 });
8138
8139 // @factory L.circle(latlng: LatLng, options?: Circle options)
8140 // Instantiates a circle object given a geographical point, and an options object
8141 // which contains the circle radius.
8142 // @alternative
8143 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8144 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8145 // Do not use in new applications or plugins.
8146 function circle(latlng, options, legacyOptions) {
8147 return new Circle(latlng, options, legacyOptions);
8148 }
8149
8150 /*
8151 * @class Polyline
8152 * @aka L.Polyline
8153 * @inherits Path
8154 *
8155 * A class for drawing polyline overlays on a map. Extends `Path`.
8156 *
8157 * @example
8158 *
8159 * ```js
8160 * // create a red polyline from an array of LatLng points
8161 * var latlngs = [
8162 * [45.51, -122.68],
8163 * [37.77, -122.43],
8164 * [34.04, -118.2]
8165 * ];
8166 *
8167 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8168 *
8169 * // zoom the map to the polyline
8170 * map.fitBounds(polyline.getBounds());
8171 * ```
8172 *
8173 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8174 *
8175 * ```js
8176 * // create a red polyline from an array of arrays of LatLng points
8177 * var latlngs = [
8178 * [[45.51, -122.68],
8179 * [37.77, -122.43],
8180 * [34.04, -118.2]],
8181 * [[40.78, -73.91],
8182 * [41.83, -87.62],
8183 * [32.76, -96.72]]
8184 * ];
8185 * ```
8186 */
8187
8188
8189 var Polyline = Path.extend({
8190
8191 // @section
8192 // @aka Polyline options
8193 options: {
8194 // @option smoothFactor: Number = 1.0
8195 // How much to simplify the polyline on each zoom level. More means
8196 // better performance and smoother look, and less means more accurate representation.
8197 smoothFactor: 1.0,
8198
8199 // @option noClip: Boolean = false
8200 // Disable polyline clipping.
8201 noClip: false
8202 },
8203
8204 initialize: function (latlngs, options) {
8205 setOptions(this, options);
8206 this._setLatLngs(latlngs);
8207 },
8208
8209 // @method getLatLngs(): LatLng[]
8210 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8211 getLatLngs: function () {
8212 return this._latlngs;
8213 },
8214
8215 // @method setLatLngs(latlngs: LatLng[]): this
8216 // Replaces all the points in the polyline with the given array of geographical points.
8217 setLatLngs: function (latlngs) {
8218 this._setLatLngs(latlngs);
8219 return this.redraw();
8220 },
8221
8222 // @method isEmpty(): Boolean
8223 // Returns `true` if the Polyline has no LatLngs.
8224 isEmpty: function () {
8225 return !this._latlngs.length;
8226 },
8227
8228 // @method closestLayerPoint(p: Point): Point
8229 // Returns the point closest to `p` on the Polyline.
8230 closestLayerPoint: function (p) {
8231 var minDistance = Infinity,
8232 minPoint = null,
8233 closest = _sqClosestPointOnSegment,
8234 p1, p2;
8235
8236 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8237 var points = this._parts[j];
8238
8239 for (var i = 1, len = points.length; i < len; i++) {
8240 p1 = points[i - 1];
8241 p2 = points[i];
8242
8243 var sqDist = closest(p, p1, p2, true);
8244
8245 if (sqDist < minDistance) {
8246 minDistance = sqDist;
8247 minPoint = closest(p, p1, p2);
8248 }
8249 }
8250 }
8251 if (minPoint) {
8252 minPoint.distance = Math.sqrt(minDistance);
8253 }
8254 return minPoint;
8255 },
8256
8257 // @method getCenter(): LatLng
8258 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
8259 getCenter: function () {
8260 // throws error when not yet added to map as this center calculation requires projected coordinates
8261 if (!this._map) {
8262 throw new Error('Must add layer to map before using getCenter()');
8263 }
8264
8265 var i, halfDist, segDist, dist, p1, p2, ratio,
8266 points = this._rings[0],
8267 len = points.length;
8268
8269 if (!len) { return null; }
8270
8271 // polyline centroid algorithm; only uses the first ring if there are multiple
8272
8273 for (i = 0, halfDist = 0; i < len - 1; i++) {
8274 halfDist += points[i].distanceTo(points[i + 1]) / 2;
8275 }
8276
8277 // The line is so small in the current view that all points are on the same pixel.
8278 if (halfDist === 0) {
8279 return this._map.layerPointToLatLng(points[0]);
8280 }
8281
8282 for (i = 0, dist = 0; i < len - 1; i++) {
8283 p1 = points[i];
8284 p2 = points[i + 1];
8285 segDist = p1.distanceTo(p2);
8286 dist += segDist;
8287
8288 if (dist > halfDist) {
8289 ratio = (dist - halfDist) / segDist;
8290 return this._map.layerPointToLatLng([
8291 p2.x - ratio * (p2.x - p1.x),
8292 p2.y - ratio * (p2.y - p1.y)
8293 ]);
8294 }
8295 }
8296 },
8297
8298 // @method getBounds(): LatLngBounds
8299 // Returns the `LatLngBounds` of the path.
8300 getBounds: function () {
8301 return this._bounds;
8302 },
8303
8304 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8305 // Adds a given point to the polyline. By default, adds to the first ring of
8306 // the polyline in case of a multi-polyline, but can be overridden by passing
8307 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8308 addLatLng: function (latlng, latlngs) {
8309 latlngs = latlngs || this._defaultShape();
8310 latlng = toLatLng(latlng);
8311 latlngs.push(latlng);
8312 this._bounds.extend(latlng);
8313 return this.redraw();
8314 },
8315
8316 _setLatLngs: function (latlngs) {
8317 this._bounds = new LatLngBounds();
8318 this._latlngs = this._convertLatLngs(latlngs);
8319 },
8320
8321 _defaultShape: function () {
8322 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8323 },
8324
8325 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8326 _convertLatLngs: function (latlngs) {
8327 var result = [],
8328 flat = isFlat(latlngs);
8329
8330 for (var i = 0, len = latlngs.length; i < len; i++) {
8331 if (flat) {
8332 result[i] = toLatLng(latlngs[i]);
8333 this._bounds.extend(result[i]);
8334 } else {
8335 result[i] = this._convertLatLngs(latlngs[i]);
8336 }
8337 }
8338
8339 return result;
8340 },
8341
8342 _project: function () {
8343 var pxBounds = new Bounds();
8344 this._rings = [];
8345 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8346
8347 if (this._bounds.isValid() && pxBounds.isValid()) {
8348 this._rawPxBounds = pxBounds;
8349 this._updateBounds();
8350 }
8351 },
8352
8353 _updateBounds: function () {
8354 var w = this._clickTolerance(),
8355 p = new Point(w, w);
8356
8357 if (!this._rawPxBounds) {
8358 return;
8359 }
8360
8361 this._pxBounds = new Bounds([
8362 this._rawPxBounds.min.subtract(p),
8363 this._rawPxBounds.max.add(p)
8364 ]);
8365 },
8366
8367 // recursively turns latlngs into a set of rings with projected coordinates
8368 _projectLatlngs: function (latlngs, result, projectedBounds) {
8369 var flat = latlngs[0] instanceof LatLng,
8370 len = latlngs.length,
8371 i, ring;
8372
8373 if (flat) {
8374 ring = [];
8375 for (i = 0; i < len; i++) {
8376 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8377 projectedBounds.extend(ring[i]);
8378 }
8379 result.push(ring);
8380 } else {
8381 for (i = 0; i < len; i++) {
8382 this._projectLatlngs(latlngs[i], result, projectedBounds);
8383 }
8384 }
8385 },
8386
8387 // clip polyline by renderer bounds so that we have less to render for performance
8388 _clipPoints: function () {
8389 var bounds = this._renderer._bounds;
8390
8391 this._parts = [];
8392 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8393 return;
8394 }
8395
8396 if (this.options.noClip) {
8397 this._parts = this._rings;
8398 return;
8399 }
8400
8401 var parts = this._parts,
8402 i, j, k, len, len2, segment, points;
8403
8404 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8405 points = this._rings[i];
8406
8407 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8408 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8409
8410 if (!segment) { continue; }
8411
8412 parts[k] = parts[k] || [];
8413 parts[k].push(segment[0]);
8414
8415 // if segment goes out of screen, or it's the last one, it's the end of the line part
8416 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8417 parts[k].push(segment[1]);
8418 k++;
8419 }
8420 }
8421 }
8422 },
8423
8424 // simplify each clipped part of the polyline for performance
8425 _simplifyPoints: function () {
8426 var parts = this._parts,
8427 tolerance = this.options.smoothFactor;
8428
8429 for (var i = 0, len = parts.length; i < len; i++) {
8430 parts[i] = simplify(parts[i], tolerance);
8431 }
8432 },
8433
8434 _update: function () {
8435 if (!this._map) { return; }
8436
8437 this._clipPoints();
8438 this._simplifyPoints();
8439 this._updatePath();
8440 },
8441
8442 _updatePath: function () {
8443 this._renderer._updatePoly(this);
8444 },
8445
8446 // Needed by the `Canvas` renderer for interactivity
8447 _containsPoint: function (p, closed) {
8448 var i, j, k, len, len2, part,
8449 w = this._clickTolerance();
8450
8451 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8452
8453 // hit detection for polylines
8454 for (i = 0, len = this._parts.length; i < len; i++) {
8455 part = this._parts[i];
8456
8457 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8458 if (!closed && (j === 0)) { continue; }
8459
8460 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8461 return true;
8462 }
8463 }
8464 }
8465 return false;
8466 }
8467 });
8468
8469 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8470 // Instantiates a polyline object given an array of geographical points and
8471 // optionally an options object. You can create a `Polyline` object with
8472 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8473 // of geographic points.
8474 function polyline(latlngs, options) {
8475 return new Polyline(latlngs, options);
8476 }
8477
8478 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8479 Polyline._flat = _flat;
8480
8481 /*
8482 * @class Polygon
8483 * @aka L.Polygon
8484 * @inherits Polyline
8485 *
8486 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8487 *
8488 * 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.
8489 *
8490 *
8491 * @example
8492 *
8493 * ```js
8494 * // create a red polygon from an array of LatLng points
8495 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8496 *
8497 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8498 *
8499 * // zoom the map to the polygon
8500 * map.fitBounds(polygon.getBounds());
8501 * ```
8502 *
8503 * 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:
8504 *
8505 * ```js
8506 * var latlngs = [
8507 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8508 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8509 * ];
8510 * ```
8511 *
8512 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8513 *
8514 * ```js
8515 * var latlngs = [
8516 * [ // first polygon
8517 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8518 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8519 * ],
8520 * [ // second polygon
8521 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8522 * ]
8523 * ];
8524 * ```
8525 */
8526
8527 var Polygon = Polyline.extend({
8528
8529 options: {
8530 fill: true
8531 },
8532
8533 isEmpty: function () {
8534 return !this._latlngs.length || !this._latlngs[0].length;
8535 },
8536
8537 getCenter: function () {
8538 // throws error when not yet added to map as this center calculation requires projected coordinates
8539 if (!this._map) {
8540 throw new Error('Must add layer to map before using getCenter()');
8541 }
8542
8543 var i, j, p1, p2, f, area, x, y, center,
8544 points = this._rings[0],
8545 len = points.length;
8546
8547 if (!len) { return null; }
8548
8549 // polygon centroid algorithm; only uses the first ring if there are multiple
8550
8551 area = x = y = 0;
8552
8553 for (i = 0, j = len - 1; i < len; j = i++) {
8554 p1 = points[i];
8555 p2 = points[j];
8556
8557 f = p1.y * p2.x - p2.y * p1.x;
8558 x += (p1.x + p2.x) * f;
8559 y += (p1.y + p2.y) * f;
8560 area += f * 3;
8561 }
8562
8563 if (area === 0) {
8564 // Polygon is so small that all points are on same pixel.
8565 center = points[0];
8566 } else {
8567 center = [x / area, y / area];
8568 }
8569 return this._map.layerPointToLatLng(center);
8570 },
8571
8572 _convertLatLngs: function (latlngs) {
8573 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8574 len = result.length;
8575
8576 // remove last point if it equals first one
8577 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8578 result.pop();
8579 }
8580 return result;
8581 },
8582
8583 _setLatLngs: function (latlngs) {
8584 Polyline.prototype._setLatLngs.call(this, latlngs);
8585 if (isFlat(this._latlngs)) {
8586 this._latlngs = [this._latlngs];
8587 }
8588 },
8589
8590 _defaultShape: function () {
8591 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8592 },
8593
8594 _clipPoints: function () {
8595 // polygons need a different clipping algorithm so we redefine that
8596
8597 var bounds = this._renderer._bounds,
8598 w = this.options.weight,
8599 p = new Point(w, w);
8600
8601 // increase clip padding by stroke width to avoid stroke on clip edges
8602 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8603
8604 this._parts = [];
8605 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8606 return;
8607 }
8608
8609 if (this.options.noClip) {
8610 this._parts = this._rings;
8611 return;
8612 }
8613
8614 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8615 clipped = clipPolygon(this._rings[i], bounds, true);
8616 if (clipped.length) {
8617 this._parts.push(clipped);
8618 }
8619 }
8620 },
8621
8622 _updatePath: function () {
8623 this._renderer._updatePoly(this, true);
8624 },
8625
8626 // Needed by the `Canvas` renderer for interactivity
8627 _containsPoint: function (p) {
8628 var inside = false,
8629 part, p1, p2, i, j, k, len, len2;
8630
8631 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8632
8633 // ray casting algorithm for detecting if point is in polygon
8634 for (i = 0, len = this._parts.length; i < len; i++) {
8635 part = this._parts[i];
8636
8637 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8638 p1 = part[j];
8639 p2 = part[k];
8640
8641 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)) {
8642 inside = !inside;
8643 }
8644 }
8645 }
8646
8647 // also check if it's on polygon stroke
8648 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8649 }
8650
8651 });
8652
8653
8654 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8655 function polygon(latlngs, options) {
8656 return new Polygon(latlngs, options);
8657 }
8658
8659 /*
8660 * @class GeoJSON
8661 * @aka L.GeoJSON
8662 * @inherits FeatureGroup
8663 *
8664 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8665 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8666 *
8667 * @example
8668 *
8669 * ```js
8670 * L.geoJSON(data, {
8671 * style: function (feature) {
8672 * return {color: feature.properties.color};
8673 * }
8674 * }).bindPopup(function (layer) {
8675 * return layer.feature.properties.description;
8676 * }).addTo(map);
8677 * ```
8678 */
8679
8680 var GeoJSON = FeatureGroup.extend({
8681
8682 /* @section
8683 * @aka GeoJSON options
8684 *
8685 * @option pointToLayer: Function = *
8686 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8687 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8688 * The default is to spawn a default `Marker`:
8689 * ```js
8690 * function(geoJsonPoint, latlng) {
8691 * return L.marker(latlng);
8692 * }
8693 * ```
8694 *
8695 * @option style: Function = *
8696 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8697 * called internally when data is added.
8698 * The default value is to not override any defaults:
8699 * ```js
8700 * function (geoJsonFeature) {
8701 * return {}
8702 * }
8703 * ```
8704 *
8705 * @option onEachFeature: Function = *
8706 * A `Function` that will be called once for each created `Feature`, after it has
8707 * been created and styled. Useful for attaching events and popups to features.
8708 * The default is to do nothing with the newly created layers:
8709 * ```js
8710 * function (feature, layer) {}
8711 * ```
8712 *
8713 * @option filter: Function = *
8714 * A `Function` that will be used to decide whether to include a feature or not.
8715 * The default is to include all features:
8716 * ```js
8717 * function (geoJsonFeature) {
8718 * return true;
8719 * }
8720 * ```
8721 * Note: dynamically changing the `filter` option will have effect only on newly
8722 * added data. It will _not_ re-evaluate already included features.
8723 *
8724 * @option coordsToLatLng: Function = *
8725 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8726 * The default is the `coordsToLatLng` static method.
8727 *
8728 * @option markersInheritOptions: Boolean = false
8729 * Whether default Markers for "Point" type Features inherit from group options.
8730 */
8731
8732 initialize: function (geojson, options) {
8733 setOptions(this, options);
8734
8735 this._layers = {};
8736
8737 if (geojson) {
8738 this.addData(geojson);
8739 }
8740 },
8741
8742 // @method addData( <GeoJSON> data ): this
8743 // Adds a GeoJSON object to the layer.
8744 addData: function (geojson) {
8745 var features = isArray(geojson) ? geojson : geojson.features,
8746 i, len, feature;
8747
8748 if (features) {
8749 for (i = 0, len = features.length; i < len; i++) {
8750 // only add this if geometry or geometries are set and not null
8751 feature = features[i];
8752 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8753 this.addData(feature);
8754 }
8755 }
8756 return this;
8757 }
8758
8759 var options = this.options;
8760
8761 if (options.filter && !options.filter(geojson)) { return this; }
8762
8763 var layer = geometryToLayer(geojson, options);
8764 if (!layer) {
8765 return this;
8766 }
8767 layer.feature = asFeature(geojson);
8768
8769 layer.defaultOptions = layer.options;
8770 this.resetStyle(layer);
8771
8772 if (options.onEachFeature) {
8773 options.onEachFeature(geojson, layer);
8774 }
8775
8776 return this.addLayer(layer);
8777 },
8778
8779 // @method resetStyle( <Path> layer? ): this
8780 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8781 // If `layer` is omitted, the style of all features in the current layer is reset.
8782 resetStyle: function (layer) {
8783 if (layer === undefined) {
8784 return this.eachLayer(this.resetStyle, this);
8785 }
8786 // reset any custom styles
8787 layer.options = extend({}, layer.defaultOptions);
8788 this._setLayerStyle(layer, this.options.style);
8789 return this;
8790 },
8791
8792 // @method setStyle( <Function> style ): this
8793 // Changes styles of GeoJSON vector layers with the given style function.
8794 setStyle: function (style) {
8795 return this.eachLayer(function (layer) {
8796 this._setLayerStyle(layer, style);
8797 }, this);
8798 },
8799
8800 _setLayerStyle: function (layer, style) {
8801 if (layer.setStyle) {
8802 if (typeof style === 'function') {
8803 style = style(layer.feature);
8804 }
8805 layer.setStyle(style);
8806 }
8807 }
8808 });
8809
8810 // @section
8811 // There are several static functions which can be called without instantiating L.GeoJSON:
8812
8813 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8814 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8815 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8816 // functions if provided as options.
8817 function geometryToLayer(geojson, options) {
8818
8819 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8820 coords = geometry ? geometry.coordinates : null,
8821 layers = [],
8822 pointToLayer = options && options.pointToLayer,
8823 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8824 latlng, latlngs, i, len;
8825
8826 if (!coords && !geometry) {
8827 return null;
8828 }
8829
8830 switch (geometry.type) {
8831 case 'Point':
8832 latlng = _coordsToLatLng(coords);
8833 return _pointToLayer(pointToLayer, geojson, latlng, options);
8834
8835 case 'MultiPoint':
8836 for (i = 0, len = coords.length; i < len; i++) {
8837 latlng = _coordsToLatLng(coords[i]);
8838 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
8839 }
8840 return new FeatureGroup(layers);
8841
8842 case 'LineString':
8843 case 'MultiLineString':
8844 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8845 return new Polyline(latlngs, options);
8846
8847 case 'Polygon':
8848 case 'MultiPolygon':
8849 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8850 return new Polygon(latlngs, options);
8851
8852 case 'GeometryCollection':
8853 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8854 var layer = geometryToLayer({
8855 geometry: geometry.geometries[i],
8856 type: 'Feature',
8857 properties: geojson.properties
8858 }, options);
8859
8860 if (layer) {
8861 layers.push(layer);
8862 }
8863 }
8864 return new FeatureGroup(layers);
8865
8866 default:
8867 throw new Error('Invalid GeoJSON object.');
8868 }
8869 }
8870
8871 function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
8872 return pointToLayerFn ?
8873 pointToLayerFn(geojson, latlng) :
8874 new Marker(latlng, options && options.markersInheritOptions && options);
8875 }
8876
8877 // @function coordsToLatLng(coords: Array): LatLng
8878 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8879 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8880 function coordsToLatLng(coords) {
8881 return new LatLng(coords[1], coords[0], coords[2]);
8882 }
8883
8884 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8885 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8886 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8887 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8888 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8889 var latlngs = [];
8890
8891 for (var i = 0, len = coords.length, latlng; i < len; i++) {
8892 latlng = levelsDeep ?
8893 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8894 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8895
8896 latlngs.push(latlng);
8897 }
8898
8899 return latlngs;
8900 }
8901
8902 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
8903 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8904 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
8905 function latLngToCoords(latlng, precision) {
8906 latlng = toLatLng(latlng);
8907 return latlng.alt !== undefined ?
8908 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8909 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8910 }
8911
8912 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
8913 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8914 // `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.
8915 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
8916 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8917 var coords = [];
8918
8919 for (var i = 0, len = latlngs.length; i < len; i++) {
8920 coords.push(levelsDeep ?
8921 latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8922 latLngToCoords(latlngs[i], precision));
8923 }
8924
8925 if (!levelsDeep && closed) {
8926 coords.push(coords[0]);
8927 }
8928
8929 return coords;
8930 }
8931
8932 function getFeature(layer, newGeometry) {
8933 return layer.feature ?
8934 extend({}, layer.feature, {geometry: newGeometry}) :
8935 asFeature(newGeometry);
8936 }
8937
8938 // @function asFeature(geojson: Object): Object
8939 // Normalize GeoJSON geometries/features into GeoJSON features.
8940 function asFeature(geojson) {
8941 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8942 return geojson;
8943 }
8944
8945 return {
8946 type: 'Feature',
8947 properties: {},
8948 geometry: geojson
8949 };
8950 }
8951
8952 var PointToGeoJSON = {
8953 toGeoJSON: function (precision) {
8954 return getFeature(this, {
8955 type: 'Point',
8956 coordinates: latLngToCoords(this.getLatLng(), precision)
8957 });
8958 }
8959 };
8960
8961 // @namespace Marker
8962 // @section Other methods
8963 // @method toGeoJSON(precision?: Number|false): Object
8964 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
8965 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8966 Marker.include(PointToGeoJSON);
8967
8968 // @namespace CircleMarker
8969 // @method toGeoJSON(precision?: Number|false): Object
8970 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
8971 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8972 Circle.include(PointToGeoJSON);
8973 CircleMarker.include(PointToGeoJSON);
8974
8975
8976 // @namespace Polyline
8977 // @method toGeoJSON(precision?: Number|false): Object
8978 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
8979 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8980 Polyline.include({
8981 toGeoJSON: function (precision) {
8982 var multi = !isFlat(this._latlngs);
8983
8984 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8985
8986 return getFeature(this, {
8987 type: (multi ? 'Multi' : '') + 'LineString',
8988 coordinates: coords
8989 });
8990 }
8991 });
8992
8993 // @namespace Polygon
8994 // @method toGeoJSON(precision?: Number|false): Object
8995 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
8996 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8997 Polygon.include({
8998 toGeoJSON: function (precision) {
8999 var holes = !isFlat(this._latlngs),
9000 multi = holes && !isFlat(this._latlngs[0]);
9001
9002 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
9003
9004 if (!holes) {
9005 coords = [coords];
9006 }
9007
9008 return getFeature(this, {
9009 type: (multi ? 'Multi' : '') + 'Polygon',
9010 coordinates: coords
9011 });
9012 }
9013 });
9014
9015
9016 // @namespace LayerGroup
9017 LayerGroup.include({
9018 toMultiPoint: function (precision) {
9019 var coords = [];
9020
9021 this.eachLayer(function (layer) {
9022 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
9023 });
9024
9025 return getFeature(this, {
9026 type: 'MultiPoint',
9027 coordinates: coords
9028 });
9029 },
9030
9031 // @method toGeoJSON(precision?: Number|false): Object
9032 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
9033 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
9034 toGeoJSON: function (precision) {
9035
9036 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9037
9038 if (type === 'MultiPoint') {
9039 return this.toMultiPoint(precision);
9040 }
9041
9042 var isGeometryCollection = type === 'GeometryCollection',
9043 jsons = [];
9044
9045 this.eachLayer(function (layer) {
9046 if (layer.toGeoJSON) {
9047 var json = layer.toGeoJSON(precision);
9048 if (isGeometryCollection) {
9049 jsons.push(json.geometry);
9050 } else {
9051 var feature = asFeature(json);
9052 // Squash nested feature collections
9053 if (feature.type === 'FeatureCollection') {
9054 jsons.push.apply(jsons, feature.features);
9055 } else {
9056 jsons.push(feature);
9057 }
9058 }
9059 }
9060 });
9061
9062 if (isGeometryCollection) {
9063 return getFeature(this, {
9064 geometries: jsons,
9065 type: 'GeometryCollection'
9066 });
9067 }
9068
9069 return {
9070 type: 'FeatureCollection',
9071 features: jsons
9072 };
9073 }
9074 });
9075
9076 // @namespace GeoJSON
9077 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9078 // Creates a GeoJSON layer. Optionally accepts an object in
9079 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9080 // (you can alternatively add it later with `addData` method) and an `options` object.
9081 function geoJSON(geojson, options) {
9082 return new GeoJSON(geojson, options);
9083 }
9084
9085 // Backward compatibility.
9086 var geoJson = geoJSON;
9087
9088 /*
9089 * @class ImageOverlay
9090 * @aka L.ImageOverlay
9091 * @inherits Interactive layer
9092 *
9093 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9094 *
9095 * @example
9096 *
9097 * ```js
9098 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9099 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9100 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9101 * ```
9102 */
9103
9104 var ImageOverlay = Layer.extend({
9105
9106 // @section
9107 // @aka ImageOverlay options
9108 options: {
9109 // @option opacity: Number = 1.0
9110 // The opacity of the image overlay.
9111 opacity: 1,
9112
9113 // @option alt: String = ''
9114 // Text for the `alt` attribute of the image (useful for accessibility).
9115 alt: '',
9116
9117 // @option interactive: Boolean = false
9118 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9119 interactive: false,
9120
9121 // @option crossOrigin: Boolean|String = false
9122 // Whether the crossOrigin attribute will be added to the image.
9123 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
9124 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9125 crossOrigin: false,
9126
9127 // @option errorOverlayUrl: String = ''
9128 // URL to the overlay image to show in place of the overlay that failed to load.
9129 errorOverlayUrl: '',
9130
9131 // @option zIndex: Number = 1
9132 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9133 zIndex: 1,
9134
9135 // @option className: String = ''
9136 // A custom class name to assign to the image. Empty by default.
9137 className: ''
9138 },
9139
9140 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9141 this._url = url;
9142 this._bounds = toLatLngBounds(bounds);
9143
9144 setOptions(this, options);
9145 },
9146
9147 onAdd: function () {
9148 if (!this._image) {
9149 this._initImage();
9150
9151 if (this.options.opacity < 1) {
9152 this._updateOpacity();
9153 }
9154 }
9155
9156 if (this.options.interactive) {
9157 addClass(this._image, 'leaflet-interactive');
9158 this.addInteractiveTarget(this._image);
9159 }
9160
9161 this.getPane().appendChild(this._image);
9162 this._reset();
9163 },
9164
9165 onRemove: function () {
9166 remove(this._image);
9167 if (this.options.interactive) {
9168 this.removeInteractiveTarget(this._image);
9169 }
9170 },
9171
9172 // @method setOpacity(opacity: Number): this
9173 // Sets the opacity of the overlay.
9174 setOpacity: function (opacity) {
9175 this.options.opacity = opacity;
9176
9177 if (this._image) {
9178 this._updateOpacity();
9179 }
9180 return this;
9181 },
9182
9183 setStyle: function (styleOpts) {
9184 if (styleOpts.opacity) {
9185 this.setOpacity(styleOpts.opacity);
9186 }
9187 return this;
9188 },
9189
9190 // @method bringToFront(): this
9191 // Brings the layer to the top of all overlays.
9192 bringToFront: function () {
9193 if (this._map) {
9194 toFront(this._image);
9195 }
9196 return this;
9197 },
9198
9199 // @method bringToBack(): this
9200 // Brings the layer to the bottom of all overlays.
9201 bringToBack: function () {
9202 if (this._map) {
9203 toBack(this._image);
9204 }
9205 return this;
9206 },
9207
9208 // @method setUrl(url: String): this
9209 // Changes the URL of the image.
9210 setUrl: function (url) {
9211 this._url = url;
9212
9213 if (this._image) {
9214 this._image.src = url;
9215 }
9216 return this;
9217 },
9218
9219 // @method setBounds(bounds: LatLngBounds): this
9220 // Update the bounds that this ImageOverlay covers
9221 setBounds: function (bounds) {
9222 this._bounds = toLatLngBounds(bounds);
9223
9224 if (this._map) {
9225 this._reset();
9226 }
9227 return this;
9228 },
9229
9230 getEvents: function () {
9231 var events = {
9232 zoom: this._reset,
9233 viewreset: this._reset
9234 };
9235
9236 if (this._zoomAnimated) {
9237 events.zoomanim = this._animateZoom;
9238 }
9239
9240 return events;
9241 },
9242
9243 // @method setZIndex(value: Number): this
9244 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9245 setZIndex: function (value) {
9246 this.options.zIndex = value;
9247 this._updateZIndex();
9248 return this;
9249 },
9250
9251 // @method getBounds(): LatLngBounds
9252 // Get the bounds that this ImageOverlay covers
9253 getBounds: function () {
9254 return this._bounds;
9255 },
9256
9257 // @method getElement(): HTMLElement
9258 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9259 // used by this overlay.
9260 getElement: function () {
9261 return this._image;
9262 },
9263
9264 _initImage: function () {
9265 var wasElementSupplied = this._url.tagName === 'IMG';
9266 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9267
9268 addClass(img, 'leaflet-image-layer');
9269 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9270 if (this.options.className) { addClass(img, this.options.className); }
9271
9272 img.onselectstart = falseFn;
9273 img.onmousemove = falseFn;
9274
9275 // @event load: Event
9276 // Fired when the ImageOverlay layer has loaded its image
9277 img.onload = bind(this.fire, this, 'load');
9278 img.onerror = bind(this._overlayOnError, this, 'error');
9279
9280 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9281 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9282 }
9283
9284 if (this.options.zIndex) {
9285 this._updateZIndex();
9286 }
9287
9288 if (wasElementSupplied) {
9289 this._url = img.src;
9290 return;
9291 }
9292
9293 img.src = this._url;
9294 img.alt = this.options.alt;
9295 },
9296
9297 _animateZoom: function (e) {
9298 var scale = this._map.getZoomScale(e.zoom),
9299 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9300
9301 setTransform(this._image, offset, scale);
9302 },
9303
9304 _reset: function () {
9305 var image = this._image,
9306 bounds = new Bounds(
9307 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9308 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9309 size = bounds.getSize();
9310
9311 setPosition(image, bounds.min);
9312
9313 image.style.width = size.x + 'px';
9314 image.style.height = size.y + 'px';
9315 },
9316
9317 _updateOpacity: function () {
9318 setOpacity(this._image, this.options.opacity);
9319 },
9320
9321 _updateZIndex: function () {
9322 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9323 this._image.style.zIndex = this.options.zIndex;
9324 }
9325 },
9326
9327 _overlayOnError: function () {
9328 // @event error: Event
9329 // Fired when the ImageOverlay layer fails to load its image
9330 this.fire('error');
9331
9332 var errorUrl = this.options.errorOverlayUrl;
9333 if (errorUrl && this._url !== errorUrl) {
9334 this._url = errorUrl;
9335 this._image.src = errorUrl;
9336 }
9337 },
9338
9339 // @method getCenter(): LatLng
9340 // Returns the center of the ImageOverlay.
9341 getCenter: function () {
9342 return this._bounds.getCenter();
9343 }
9344 });
9345
9346 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9347 // Instantiates an image overlay object given the URL of the image and the
9348 // geographical bounds it is tied to.
9349 var imageOverlay = function (url, bounds, options) {
9350 return new ImageOverlay(url, bounds, options);
9351 };
9352
9353 /*
9354 * @class VideoOverlay
9355 * @aka L.VideoOverlay
9356 * @inherits ImageOverlay
9357 *
9358 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9359 *
9360 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9361 * HTML5 element.
9362 *
9363 * @example
9364 *
9365 * ```js
9366 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9367 * videoBounds = [[ 32, -130], [ 13, -100]];
9368 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9369 * ```
9370 */
9371
9372 var VideoOverlay = ImageOverlay.extend({
9373
9374 // @section
9375 // @aka VideoOverlay options
9376 options: {
9377 // @option autoplay: Boolean = true
9378 // Whether the video starts playing automatically when loaded.
9379 // On some browsers autoplay will only work with `muted: true`
9380 autoplay: true,
9381
9382 // @option loop: Boolean = true
9383 // Whether the video will loop back to the beginning when played.
9384 loop: true,
9385
9386 // @option keepAspectRatio: Boolean = true
9387 // Whether the video will save aspect ratio after the projection.
9388 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
9389 keepAspectRatio: true,
9390
9391 // @option muted: Boolean = false
9392 // Whether the video starts on mute when loaded.
9393 muted: false,
9394
9395 // @option playsInline: Boolean = true
9396 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
9397 playsInline: true
9398 },
9399
9400 _initImage: function () {
9401 var wasElementSupplied = this._url.tagName === 'VIDEO';
9402 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9403
9404 addClass(vid, 'leaflet-image-layer');
9405 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9406 if (this.options.className) { addClass(vid, this.options.className); }
9407
9408 vid.onselectstart = falseFn;
9409 vid.onmousemove = falseFn;
9410
9411 // @event load: Event
9412 // Fired when the video has finished loading the first frame
9413 vid.onloadeddata = bind(this.fire, this, 'load');
9414
9415 if (wasElementSupplied) {
9416 var sourceElements = vid.getElementsByTagName('source');
9417 var sources = [];
9418 for (var j = 0; j < sourceElements.length; j++) {
9419 sources.push(sourceElements[j].src);
9420 }
9421
9422 this._url = (sourceElements.length > 0) ? sources : [vid.src];
9423 return;
9424 }
9425
9426 if (!isArray(this._url)) { this._url = [this._url]; }
9427
9428 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
9429 vid.style['objectFit'] = 'fill';
9430 }
9431 vid.autoplay = !!this.options.autoplay;
9432 vid.loop = !!this.options.loop;
9433 vid.muted = !!this.options.muted;
9434 vid.playsInline = !!this.options.playsInline;
9435 for (var i = 0; i < this._url.length; i++) {
9436 var source = create$1('source');
9437 source.src = this._url[i];
9438 vid.appendChild(source);
9439 }
9440 }
9441
9442 // @method getElement(): HTMLVideoElement
9443 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9444 // used by this overlay.
9445 });
9446
9447
9448 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9449 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9450 // geographical bounds it is tied to.
9451
9452 function videoOverlay(video, bounds, options) {
9453 return new VideoOverlay(video, bounds, options);
9454 }
9455
9456 /*
9457 * @class SVGOverlay
9458 * @aka L.SVGOverlay
9459 * @inherits ImageOverlay
9460 *
9461 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9462 *
9463 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9464 *
9465 * @example
9466 *
9467 * ```js
9468 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9469 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9470 * svgElement.setAttribute('viewBox', "0 0 200 200");
9471 * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
9472 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9473 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9474 * ```
9475 */
9476
9477 var SVGOverlay = ImageOverlay.extend({
9478 _initImage: function () {
9479 var el = this._image = this._url;
9480
9481 addClass(el, 'leaflet-image-layer');
9482 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9483 if (this.options.className) { addClass(el, this.options.className); }
9484
9485 el.onselectstart = falseFn;
9486 el.onmousemove = falseFn;
9487 }
9488
9489 // @method getElement(): SVGElement
9490 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9491 // used by this overlay.
9492 });
9493
9494
9495 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9496 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9497 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9498
9499 function svgOverlay(el, bounds, options) {
9500 return new SVGOverlay(el, bounds, options);
9501 }
9502
9503 /*
9504 * @class DivOverlay
9505 * @inherits Interactive layer
9506 * @aka L.DivOverlay
9507 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
9508 */
9509
9510 // @namespace DivOverlay
9511 var DivOverlay = Layer.extend({
9512
9513 // @section
9514 // @aka DivOverlay options
9515 options: {
9516 // @option interactive: Boolean = false
9517 // If true, the popup/tooltip will listen to the mouse events.
9518 interactive: false,
9519
9520 // @option offset: Point = Point(0, 0)
9521 // The offset of the overlay position.
9522 offset: [0, 0],
9523
9524 // @option className: String = ''
9525 // A custom CSS class name to assign to the overlay.
9526 className: '',
9527
9528 // @option pane: String = undefined
9529 // `Map pane` where the overlay will be added.
9530 pane: undefined
9531 },
9532
9533 initialize: function (options, source) {
9534 setOptions(this, options);
9535
9536 this._source = source;
9537 },
9538
9539 // @method openOn(map: Map): this
9540 // Adds the overlay to the map.
9541 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
9542 openOn: function (map) {
9543 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
9544 if (!map.hasLayer(this)) {
9545 map.addLayer(this);
9546 }
9547 return this;
9548 },
9549
9550 // @method close(): this
9551 // Closes the overlay.
9552 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
9553 // and `layer.closePopup()`/`.closeTooltip()`.
9554 close: function () {
9555 if (this._map) {
9556 this._map.removeLayer(this);
9557 }
9558 return this;
9559 },
9560
9561 // @method toggle(layer?: Layer): this
9562 // Opens or closes the overlay bound to layer depending on its current state.
9563 // Argument may be omitted only for overlay bound to layer.
9564 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
9565 toggle: function (layer) {
9566 if (this._map) {
9567 this.close();
9568 } else {
9569 if (arguments.length) {
9570 this._source = layer;
9571 } else {
9572 layer = this._source;
9573 }
9574 this._prepareOpen();
9575
9576 // open the overlay on the map
9577 this.openOn(layer._map);
9578 }
9579 return this;
9580 },
9581
9582 onAdd: function (map) {
9583 this._zoomAnimated = map._zoomAnimated;
9584
9585 if (!this._container) {
9586 this._initLayout();
9587 }
9588
9589 if (map._fadeAnimated) {
9590 setOpacity(this._container, 0);
9591 }
9592
9593 clearTimeout(this._removeTimeout);
9594 this.getPane().appendChild(this._container);
9595 this.update();
9596
9597 if (map._fadeAnimated) {
9598 setOpacity(this._container, 1);
9599 }
9600
9601 this.bringToFront();
9602
9603 if (this.options.interactive) {
9604 addClass(this._container, 'leaflet-interactive');
9605 this.addInteractiveTarget(this._container);
9606 }
9607 },
9608
9609 onRemove: function (map) {
9610 if (map._fadeAnimated) {
9611 setOpacity(this._container, 0);
9612 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9613 } else {
9614 remove(this._container);
9615 }
9616
9617 if (this.options.interactive) {
9618 removeClass(this._container, 'leaflet-interactive');
9619 this.removeInteractiveTarget(this._container);
9620 }
9621 },
9622
9623 // @namespace DivOverlay
9624 // @method getLatLng: LatLng
9625 // Returns the geographical point of the overlay.
9626 getLatLng: function () {
9627 return this._latlng;
9628 },
9629
9630 // @method setLatLng(latlng: LatLng): this
9631 // Sets the geographical point where the overlay will open.
9632 setLatLng: function (latlng) {
9633 this._latlng = toLatLng(latlng);
9634 if (this._map) {
9635 this._updatePosition();
9636 this._adjustPan();
9637 }
9638 return this;
9639 },
9640
9641 // @method getContent: String|HTMLElement
9642 // Returns the content of the overlay.
9643 getContent: function () {
9644 return this._content;
9645 },
9646
9647 // @method setContent(htmlContent: String|HTMLElement|Function): this
9648 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
9649 // The function should return a `String` or `HTMLElement` to be used in the overlay.
9650 setContent: function (content) {
9651 this._content = content;
9652 this.update();
9653 return this;
9654 },
9655
9656 // @method getElement: String|HTMLElement
9657 // Returns the HTML container of the overlay.
9658 getElement: function () {
9659 return this._container;
9660 },
9661
9662 // @method update: null
9663 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
9664 update: function () {
9665 if (!this._map) { return; }
9666
9667 this._container.style.visibility = 'hidden';
9668
9669 this._updateContent();
9670 this._updateLayout();
9671 this._updatePosition();
9672
9673 this._container.style.visibility = '';
9674
9675 this._adjustPan();
9676 },
9677
9678 getEvents: function () {
9679 var events = {
9680 zoom: this._updatePosition,
9681 viewreset: this._updatePosition
9682 };
9683
9684 if (this._zoomAnimated) {
9685 events.zoomanim = this._animateZoom;
9686 }
9687 return events;
9688 },
9689
9690 // @method isOpen: Boolean
9691 // Returns `true` when the overlay is visible on the map.
9692 isOpen: function () {
9693 return !!this._map && this._map.hasLayer(this);
9694 },
9695
9696 // @method bringToFront: this
9697 // Brings this overlay in front of other overlays (in the same map pane).
9698 bringToFront: function () {
9699 if (this._map) {
9700 toFront(this._container);
9701 }
9702 return this;
9703 },
9704
9705 // @method bringToBack: this
9706 // Brings this overlay to the back of other overlays (in the same map pane).
9707 bringToBack: function () {
9708 if (this._map) {
9709 toBack(this._container);
9710 }
9711 return this;
9712 },
9713
9714 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
9715 _prepareOpen: function (latlng) {
9716 var source = this._source;
9717 if (!source._map) { return false; }
9718
9719 if (source instanceof FeatureGroup) {
9720 source = null;
9721 var layers = this._source._layers;
9722 for (var id in layers) {
9723 if (layers[id]._map) {
9724 source = layers[id];
9725 break;
9726 }
9727 }
9728 if (!source) { return false; } // Unable to get source layer.
9729
9730 // set overlay source to this layer
9731 this._source = source;
9732 }
9733
9734 if (!latlng) {
9735 if (source.getCenter) {
9736 latlng = source.getCenter();
9737 } else if (source.getLatLng) {
9738 latlng = source.getLatLng();
9739 } else if (source.getBounds) {
9740 latlng = source.getBounds().getCenter();
9741 } else {
9742 throw new Error('Unable to get source layer LatLng.');
9743 }
9744 }
9745 this.setLatLng(latlng);
9746
9747 if (this._map) {
9748 // update the overlay (content, layout, etc...)
9749 this.update();
9750 }
9751
9752 return true;
9753 },
9754
9755 _updateContent: function () {
9756 if (!this._content) { return; }
9757
9758 var node = this._contentNode;
9759 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9760
9761 if (typeof content === 'string') {
9762 node.innerHTML = content;
9763 } else {
9764 while (node.hasChildNodes()) {
9765 node.removeChild(node.firstChild);
9766 }
9767 node.appendChild(content);
9768 }
9769
9770 // @namespace DivOverlay
9771 // @section DivOverlay events
9772 // @event contentupdate: Event
9773 // Fired when the content of the overlay is updated
9774 this.fire('contentupdate');
9775 },
9776
9777 _updatePosition: function () {
9778 if (!this._map) { return; }
9779
9780 var pos = this._map.latLngToLayerPoint(this._latlng),
9781 offset = toPoint(this.options.offset),
9782 anchor = this._getAnchor();
9783
9784 if (this._zoomAnimated) {
9785 setPosition(this._container, pos.add(anchor));
9786 } else {
9787 offset = offset.add(pos).add(anchor);
9788 }
9789
9790 var bottom = this._containerBottom = -offset.y,
9791 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9792
9793 // bottom position the overlay in case the height of the overlay changes (images loading etc)
9794 this._container.style.bottom = bottom + 'px';
9795 this._container.style.left = left + 'px';
9796 },
9797
9798 _getAnchor: function () {
9799 return [0, 0];
9800 }
9801
9802 });
9803
9804 Map.include({
9805 _initOverlay: function (OverlayClass, content, latlng, options) {
9806 var overlay = content;
9807 if (!(overlay instanceof OverlayClass)) {
9808 overlay = new OverlayClass(options).setContent(content);
9809 }
9810 if (latlng) {
9811 overlay.setLatLng(latlng);
9812 }
9813 return overlay;
9814 }
9815 });
9816
9817
9818 Layer.include({
9819 _initOverlay: function (OverlayClass, old, content, options) {
9820 var overlay = content;
9821 if (overlay instanceof OverlayClass) {
9822 setOptions(overlay, options);
9823 overlay._source = this;
9824 } else {
9825 overlay = (old && !options) ? old : new OverlayClass(options, this);
9826 overlay.setContent(content);
9827 }
9828 return overlay;
9829 }
9830 });
9831
9832 /*
9833 * @class Popup
9834 * @inherits DivOverlay
9835 * @aka L.Popup
9836 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9837 * open popups while making sure that only one popup is open at one time
9838 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9839 *
9840 * @example
9841 *
9842 * If you want to just bind a popup to marker click and then open it, it's really easy:
9843 *
9844 * ```js
9845 * marker.bindPopup(popupContent).openPopup();
9846 * ```
9847 * Path overlays like polylines also have a `bindPopup` method.
9848 * Here's a more complicated way to open a popup on a map:
9849 *
9850 * ```js
9851 * var popup = L.popup()
9852 * .setLatLng(latlng)
9853 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9854 * .openOn(map);
9855 * ```
9856 */
9857
9858
9859 // @namespace Popup
9860 var Popup = DivOverlay.extend({
9861
9862 // @section
9863 // @aka Popup options
9864 options: {
9865 // @option pane: String = 'popupPane'
9866 // `Map pane` where the popup will be added.
9867 pane: 'popupPane',
9868
9869 // @option offset: Point = Point(0, 7)
9870 // The offset of the popup position.
9871 offset: [0, 7],
9872
9873 // @option maxWidth: Number = 300
9874 // Max width of the popup, in pixels.
9875 maxWidth: 300,
9876
9877 // @option minWidth: Number = 50
9878 // Min width of the popup, in pixels.
9879 minWidth: 50,
9880
9881 // @option maxHeight: Number = null
9882 // If set, creates a scrollable container of the given height
9883 // inside a popup if its content exceeds it.
9884 maxHeight: null,
9885
9886 // @option autoPan: Boolean = true
9887 // Set it to `false` if you don't want the map to do panning animation
9888 // to fit the opened popup.
9889 autoPan: true,
9890
9891 // @option autoPanPaddingTopLeft: Point = null
9892 // The margin between the popup and the top left corner of the map
9893 // view after autopanning was performed.
9894 autoPanPaddingTopLeft: null,
9895
9896 // @option autoPanPaddingBottomRight: Point = null
9897 // The margin between the popup and the bottom right corner of the map
9898 // view after autopanning was performed.
9899 autoPanPaddingBottomRight: null,
9900
9901 // @option autoPanPadding: Point = Point(5, 5)
9902 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9903 autoPanPadding: [5, 5],
9904
9905 // @option keepInView: Boolean = false
9906 // Set it to `true` if you want to prevent users from panning the popup
9907 // off of the screen while it is open.
9908 keepInView: false,
9909
9910 // @option closeButton: Boolean = true
9911 // Controls the presence of a close button in the popup.
9912 closeButton: true,
9913
9914 // @option autoClose: Boolean = true
9915 // Set it to `false` if you want to override the default behavior of
9916 // the popup closing when another popup is opened.
9917 autoClose: true,
9918
9919 // @option closeOnEscapeKey: Boolean = true
9920 // Set it to `false` if you want to override the default behavior of
9921 // the ESC key for closing of the popup.
9922 closeOnEscapeKey: true,
9923
9924 // @option closeOnClick: Boolean = *
9925 // Set it if you want to override the default behavior of the popup closing when user clicks
9926 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9927
9928 // @option className: String = ''
9929 // A custom CSS class name to assign to the popup.
9930 className: ''
9931 },
9932
9933 // @namespace Popup
9934 // @method openOn(map: Map): this
9935 // Alternative to `map.openPopup(popup)`.
9936 // Adds the popup to the map and closes the previous one.
9937 openOn: function (map) {
9938 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
9939
9940 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
9941 map.removeLayer(map._popup);
9942 }
9943 map._popup = this;
9944
9945 return DivOverlay.prototype.openOn.call(this, map);
9946 },
9947
9948 onAdd: function (map) {
9949 DivOverlay.prototype.onAdd.call(this, map);
9950
9951 // @namespace Map
9952 // @section Popup events
9953 // @event popupopen: PopupEvent
9954 // Fired when a popup is opened in the map
9955 map.fire('popupopen', {popup: this});
9956
9957 if (this._source) {
9958 // @namespace Layer
9959 // @section Popup events
9960 // @event popupopen: PopupEvent
9961 // Fired when a popup bound to this layer is opened
9962 this._source.fire('popupopen', {popup: this}, true);
9963 // For non-path layers, we toggle the popup when clicking
9964 // again the layer, so prevent the map to reopen it.
9965 if (!(this._source instanceof Path)) {
9966 this._source.on('preclick', stopPropagation);
9967 }
9968 }
9969 },
9970
9971 onRemove: function (map) {
9972 DivOverlay.prototype.onRemove.call(this, map);
9973
9974 // @namespace Map
9975 // @section Popup events
9976 // @event popupclose: PopupEvent
9977 // Fired when a popup in the map is closed
9978 map.fire('popupclose', {popup: this});
9979
9980 if (this._source) {
9981 // @namespace Layer
9982 // @section Popup events
9983 // @event popupclose: PopupEvent
9984 // Fired when a popup bound to this layer is closed
9985 this._source.fire('popupclose', {popup: this}, true);
9986 if (!(this._source instanceof Path)) {
9987 this._source.off('preclick', stopPropagation);
9988 }
9989 }
9990 },
9991
9992 getEvents: function () {
9993 var events = DivOverlay.prototype.getEvents.call(this);
9994
9995 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9996 events.preclick = this.close;
9997 }
9998
9999 if (this.options.keepInView) {
10000 events.moveend = this._adjustPan;
10001 }
10002
10003 return events;
10004 },
10005
10006 _initLayout: function () {
10007 var prefix = 'leaflet-popup',
10008 container = this._container = create$1('div',
10009 prefix + ' ' + (this.options.className || '') +
10010 ' leaflet-zoom-animated');
10011
10012 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
10013 this._contentNode = create$1('div', prefix + '-content', wrapper);
10014
10015 disableClickPropagation(container);
10016 disableScrollPropagation(this._contentNode);
10017 on(container, 'contextmenu', stopPropagation);
10018
10019 this._tipContainer = create$1('div', prefix + '-tip-container', container);
10020 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
10021
10022 if (this.options.closeButton) {
10023 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
10024 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
10025 closeButton.setAttribute('aria-label', 'Close popup');
10026 closeButton.href = '#close';
10027 closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';
10028
10029 on(closeButton, 'click', this.close, this);
10030 }
10031 },
10032
10033 _updateLayout: function () {
10034 var container = this._contentNode,
10035 style = container.style;
10036
10037 style.width = '';
10038 style.whiteSpace = 'nowrap';
10039
10040 var width = container.offsetWidth;
10041 width = Math.min(width, this.options.maxWidth);
10042 width = Math.max(width, this.options.minWidth);
10043
10044 style.width = (width + 1) + 'px';
10045 style.whiteSpace = '';
10046
10047 style.height = '';
10048
10049 var height = container.offsetHeight,
10050 maxHeight = this.options.maxHeight,
10051 scrolledClass = 'leaflet-popup-scrolled';
10052
10053 if (maxHeight && height > maxHeight) {
10054 style.height = maxHeight + 'px';
10055 addClass(container, scrolledClass);
10056 } else {
10057 removeClass(container, scrolledClass);
10058 }
10059
10060 this._containerWidth = this._container.offsetWidth;
10061 },
10062
10063 _animateZoom: function (e) {
10064 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
10065 anchor = this._getAnchor();
10066 setPosition(this._container, pos.add(anchor));
10067 },
10068
10069 _adjustPan: function (e) {
10070 if (!this.options.autoPan) { return; }
10071 if (this._map._panAnim) { this._map._panAnim.stop(); }
10072
10073 var map = this._map,
10074 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
10075 containerHeight = this._container.offsetHeight + marginBottom,
10076 containerWidth = this._containerWidth,
10077 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
10078
10079 layerPos._add(getPosition(this._container));
10080
10081 var containerPos = map.layerPointToContainerPoint(layerPos),
10082 padding = toPoint(this.options.autoPanPadding),
10083 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
10084 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
10085 size = map.getSize(),
10086 dx = 0,
10087 dy = 0;
10088
10089 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
10090 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
10091 }
10092 if (containerPos.x - dx - paddingTL.x < 0) { // left
10093 dx = containerPos.x - paddingTL.x;
10094 }
10095 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
10096 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
10097 }
10098 if (containerPos.y - dy - paddingTL.y < 0) { // top
10099 dy = containerPos.y - paddingTL.y;
10100 }
10101
10102 // @namespace Map
10103 // @section Popup events
10104 // @event autopanstart: Event
10105 // Fired when the map starts autopanning when opening a popup.
10106 if (dx || dy) {
10107 map
10108 .fire('autopanstart')
10109 .panBy([dx, dy], {animate: e && e.type === 'moveend'});
10110 }
10111 },
10112
10113 _getAnchor: function () {
10114 // Where should we anchor the popup on the source layer?
10115 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
10116 }
10117
10118 });
10119
10120 // @namespace Popup
10121 // @factory L.popup(options?: Popup options, source?: Layer)
10122 // 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.
10123 var popup = function (options, source) {
10124 return new Popup(options, source);
10125 };
10126
10127
10128 /* @namespace Map
10129 * @section Interaction Options
10130 * @option closePopupOnClick: Boolean = true
10131 * Set it to `false` if you don't want popups to close when user clicks the map.
10132 */
10133 Map.mergeOptions({
10134 closePopupOnClick: true
10135 });
10136
10137
10138 // @namespace Map
10139 // @section Methods for Layers and Controls
10140 Map.include({
10141 // @method openPopup(popup: Popup): this
10142 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
10143 // @alternative
10144 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
10145 // Creates a popup with the specified content and options and opens it in the given point on a map.
10146 openPopup: function (popup, latlng, options) {
10147 this._initOverlay(Popup, popup, latlng, options)
10148 .openOn(this);
10149
10150 return this;
10151 },
10152
10153 // @method closePopup(popup?: Popup): this
10154 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
10155 closePopup: function (popup) {
10156 popup = arguments.length ? popup : this._popup;
10157 if (popup) {
10158 popup.close();
10159 }
10160 return this;
10161 }
10162 });
10163
10164 /*
10165 * @namespace Layer
10166 * @section Popup methods example
10167 *
10168 * All layers share a set of methods convenient for binding popups to it.
10169 *
10170 * ```js
10171 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10172 * layer.openPopup();
10173 * layer.closePopup();
10174 * ```
10175 *
10176 * 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.
10177 */
10178
10179 // @section Popup methods
10180 Layer.include({
10181
10182 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10183 // Binds a popup to the layer with the passed `content` and sets up the
10184 // necessary event listeners. If a `Function` is passed it will receive
10185 // the layer as the first argument and should return a `String` or `HTMLElement`.
10186 bindPopup: function (content, options) {
10187 this._popup = this._initOverlay(Popup, this._popup, content, options);
10188 if (!this._popupHandlersAdded) {
10189 this.on({
10190 click: this._openPopup,
10191 keypress: this._onKeyPress,
10192 remove: this.closePopup,
10193 move: this._movePopup
10194 });
10195 this._popupHandlersAdded = true;
10196 }
10197
10198 return this;
10199 },
10200
10201 // @method unbindPopup(): this
10202 // Removes the popup previously bound with `bindPopup`.
10203 unbindPopup: function () {
10204 if (this._popup) {
10205 this.off({
10206 click: this._openPopup,
10207 keypress: this._onKeyPress,
10208 remove: this.closePopup,
10209 move: this._movePopup
10210 });
10211 this._popupHandlersAdded = false;
10212 this._popup = null;
10213 }
10214 return this;
10215 },
10216
10217 // @method openPopup(latlng?: LatLng): this
10218 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10219 openPopup: function (latlng) {
10220 if (this._popup && this._popup._prepareOpen(latlng)) {
10221 // open the popup on the map
10222 this._popup.openOn(this._map);
10223 }
10224 return this;
10225 },
10226
10227 // @method closePopup(): this
10228 // Closes the popup bound to this layer if it is open.
10229 closePopup: function () {
10230 if (this._popup) {
10231 this._popup.close();
10232 }
10233 return this;
10234 },
10235
10236 // @method togglePopup(): this
10237 // Opens or closes the popup bound to this layer depending on its current state.
10238 togglePopup: function () {
10239 if (this._popup) {
10240 this._popup.toggle(this);
10241 }
10242 return this;
10243 },
10244
10245 // @method isPopupOpen(): boolean
10246 // Returns `true` if the popup bound to this layer is currently open.
10247 isPopupOpen: function () {
10248 return (this._popup ? this._popup.isOpen() : false);
10249 },
10250
10251 // @method setPopupContent(content: String|HTMLElement|Popup): this
10252 // Sets the content of the popup bound to this layer.
10253 setPopupContent: function (content) {
10254 if (this._popup) {
10255 this._popup.setContent(content);
10256 }
10257 return this;
10258 },
10259
10260 // @method getPopup(): Popup
10261 // Returns the popup bound to this layer.
10262 getPopup: function () {
10263 return this._popup;
10264 },
10265
10266 _openPopup: function (e) {
10267 if (!this._popup || !this._map) {
10268 return;
10269 }
10270 // prevent map click
10271 stop(e);
10272
10273 var target = e.layer || e.target;
10274 if (this._popup._source === target && !(target instanceof Path)) {
10275 // treat it like a marker and figure out
10276 // if we should toggle it open/closed
10277 if (this._map.hasLayer(this._popup)) {
10278 this.closePopup();
10279 } else {
10280 this.openPopup(e.latlng);
10281 }
10282 return;
10283 }
10284 this._popup._source = target;
10285 this.openPopup(e.latlng);
10286 },
10287
10288 _movePopup: function (e) {
10289 this._popup.setLatLng(e.latlng);
10290 },
10291
10292 _onKeyPress: function (e) {
10293 if (e.originalEvent.keyCode === 13) {
10294 this._openPopup(e);
10295 }
10296 }
10297 });
10298
10299 /*
10300 * @class Tooltip
10301 * @inherits DivOverlay
10302 * @aka L.Tooltip
10303 * Used to display small texts on top of map layers.
10304 *
10305 * @example
10306 *
10307 * ```js
10308 * marker.bindTooltip("my tooltip text").openTooltip();
10309 * ```
10310 * Note about tooltip offset. Leaflet takes two options in consideration
10311 * for computing tooltip offsetting:
10312 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10313 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10314 * move it to the bottom. Negatives will move to the left and top.
10315 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10316 * should adapt this value if you use a custom icon.
10317 */
10318
10319
10320 // @namespace Tooltip
10321 var Tooltip = DivOverlay.extend({
10322
10323 // @section
10324 // @aka Tooltip options
10325 options: {
10326 // @option pane: String = 'tooltipPane'
10327 // `Map pane` where the tooltip will be added.
10328 pane: 'tooltipPane',
10329
10330 // @option offset: Point = Point(0, 0)
10331 // Optional offset of the tooltip position.
10332 offset: [0, 0],
10333
10334 // @option direction: String = 'auto'
10335 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10336 // `top`, `bottom`, `center`, `auto`.
10337 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10338 // position on the map.
10339 direction: 'auto',
10340
10341 // @option permanent: Boolean = false
10342 // Whether to open the tooltip permanently or only on mouseover.
10343 permanent: false,
10344
10345 // @option sticky: Boolean = false
10346 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10347 sticky: false,
10348
10349 // @option opacity: Number = 0.9
10350 // Tooltip container opacity.
10351 opacity: 0.9
10352 },
10353
10354 onAdd: function (map) {
10355 DivOverlay.prototype.onAdd.call(this, map);
10356 this.setOpacity(this.options.opacity);
10357
10358 // @namespace Map
10359 // @section Tooltip events
10360 // @event tooltipopen: TooltipEvent
10361 // Fired when a tooltip is opened in the map.
10362 map.fire('tooltipopen', {tooltip: this});
10363
10364 if (this._source) {
10365 this.addEventParent(this._source);
10366
10367 // @namespace Layer
10368 // @section Tooltip events
10369 // @event tooltipopen: TooltipEvent
10370 // Fired when a tooltip bound to this layer is opened.
10371 this._source.fire('tooltipopen', {tooltip: this}, true);
10372 }
10373 },
10374
10375 onRemove: function (map) {
10376 DivOverlay.prototype.onRemove.call(this, map);
10377
10378 // @namespace Map
10379 // @section Tooltip events
10380 // @event tooltipclose: TooltipEvent
10381 // Fired when a tooltip in the map is closed.
10382 map.fire('tooltipclose', {tooltip: this});
10383
10384 if (this._source) {
10385 this.removeEventParent(this._source);
10386
10387 // @namespace Layer
10388 // @section Tooltip events
10389 // @event tooltipclose: TooltipEvent
10390 // Fired when a tooltip bound to this layer is closed.
10391 this._source.fire('tooltipclose', {tooltip: this}, true);
10392 }
10393 },
10394
10395 getEvents: function () {
10396 var events = DivOverlay.prototype.getEvents.call(this);
10397
10398 if (!this.options.permanent) {
10399 events.preclick = this.close;
10400 }
10401
10402 return events;
10403 },
10404
10405 _initLayout: function () {
10406 var prefix = 'leaflet-tooltip',
10407 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10408
10409 this._contentNode = this._container = create$1('div', className);
10410 },
10411
10412 _updateLayout: function () {},
10413
10414 _adjustPan: function () {},
10415
10416 _setPosition: function (pos) {
10417 var subX, subY,
10418 map = this._map,
10419 container = this._container,
10420 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10421 tooltipPoint = map.layerPointToContainerPoint(pos),
10422 direction = this.options.direction,
10423 tooltipWidth = container.offsetWidth,
10424 tooltipHeight = container.offsetHeight,
10425 offset = toPoint(this.options.offset),
10426 anchor = this._getAnchor();
10427
10428 if (direction === 'top') {
10429 subX = tooltipWidth / 2;
10430 subY = tooltipHeight;
10431 } else if (direction === 'bottom') {
10432 subX = tooltipWidth / 2;
10433 subY = 0;
10434 } else if (direction === 'center') {
10435 subX = tooltipWidth / 2;
10436 subY = tooltipHeight / 2;
10437 } else if (direction === 'right') {
10438 subX = 0;
10439 subY = tooltipHeight / 2;
10440 } else if (direction === 'left') {
10441 subX = tooltipWidth;
10442 subY = tooltipHeight / 2;
10443 } else if (tooltipPoint.x < centerPoint.x) {
10444 direction = 'right';
10445 subX = 0;
10446 subY = tooltipHeight / 2;
10447 } else {
10448 direction = 'left';
10449 subX = tooltipWidth + (offset.x + anchor.x) * 2;
10450 subY = tooltipHeight / 2;
10451 }
10452
10453 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10454
10455 removeClass(container, 'leaflet-tooltip-right');
10456 removeClass(container, 'leaflet-tooltip-left');
10457 removeClass(container, 'leaflet-tooltip-top');
10458 removeClass(container, 'leaflet-tooltip-bottom');
10459 addClass(container, 'leaflet-tooltip-' + direction);
10460 setPosition(container, pos);
10461 },
10462
10463 _updatePosition: function () {
10464 var pos = this._map.latLngToLayerPoint(this._latlng);
10465 this._setPosition(pos);
10466 },
10467
10468 setOpacity: function (opacity) {
10469 this.options.opacity = opacity;
10470
10471 if (this._container) {
10472 setOpacity(this._container, opacity);
10473 }
10474 },
10475
10476 _animateZoom: function (e) {
10477 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10478 this._setPosition(pos);
10479 },
10480
10481 _getAnchor: function () {
10482 // Where should we anchor the tooltip on the source layer?
10483 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10484 }
10485
10486 });
10487
10488 // @namespace Tooltip
10489 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10490 // 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.
10491 var tooltip = function (options, source) {
10492 return new Tooltip(options, source);
10493 };
10494
10495 // @namespace Map
10496 // @section Methods for Layers and Controls
10497 Map.include({
10498
10499 // @method openTooltip(tooltip: Tooltip): this
10500 // Opens the specified tooltip.
10501 // @alternative
10502 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10503 // Creates a tooltip with the specified content and options and open it.
10504 openTooltip: function (tooltip, latlng, options) {
10505 this._initOverlay(Tooltip, tooltip, latlng, options)
10506 .openOn(this);
10507
10508 return this;
10509 },
10510
10511 // @method closeTooltip(tooltip: Tooltip): this
10512 // Closes the tooltip given as parameter.
10513 closeTooltip: function (tooltip) {
10514 tooltip.close();
10515 return this;
10516 }
10517
10518 });
10519
10520 /*
10521 * @namespace Layer
10522 * @section Tooltip methods example
10523 *
10524 * All layers share a set of methods convenient for binding tooltips to it.
10525 *
10526 * ```js
10527 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10528 * layer.openTooltip();
10529 * layer.closeTooltip();
10530 * ```
10531 */
10532
10533 // @section Tooltip methods
10534 Layer.include({
10535
10536 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10537 // Binds a tooltip to the layer with the passed `content` and sets up the
10538 // necessary event listeners. If a `Function` is passed it will receive
10539 // the layer as the first argument and should return a `String` or `HTMLElement`.
10540 bindTooltip: function (content, options) {
10541
10542 if (this._tooltip && this.isTooltipOpen()) {
10543 this.unbindTooltip();
10544 }
10545
10546 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
10547 this._initTooltipInteractions();
10548
10549 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10550 this.openTooltip();
10551 }
10552
10553 return this;
10554 },
10555
10556 // @method unbindTooltip(): this
10557 // Removes the tooltip previously bound with `bindTooltip`.
10558 unbindTooltip: function () {
10559 if (this._tooltip) {
10560 this._initTooltipInteractions(true);
10561 this.closeTooltip();
10562 this._tooltip = null;
10563 }
10564 return this;
10565 },
10566
10567 _initTooltipInteractions: function (remove) {
10568 if (!remove && this._tooltipHandlersAdded) { return; }
10569 var onOff = remove ? 'off' : 'on',
10570 events = {
10571 remove: this.closeTooltip,
10572 move: this._moveTooltip
10573 };
10574 if (!this._tooltip.options.permanent) {
10575 events.mouseover = this._openTooltip;
10576 events.mouseout = this.closeTooltip;
10577 events.click = this._openTooltip;
10578 } else {
10579 events.add = this._openTooltip;
10580 }
10581 if (this._tooltip.options.sticky) {
10582 events.mousemove = this._moveTooltip;
10583 }
10584 this[onOff](events);
10585 this._tooltipHandlersAdded = !remove;
10586 },
10587
10588 // @method openTooltip(latlng?: LatLng): this
10589 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10590 openTooltip: function (latlng) {
10591 if (this._tooltip && this._tooltip._prepareOpen(latlng)) {
10592 // open the tooltip on the map
10593 this._tooltip.openOn(this._map);
10594 }
10595 return this;
10596 },
10597
10598 // @method closeTooltip(): this
10599 // Closes the tooltip bound to this layer if it is open.
10600 closeTooltip: function () {
10601 if (this._tooltip) {
10602 return this._tooltip.close();
10603 }
10604 },
10605
10606 // @method toggleTooltip(): this
10607 // Opens or closes the tooltip bound to this layer depending on its current state.
10608 toggleTooltip: function () {
10609 if (this._tooltip) {
10610 this._tooltip.toggle(this);
10611 }
10612 return this;
10613 },
10614
10615 // @method isTooltipOpen(): boolean
10616 // Returns `true` if the tooltip bound to this layer is currently open.
10617 isTooltipOpen: function () {
10618 return this._tooltip.isOpen();
10619 },
10620
10621 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10622 // Sets the content of the tooltip bound to this layer.
10623 setTooltipContent: function (content) {
10624 if (this._tooltip) {
10625 this._tooltip.setContent(content);
10626 }
10627 return this;
10628 },
10629
10630 // @method getTooltip(): Tooltip
10631 // Returns the tooltip bound to this layer.
10632 getTooltip: function () {
10633 return this._tooltip;
10634 },
10635
10636 _openTooltip: function (e) {
10637 if (!this._tooltip || !this._map || (this._map.dragging && this._map.dragging.moving())) {
10638 return;
10639 }
10640 this._tooltip._source = e.layer || e.target;
10641
10642 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
10643 },
10644
10645 _moveTooltip: function (e) {
10646 var latlng = e.latlng, containerPoint, layerPoint;
10647 if (this._tooltip.options.sticky && e.originalEvent) {
10648 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10649 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10650 latlng = this._map.layerPointToLatLng(layerPoint);
10651 }
10652 this._tooltip.setLatLng(latlng);
10653 }
10654 });
10655
10656 /*
10657 * @class DivIcon
10658 * @aka L.DivIcon
10659 * @inherits Icon
10660 *
10661 * Represents a lightweight icon for markers that uses a simple `<div>`
10662 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10663 *
10664 * @example
10665 * ```js
10666 * var myIcon = L.divIcon({className: 'my-div-icon'});
10667 * // you can set .my-div-icon styles in CSS
10668 *
10669 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10670 * ```
10671 *
10672 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10673 */
10674
10675 var DivIcon = Icon.extend({
10676 options: {
10677 // @section
10678 // @aka DivIcon options
10679 iconSize: [12, 12], // also can be set through CSS
10680
10681 // iconAnchor: (Point),
10682 // popupAnchor: (Point),
10683
10684 // @option html: String|HTMLElement = ''
10685 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10686 // an instance of `HTMLElement`.
10687 html: false,
10688
10689 // @option bgPos: Point = [0, 0]
10690 // Optional relative position of the background, in pixels
10691 bgPos: null,
10692
10693 className: 'leaflet-div-icon'
10694 },
10695
10696 createIcon: function (oldIcon) {
10697 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10698 options = this.options;
10699
10700 if (options.html instanceof Element) {
10701 empty(div);
10702 div.appendChild(options.html);
10703 } else {
10704 div.innerHTML = options.html !== false ? options.html : '';
10705 }
10706
10707 if (options.bgPos) {
10708 var bgPos = toPoint(options.bgPos);
10709 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10710 }
10711 this._setIconStyles(div, 'icon');
10712
10713 return div;
10714 },
10715
10716 createShadow: function () {
10717 return null;
10718 }
10719 });
10720
10721 // @factory L.divIcon(options: DivIcon options)
10722 // Creates a `DivIcon` instance with the given options.
10723 function divIcon(options) {
10724 return new DivIcon(options);
10725 }
10726
10727 Icon.Default = IconDefault;
10728
10729 /*
10730 * @class GridLayer
10731 * @inherits Layer
10732 * @aka L.GridLayer
10733 *
10734 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10735 * 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.
10736 *
10737 *
10738 * @section Synchronous usage
10739 * @example
10740 *
10741 * 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.
10742 *
10743 * ```js
10744 * var CanvasLayer = L.GridLayer.extend({
10745 * createTile: function(coords){
10746 * // create a <canvas> element for drawing
10747 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10748 *
10749 * // setup tile width and height according to the options
10750 * var size = this.getTileSize();
10751 * tile.width = size.x;
10752 * tile.height = size.y;
10753 *
10754 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10755 * var ctx = tile.getContext('2d');
10756 *
10757 * // return the tile so it can be rendered on screen
10758 * return tile;
10759 * }
10760 * });
10761 * ```
10762 *
10763 * @section Asynchronous usage
10764 * @example
10765 *
10766 * 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.
10767 *
10768 * ```js
10769 * var CanvasLayer = L.GridLayer.extend({
10770 * createTile: function(coords, done){
10771 * var error;
10772 *
10773 * // create a <canvas> element for drawing
10774 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10775 *
10776 * // setup tile width and height according to the options
10777 * var size = this.getTileSize();
10778 * tile.width = size.x;
10779 * tile.height = size.y;
10780 *
10781 * // draw something asynchronously and pass the tile to the done() callback
10782 * setTimeout(function() {
10783 * done(error, tile);
10784 * }, 1000);
10785 *
10786 * return tile;
10787 * }
10788 * });
10789 * ```
10790 *
10791 * @section
10792 */
10793
10794
10795 var GridLayer = Layer.extend({
10796
10797 // @section
10798 // @aka GridLayer options
10799 options: {
10800 // @option tileSize: Number|Point = 256
10801 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10802 tileSize: 256,
10803
10804 // @option opacity: Number = 1.0
10805 // Opacity of the tiles. Can be used in the `createTile()` function.
10806 opacity: 1,
10807
10808 // @option updateWhenIdle: Boolean = (depends)
10809 // Load new tiles only when panning ends.
10810 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10811 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10812 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10813 updateWhenIdle: Browser.mobile,
10814
10815 // @option updateWhenZooming: Boolean = true
10816 // 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.
10817 updateWhenZooming: true,
10818
10819 // @option updateInterval: Number = 200
10820 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10821 updateInterval: 200,
10822
10823 // @option zIndex: Number = 1
10824 // The explicit zIndex of the tile layer.
10825 zIndex: 1,
10826
10827 // @option bounds: LatLngBounds = undefined
10828 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10829 bounds: null,
10830
10831 // @option minZoom: Number = 0
10832 // The minimum zoom level down to which this layer will be displayed (inclusive).
10833 minZoom: 0,
10834
10835 // @option maxZoom: Number = undefined
10836 // The maximum zoom level up to which this layer will be displayed (inclusive).
10837 maxZoom: undefined,
10838
10839 // @option maxNativeZoom: Number = undefined
10840 // Maximum zoom number the tile source has available. If it is specified,
10841 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10842 // from `maxNativeZoom` level and auto-scaled.
10843 maxNativeZoom: undefined,
10844
10845 // @option minNativeZoom: Number = undefined
10846 // Minimum zoom number the tile source has available. If it is specified,
10847 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10848 // from `minNativeZoom` level and auto-scaled.
10849 minNativeZoom: undefined,
10850
10851 // @option noWrap: Boolean = false
10852 // Whether the layer is wrapped around the antimeridian. If `true`, the
10853 // GridLayer will only be displayed once at low zoom levels. Has no
10854 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10855 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10856 // tiles outside the CRS limits.
10857 noWrap: false,
10858
10859 // @option pane: String = 'tilePane'
10860 // `Map pane` where the grid layer will be added.
10861 pane: 'tilePane',
10862
10863 // @option className: String = ''
10864 // A custom class name to assign to the tile layer. Empty by default.
10865 className: '',
10866
10867 // @option keepBuffer: Number = 2
10868 // When panning the map, keep this many rows and columns of tiles before unloading them.
10869 keepBuffer: 2
10870 },
10871
10872 initialize: function (options) {
10873 setOptions(this, options);
10874 },
10875
10876 onAdd: function () {
10877 this._initContainer();
10878
10879 this._levels = {};
10880 this._tiles = {};
10881
10882 this._resetView(); // implicit _update() call
10883 },
10884
10885 beforeAdd: function (map) {
10886 map._addZoomLimit(this);
10887 },
10888
10889 onRemove: function (map) {
10890 this._removeAllTiles();
10891 remove(this._container);
10892 map._removeZoomLimit(this);
10893 this._container = null;
10894 this._tileZoom = undefined;
10895 },
10896
10897 // @method bringToFront: this
10898 // Brings the tile layer to the top of all tile layers.
10899 bringToFront: function () {
10900 if (this._map) {
10901 toFront(this._container);
10902 this._setAutoZIndex(Math.max);
10903 }
10904 return this;
10905 },
10906
10907 // @method bringToBack: this
10908 // Brings the tile layer to the bottom of all tile layers.
10909 bringToBack: function () {
10910 if (this._map) {
10911 toBack(this._container);
10912 this._setAutoZIndex(Math.min);
10913 }
10914 return this;
10915 },
10916
10917 // @method getContainer: HTMLElement
10918 // Returns the HTML element that contains the tiles for this layer.
10919 getContainer: function () {
10920 return this._container;
10921 },
10922
10923 // @method setOpacity(opacity: Number): this
10924 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10925 setOpacity: function (opacity) {
10926 this.options.opacity = opacity;
10927 this._updateOpacity();
10928 return this;
10929 },
10930
10931 // @method setZIndex(zIndex: Number): this
10932 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10933 setZIndex: function (zIndex) {
10934 this.options.zIndex = zIndex;
10935 this._updateZIndex();
10936
10937 return this;
10938 },
10939
10940 // @method isLoading: Boolean
10941 // Returns `true` if any tile in the grid layer has not finished loading.
10942 isLoading: function () {
10943 return this._loading;
10944 },
10945
10946 // @method redraw: this
10947 // Causes the layer to clear all the tiles and request them again.
10948 redraw: function () {
10949 if (this._map) {
10950 this._removeAllTiles();
10951 var tileZoom = this._clampZoom(this._map.getZoom());
10952 if (tileZoom !== this._tileZoom) {
10953 this._tileZoom = tileZoom;
10954 this._updateLevels();
10955 }
10956 this._update();
10957 }
10958 return this;
10959 },
10960
10961 getEvents: function () {
10962 var events = {
10963 viewprereset: this._invalidateAll,
10964 viewreset: this._resetView,
10965 zoom: this._resetView,
10966 moveend: this._onMoveEnd
10967 };
10968
10969 if (!this.options.updateWhenIdle) {
10970 // update tiles on move, but not more often than once per given interval
10971 if (!this._onMove) {
10972 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10973 }
10974
10975 events.move = this._onMove;
10976 }
10977
10978 if (this._zoomAnimated) {
10979 events.zoomanim = this._animateZoom;
10980 }
10981
10982 return events;
10983 },
10984
10985 // @section Extension methods
10986 // Layers extending `GridLayer` shall reimplement the following method.
10987 // @method createTile(coords: Object, done?: Function): HTMLElement
10988 // Called only internally, must be overridden by classes extending `GridLayer`.
10989 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10990 // is specified, it must be called when the tile has finished loading and drawing.
10991 createTile: function () {
10992 return document.createElement('div');
10993 },
10994
10995 // @section
10996 // @method getTileSize: Point
10997 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10998 getTileSize: function () {
10999 var s = this.options.tileSize;
11000 return s instanceof Point ? s : new Point(s, s);
11001 },
11002
11003 _updateZIndex: function () {
11004 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
11005 this._container.style.zIndex = this.options.zIndex;
11006 }
11007 },
11008
11009 _setAutoZIndex: function (compare) {
11010 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
11011
11012 var layers = this.getPane().children,
11013 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
11014
11015 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
11016
11017 zIndex = layers[i].style.zIndex;
11018
11019 if (layers[i] !== this._container && zIndex) {
11020 edgeZIndex = compare(edgeZIndex, +zIndex);
11021 }
11022 }
11023
11024 if (isFinite(edgeZIndex)) {
11025 this.options.zIndex = edgeZIndex + compare(-1, 1);
11026 this._updateZIndex();
11027 }
11028 },
11029
11030 _updateOpacity: function () {
11031 if (!this._map) { return; }
11032
11033 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
11034 if (Browser.ielt9) { return; }
11035
11036 setOpacity(this._container, this.options.opacity);
11037
11038 var now = +new Date(),
11039 nextFrame = false,
11040 willPrune = false;
11041
11042 for (var key in this._tiles) {
11043 var tile = this._tiles[key];
11044 if (!tile.current || !tile.loaded) { continue; }
11045
11046 var fade = Math.min(1, (now - tile.loaded) / 200);
11047
11048 setOpacity(tile.el, fade);
11049 if (fade < 1) {
11050 nextFrame = true;
11051 } else {
11052 if (tile.active) {
11053 willPrune = true;
11054 } else {
11055 this._onOpaqueTile(tile);
11056 }
11057 tile.active = true;
11058 }
11059 }
11060
11061 if (willPrune && !this._noPrune) { this._pruneTiles(); }
11062
11063 if (nextFrame) {
11064 cancelAnimFrame(this._fadeFrame);
11065 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11066 }
11067 },
11068
11069 _onOpaqueTile: falseFn,
11070
11071 _initContainer: function () {
11072 if (this._container) { return; }
11073
11074 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11075 this._updateZIndex();
11076
11077 if (this.options.opacity < 1) {
11078 this._updateOpacity();
11079 }
11080
11081 this.getPane().appendChild(this._container);
11082 },
11083
11084 _updateLevels: function () {
11085
11086 var zoom = this._tileZoom,
11087 maxZoom = this.options.maxZoom;
11088
11089 if (zoom === undefined) { return undefined; }
11090
11091 for (var z in this._levels) {
11092 z = Number(z);
11093 if (this._levels[z].el.children.length || z === zoom) {
11094 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11095 this._onUpdateLevel(z);
11096 } else {
11097 remove(this._levels[z].el);
11098 this._removeTilesAtZoom(z);
11099 this._onRemoveLevel(z);
11100 delete this._levels[z];
11101 }
11102 }
11103
11104 var level = this._levels[zoom],
11105 map = this._map;
11106
11107 if (!level) {
11108 level = this._levels[zoom] = {};
11109
11110 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11111 level.el.style.zIndex = maxZoom;
11112
11113 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11114 level.zoom = zoom;
11115
11116 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11117
11118 // force the browser to consider the newly added element for transition
11119 falseFn(level.el.offsetWidth);
11120
11121 this._onCreateLevel(level);
11122 }
11123
11124 this._level = level;
11125
11126 return level;
11127 },
11128
11129 _onUpdateLevel: falseFn,
11130
11131 _onRemoveLevel: falseFn,
11132
11133 _onCreateLevel: falseFn,
11134
11135 _pruneTiles: function () {
11136 if (!this._map) {
11137 return;
11138 }
11139
11140 var key, tile;
11141
11142 var zoom = this._map.getZoom();
11143 if (zoom > this.options.maxZoom ||
11144 zoom < this.options.minZoom) {
11145 this._removeAllTiles();
11146 return;
11147 }
11148
11149 for (key in this._tiles) {
11150 tile = this._tiles[key];
11151 tile.retain = tile.current;
11152 }
11153
11154 for (key in this._tiles) {
11155 tile = this._tiles[key];
11156 if (tile.current && !tile.active) {
11157 var coords = tile.coords;
11158 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11159 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11160 }
11161 }
11162 }
11163
11164 for (key in this._tiles) {
11165 if (!this._tiles[key].retain) {
11166 this._removeTile(key);
11167 }
11168 }
11169 },
11170
11171 _removeTilesAtZoom: function (zoom) {
11172 for (var key in this._tiles) {
11173 if (this._tiles[key].coords.z !== zoom) {
11174 continue;
11175 }
11176 this._removeTile(key);
11177 }
11178 },
11179
11180 _removeAllTiles: function () {
11181 for (var key in this._tiles) {
11182 this._removeTile(key);
11183 }
11184 },
11185
11186 _invalidateAll: function () {
11187 for (var z in this._levels) {
11188 remove(this._levels[z].el);
11189 this._onRemoveLevel(Number(z));
11190 delete this._levels[z];
11191 }
11192 this._removeAllTiles();
11193
11194 this._tileZoom = undefined;
11195 },
11196
11197 _retainParent: function (x, y, z, minZoom) {
11198 var x2 = Math.floor(x / 2),
11199 y2 = Math.floor(y / 2),
11200 z2 = z - 1,
11201 coords2 = new Point(+x2, +y2);
11202 coords2.z = +z2;
11203
11204 var key = this._tileCoordsToKey(coords2),
11205 tile = this._tiles[key];
11206
11207 if (tile && tile.active) {
11208 tile.retain = true;
11209 return true;
11210
11211 } else if (tile && tile.loaded) {
11212 tile.retain = true;
11213 }
11214
11215 if (z2 > minZoom) {
11216 return this._retainParent(x2, y2, z2, minZoom);
11217 }
11218
11219 return false;
11220 },
11221
11222 _retainChildren: function (x, y, z, maxZoom) {
11223
11224 for (var i = 2 * x; i < 2 * x + 2; i++) {
11225 for (var j = 2 * y; j < 2 * y + 2; j++) {
11226
11227 var coords = new Point(i, j);
11228 coords.z = z + 1;
11229
11230 var key = this._tileCoordsToKey(coords),
11231 tile = this._tiles[key];
11232
11233 if (tile && tile.active) {
11234 tile.retain = true;
11235 continue;
11236
11237 } else if (tile && tile.loaded) {
11238 tile.retain = true;
11239 }
11240
11241 if (z + 1 < maxZoom) {
11242 this._retainChildren(i, j, z + 1, maxZoom);
11243 }
11244 }
11245 }
11246 },
11247
11248 _resetView: function (e) {
11249 var animating = e && (e.pinch || e.flyTo);
11250 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11251 },
11252
11253 _animateZoom: function (e) {
11254 this._setView(e.center, e.zoom, true, e.noUpdate);
11255 },
11256
11257 _clampZoom: function (zoom) {
11258 var options = this.options;
11259
11260 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11261 return options.minNativeZoom;
11262 }
11263
11264 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11265 return options.maxNativeZoom;
11266 }
11267
11268 return zoom;
11269 },
11270
11271 _setView: function (center, zoom, noPrune, noUpdate) {
11272 var tileZoom = Math.round(zoom);
11273 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11274 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11275 tileZoom = undefined;
11276 } else {
11277 tileZoom = this._clampZoom(tileZoom);
11278 }
11279
11280 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11281
11282 if (!noUpdate || tileZoomChanged) {
11283
11284 this._tileZoom = tileZoom;
11285
11286 if (this._abortLoading) {
11287 this._abortLoading();
11288 }
11289
11290 this._updateLevels();
11291 this._resetGrid();
11292
11293 if (tileZoom !== undefined) {
11294 this._update(center);
11295 }
11296
11297 if (!noPrune) {
11298 this._pruneTiles();
11299 }
11300
11301 // Flag to prevent _updateOpacity from pruning tiles during
11302 // a zoom anim or a pinch gesture
11303 this._noPrune = !!noPrune;
11304 }
11305
11306 this._setZoomTransforms(center, zoom);
11307 },
11308
11309 _setZoomTransforms: function (center, zoom) {
11310 for (var i in this._levels) {
11311 this._setZoomTransform(this._levels[i], center, zoom);
11312 }
11313 },
11314
11315 _setZoomTransform: function (level, center, zoom) {
11316 var scale = this._map.getZoomScale(zoom, level.zoom),
11317 translate = level.origin.multiplyBy(scale)
11318 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11319
11320 if (Browser.any3d) {
11321 setTransform(level.el, translate, scale);
11322 } else {
11323 setPosition(level.el, translate);
11324 }
11325 },
11326
11327 _resetGrid: function () {
11328 var map = this._map,
11329 crs = map.options.crs,
11330 tileSize = this._tileSize = this.getTileSize(),
11331 tileZoom = this._tileZoom;
11332
11333 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11334 if (bounds) {
11335 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11336 }
11337
11338 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11339 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11340 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11341 ];
11342 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11343 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11344 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11345 ];
11346 },
11347
11348 _onMoveEnd: function () {
11349 if (!this._map || this._map._animatingZoom) { return; }
11350
11351 this._update();
11352 },
11353
11354 _getTiledPixelBounds: function (center) {
11355 var map = this._map,
11356 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11357 scale = map.getZoomScale(mapZoom, this._tileZoom),
11358 pixelCenter = map.project(center, this._tileZoom).floor(),
11359 halfSize = map.getSize().divideBy(scale * 2);
11360
11361 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11362 },
11363
11364 // Private method to load tiles in the grid's active zoom level according to map bounds
11365 _update: function (center) {
11366 var map = this._map;
11367 if (!map) { return; }
11368 var zoom = this._clampZoom(map.getZoom());
11369
11370 if (center === undefined) { center = map.getCenter(); }
11371 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11372
11373 var pixelBounds = this._getTiledPixelBounds(center),
11374 tileRange = this._pxBoundsToTileRange(pixelBounds),
11375 tileCenter = tileRange.getCenter(),
11376 queue = [],
11377 margin = this.options.keepBuffer,
11378 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11379 tileRange.getTopRight().add([margin, -margin]));
11380
11381 // Sanity check: panic if the tile range contains Infinity somewhere.
11382 if (!(isFinite(tileRange.min.x) &&
11383 isFinite(tileRange.min.y) &&
11384 isFinite(tileRange.max.x) &&
11385 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11386
11387 for (var key in this._tiles) {
11388 var c = this._tiles[key].coords;
11389 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11390 this._tiles[key].current = false;
11391 }
11392 }
11393
11394 // _update just loads more tiles. If the tile zoom level differs too much
11395 // from the map's, let _setView reset levels and prune old tiles.
11396 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11397
11398 // create a queue of coordinates to load tiles from
11399 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11400 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11401 var coords = new Point(i, j);
11402 coords.z = this._tileZoom;
11403
11404 if (!this._isValidTile(coords)) { continue; }
11405
11406 var tile = this._tiles[this._tileCoordsToKey(coords)];
11407 if (tile) {
11408 tile.current = true;
11409 } else {
11410 queue.push(coords);
11411 }
11412 }
11413 }
11414
11415 // sort tile queue to load tiles in order of their distance to center
11416 queue.sort(function (a, b) {
11417 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11418 });
11419
11420 if (queue.length !== 0) {
11421 // if it's the first batch of tiles to load
11422 if (!this._loading) {
11423 this._loading = true;
11424 // @event loading: Event
11425 // Fired when the grid layer starts loading tiles.
11426 this.fire('loading');
11427 }
11428
11429 // create DOM fragment to append tiles in one batch
11430 var fragment = document.createDocumentFragment();
11431
11432 for (i = 0; i < queue.length; i++) {
11433 this._addTile(queue[i], fragment);
11434 }
11435
11436 this._level.el.appendChild(fragment);
11437 }
11438 },
11439
11440 _isValidTile: function (coords) {
11441 var crs = this._map.options.crs;
11442
11443 if (!crs.infinite) {
11444 // don't load tile if it's out of bounds and not wrapped
11445 var bounds = this._globalTileRange;
11446 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11447 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11448 }
11449
11450 if (!this.options.bounds) { return true; }
11451
11452 // don't load tile if it doesn't intersect the bounds in options
11453 var tileBounds = this._tileCoordsToBounds(coords);
11454 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11455 },
11456
11457 _keyToBounds: function (key) {
11458 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11459 },
11460
11461 _tileCoordsToNwSe: function (coords) {
11462 var map = this._map,
11463 tileSize = this.getTileSize(),
11464 nwPoint = coords.scaleBy(tileSize),
11465 sePoint = nwPoint.add(tileSize),
11466 nw = map.unproject(nwPoint, coords.z),
11467 se = map.unproject(sePoint, coords.z);
11468 return [nw, se];
11469 },
11470
11471 // converts tile coordinates to its geographical bounds
11472 _tileCoordsToBounds: function (coords) {
11473 var bp = this._tileCoordsToNwSe(coords),
11474 bounds = new LatLngBounds(bp[0], bp[1]);
11475
11476 if (!this.options.noWrap) {
11477 bounds = this._map.wrapLatLngBounds(bounds);
11478 }
11479 return bounds;
11480 },
11481 // converts tile coordinates to key for the tile cache
11482 _tileCoordsToKey: function (coords) {
11483 return coords.x + ':' + coords.y + ':' + coords.z;
11484 },
11485
11486 // converts tile cache key to coordinates
11487 _keyToTileCoords: function (key) {
11488 var k = key.split(':'),
11489 coords = new Point(+k[0], +k[1]);
11490 coords.z = +k[2];
11491 return coords;
11492 },
11493
11494 _removeTile: function (key) {
11495 var tile = this._tiles[key];
11496 if (!tile) { return; }
11497
11498 remove(tile.el);
11499
11500 delete this._tiles[key];
11501
11502 // @event tileunload: TileEvent
11503 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11504 this.fire('tileunload', {
11505 tile: tile.el,
11506 coords: this._keyToTileCoords(key)
11507 });
11508 },
11509
11510 _initTile: function (tile) {
11511 addClass(tile, 'leaflet-tile');
11512
11513 var tileSize = this.getTileSize();
11514 tile.style.width = tileSize.x + 'px';
11515 tile.style.height = tileSize.y + 'px';
11516
11517 tile.onselectstart = falseFn;
11518 tile.onmousemove = falseFn;
11519
11520 // update opacity on tiles in IE7-8 because of filter inheritance problems
11521 if (Browser.ielt9 && this.options.opacity < 1) {
11522 setOpacity(tile, this.options.opacity);
11523 }
11524 },
11525
11526 _addTile: function (coords, container) {
11527 var tilePos = this._getTilePos(coords),
11528 key = this._tileCoordsToKey(coords);
11529
11530 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11531
11532 this._initTile(tile);
11533
11534 // if createTile is defined with a second argument ("done" callback),
11535 // we know that tile is async and will be ready later; otherwise
11536 if (this.createTile.length < 2) {
11537 // mark tile as ready, but delay one frame for opacity animation to happen
11538 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11539 }
11540
11541 setPosition(tile, tilePos);
11542
11543 // save tile in cache
11544 this._tiles[key] = {
11545 el: tile,
11546 coords: coords,
11547 current: true
11548 };
11549
11550 container.appendChild(tile);
11551 // @event tileloadstart: TileEvent
11552 // Fired when a tile is requested and starts loading.
11553 this.fire('tileloadstart', {
11554 tile: tile,
11555 coords: coords
11556 });
11557 },
11558
11559 _tileReady: function (coords, err, tile) {
11560 if (err) {
11561 // @event tileerror: TileErrorEvent
11562 // Fired when there is an error loading a tile.
11563 this.fire('tileerror', {
11564 error: err,
11565 tile: tile,
11566 coords: coords
11567 });
11568 }
11569
11570 var key = this._tileCoordsToKey(coords);
11571
11572 tile = this._tiles[key];
11573 if (!tile) { return; }
11574
11575 tile.loaded = +new Date();
11576 if (this._map._fadeAnimated) {
11577 setOpacity(tile.el, 0);
11578 cancelAnimFrame(this._fadeFrame);
11579 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11580 } else {
11581 tile.active = true;
11582 this._pruneTiles();
11583 }
11584
11585 if (!err) {
11586 addClass(tile.el, 'leaflet-tile-loaded');
11587
11588 // @event tileload: TileEvent
11589 // Fired when a tile loads.
11590 this.fire('tileload', {
11591 tile: tile.el,
11592 coords: coords
11593 });
11594 }
11595
11596 if (this._noTilesToLoad()) {
11597 this._loading = false;
11598 // @event load: Event
11599 // Fired when the grid layer loaded all visible tiles.
11600 this.fire('load');
11601
11602 if (Browser.ielt9 || !this._map._fadeAnimated) {
11603 requestAnimFrame(this._pruneTiles, this);
11604 } else {
11605 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11606 // to trigger a pruning.
11607 setTimeout(bind(this._pruneTiles, this), 250);
11608 }
11609 }
11610 },
11611
11612 _getTilePos: function (coords) {
11613 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11614 },
11615
11616 _wrapCoords: function (coords) {
11617 var newCoords = new Point(
11618 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11619 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11620 newCoords.z = coords.z;
11621 return newCoords;
11622 },
11623
11624 _pxBoundsToTileRange: function (bounds) {
11625 var tileSize = this.getTileSize();
11626 return new Bounds(
11627 bounds.min.unscaleBy(tileSize).floor(),
11628 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11629 },
11630
11631 _noTilesToLoad: function () {
11632 for (var key in this._tiles) {
11633 if (!this._tiles[key].loaded) { return false; }
11634 }
11635 return true;
11636 }
11637 });
11638
11639 // @factory L.gridLayer(options?: GridLayer options)
11640 // Creates a new instance of GridLayer with the supplied options.
11641 function gridLayer(options) {
11642 return new GridLayer(options);
11643 }
11644
11645 /*
11646 * @class TileLayer
11647 * @inherits GridLayer
11648 * @aka L.TileLayer
11649 * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
11650 *
11651 * @example
11652 *
11653 * ```js
11654 * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
11655 * ```
11656 *
11657 * @section URL template
11658 * @example
11659 *
11660 * A string of the following form:
11661 *
11662 * ```
11663 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11664 * ```
11665 *
11666 * `{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.
11667 *
11668 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11669 *
11670 * ```
11671 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11672 * ```
11673 */
11674
11675
11676 var TileLayer = GridLayer.extend({
11677
11678 // @section
11679 // @aka TileLayer options
11680 options: {
11681 // @option minZoom: Number = 0
11682 // The minimum zoom level down to which this layer will be displayed (inclusive).
11683 minZoom: 0,
11684
11685 // @option maxZoom: Number = 18
11686 // The maximum zoom level up to which this layer will be displayed (inclusive).
11687 maxZoom: 18,
11688
11689 // @option subdomains: String|String[] = 'abc'
11690 // 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.
11691 subdomains: 'abc',
11692
11693 // @option errorTileUrl: String = ''
11694 // URL to the tile image to show in place of the tile that failed to load.
11695 errorTileUrl: '',
11696
11697 // @option zoomOffset: Number = 0
11698 // The zoom number used in tile URLs will be offset with this value.
11699 zoomOffset: 0,
11700
11701 // @option tms: Boolean = false
11702 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11703 tms: false,
11704
11705 // @option zoomReverse: Boolean = false
11706 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11707 zoomReverse: false,
11708
11709 // @option detectRetina: Boolean = false
11710 // 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.
11711 detectRetina: false,
11712
11713 // @option crossOrigin: Boolean|String = false
11714 // Whether the crossOrigin attribute will be added to the tiles.
11715 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
11716 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11717 crossOrigin: false,
11718
11719 // @option referrerPolicy: Boolean|String = false
11720 // Whether the referrerPolicy attribute will be added to the tiles.
11721 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
11722 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
11723 // (e.g. to validate an API token).
11724 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
11725 referrerPolicy: false
11726 },
11727
11728 initialize: function (url, options) {
11729
11730 this._url = url;
11731
11732 options = setOptions(this, options);
11733
11734 // detecting retina displays, adjusting tileSize and zoom levels
11735 if (options.detectRetina && Browser.retina && options.maxZoom > 0) {
11736
11737 options.tileSize = Math.floor(options.tileSize / 2);
11738
11739 if (!options.zoomReverse) {
11740 options.zoomOffset++;
11741 options.maxZoom--;
11742 } else {
11743 options.zoomOffset--;
11744 options.minZoom++;
11745 }
11746
11747 options.minZoom = Math.max(0, options.minZoom);
11748 }
11749
11750 if (typeof options.subdomains === 'string') {
11751 options.subdomains = options.subdomains.split('');
11752 }
11753
11754 this.on('tileunload', this._onTileRemove);
11755 },
11756
11757 // @method setUrl(url: String, noRedraw?: Boolean): this
11758 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11759 // If the URL does not change, the layer will not be redrawn unless
11760 // the noRedraw parameter is set to false.
11761 setUrl: function (url, noRedraw) {
11762 if (this._url === url && noRedraw === undefined) {
11763 noRedraw = true;
11764 }
11765
11766 this._url = url;
11767
11768 if (!noRedraw) {
11769 this.redraw();
11770 }
11771 return this;
11772 },
11773
11774 // @method createTile(coords: Object, done?: Function): HTMLElement
11775 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11776 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11777 // callback is called when the tile has been loaded.
11778 createTile: function (coords, done) {
11779 var tile = document.createElement('img');
11780
11781 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11782 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11783
11784 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11785 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11786 }
11787
11788 // for this new option we follow the documented behavior
11789 // more closely by only setting the property when string
11790 if (typeof this.options.referrerPolicy === 'string') {
11791 tile.referrerPolicy = this.options.referrerPolicy;
11792 }
11793
11794 /*
11795 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11796 https://www.w3.org/TR/WCAG20-TECHS/H67
11797 */
11798 tile.alt = '';
11799
11800 /*
11801 Set role="presentation" to force screen readers to ignore this
11802 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11803 */
11804 tile.setAttribute('role', 'presentation');
11805
11806 tile.src = this.getTileUrl(coords);
11807
11808 return tile;
11809 },
11810
11811 // @section Extension methods
11812 // @uninheritable
11813 // Layers extending `TileLayer` might reimplement the following method.
11814 // @method getTileUrl(coords: Object): String
11815 // Called only internally, returns the URL for a tile given its coordinates.
11816 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11817 getTileUrl: function (coords) {
11818 var data = {
11819 r: Browser.retina ? '@2x' : '',
11820 s: this._getSubdomain(coords),
11821 x: coords.x,
11822 y: coords.y,
11823 z: this._getZoomForUrl()
11824 };
11825 if (this._map && !this._map.options.crs.infinite) {
11826 var invertedY = this._globalTileRange.max.y - coords.y;
11827 if (this.options.tms) {
11828 data['y'] = invertedY;
11829 }
11830 data['-y'] = invertedY;
11831 }
11832
11833 return template(this._url, extend(data, this.options));
11834 },
11835
11836 _tileOnLoad: function (done, tile) {
11837 // For https://github.com/Leaflet/Leaflet/issues/3332
11838 if (Browser.ielt9) {
11839 setTimeout(bind(done, this, null, tile), 0);
11840 } else {
11841 done(null, tile);
11842 }
11843 },
11844
11845 _tileOnError: function (done, tile, e) {
11846 var errorUrl = this.options.errorTileUrl;
11847 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11848 tile.src = errorUrl;
11849 }
11850 done(e, tile);
11851 },
11852
11853 _onTileRemove: function (e) {
11854 e.tile.onload = null;
11855 },
11856
11857 _getZoomForUrl: function () {
11858 var zoom = this._tileZoom,
11859 maxZoom = this.options.maxZoom,
11860 zoomReverse = this.options.zoomReverse,
11861 zoomOffset = this.options.zoomOffset;
11862
11863 if (zoomReverse) {
11864 zoom = maxZoom - zoom;
11865 }
11866
11867 return zoom + zoomOffset;
11868 },
11869
11870 _getSubdomain: function (tilePoint) {
11871 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11872 return this.options.subdomains[index];
11873 },
11874
11875 // stops loading all tiles in the background layer
11876 _abortLoading: function () {
11877 var i, tile;
11878 for (i in this._tiles) {
11879 if (this._tiles[i].coords.z !== this._tileZoom) {
11880 tile = this._tiles[i].el;
11881
11882 tile.onload = falseFn;
11883 tile.onerror = falseFn;
11884
11885 if (!tile.complete) {
11886 tile.src = emptyImageUrl;
11887 var coords = this._tiles[i].coords;
11888 remove(tile);
11889 delete this._tiles[i];
11890 // @event tileabort: TileEvent
11891 // Fired when a tile was loading but is now not wanted.
11892 this.fire('tileabort', {
11893 tile: tile,
11894 coords: coords
11895 });
11896 }
11897 }
11898 }
11899 },
11900
11901 _removeTile: function (key) {
11902 var tile = this._tiles[key];
11903 if (!tile) { return; }
11904
11905 // Cancels any pending http requests associated with the tile
11906 tile.el.setAttribute('src', emptyImageUrl);
11907
11908 return GridLayer.prototype._removeTile.call(this, key);
11909 },
11910
11911 _tileReady: function (coords, err, tile) {
11912 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11913 return;
11914 }
11915
11916 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11917 }
11918 });
11919
11920
11921 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11922 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11923
11924 function tileLayer(url, options) {
11925 return new TileLayer(url, options);
11926 }
11927
11928 /*
11929 * @class TileLayer.WMS
11930 * @inherits TileLayer
11931 * @aka L.TileLayer.WMS
11932 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11933 *
11934 * @example
11935 *
11936 * ```js
11937 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11938 * layers: 'nexrad-n0r-900913',
11939 * format: 'image/png',
11940 * transparent: true,
11941 * attribution: "Weather data © 2012 IEM Nexrad"
11942 * });
11943 * ```
11944 */
11945
11946 var TileLayerWMS = TileLayer.extend({
11947
11948 // @section
11949 // @aka TileLayer.WMS options
11950 // If any custom options not documented here are used, they will be sent to the
11951 // WMS server as extra parameters in each request URL. This can be useful for
11952 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11953 defaultWmsParams: {
11954 service: 'WMS',
11955 request: 'GetMap',
11956
11957 // @option layers: String = ''
11958 // **(required)** Comma-separated list of WMS layers to show.
11959 layers: '',
11960
11961 // @option styles: String = ''
11962 // Comma-separated list of WMS styles.
11963 styles: '',
11964
11965 // @option format: String = 'image/jpeg'
11966 // WMS image format (use `'image/png'` for layers with transparency).
11967 format: 'image/jpeg',
11968
11969 // @option transparent: Boolean = false
11970 // If `true`, the WMS service will return images with transparency.
11971 transparent: false,
11972
11973 // @option version: String = '1.1.1'
11974 // Version of the WMS service to use
11975 version: '1.1.1'
11976 },
11977
11978 options: {
11979 // @option crs: CRS = null
11980 // Coordinate Reference System to use for the WMS requests, defaults to
11981 // map CRS. Don't change this if you're not sure what it means.
11982 crs: null,
11983
11984 // @option uppercase: Boolean = false
11985 // If `true`, WMS request parameter keys will be uppercase.
11986 uppercase: false
11987 },
11988
11989 initialize: function (url, options) {
11990
11991 this._url = url;
11992
11993 var wmsParams = extend({}, this.defaultWmsParams);
11994
11995 // all keys that are not TileLayer options go to WMS params
11996 for (var i in options) {
11997 if (!(i in this.options)) {
11998 wmsParams[i] = options[i];
11999 }
12000 }
12001
12002 options = setOptions(this, options);
12003
12004 var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
12005 var tileSize = this.getTileSize();
12006 wmsParams.width = tileSize.x * realRetina;
12007 wmsParams.height = tileSize.y * realRetina;
12008
12009 this.wmsParams = wmsParams;
12010 },
12011
12012 onAdd: function (map) {
12013
12014 this._crs = this.options.crs || map.options.crs;
12015 this._wmsVersion = parseFloat(this.wmsParams.version);
12016
12017 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
12018 this.wmsParams[projectionKey] = this._crs.code;
12019
12020 TileLayer.prototype.onAdd.call(this, map);
12021 },
12022
12023 getTileUrl: function (coords) {
12024
12025 var tileBounds = this._tileCoordsToNwSe(coords),
12026 crs = this._crs,
12027 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
12028 min = bounds.min,
12029 max = bounds.max,
12030 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
12031 [min.y, min.x, max.y, max.x] :
12032 [min.x, min.y, max.x, max.y]).join(','),
12033 url = TileLayer.prototype.getTileUrl.call(this, coords);
12034 return url +
12035 getParamString(this.wmsParams, url, this.options.uppercase) +
12036 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
12037 },
12038
12039 // @method setParams(params: Object, noRedraw?: Boolean): this
12040 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
12041 setParams: function (params, noRedraw) {
12042
12043 extend(this.wmsParams, params);
12044
12045 if (!noRedraw) {
12046 this.redraw();
12047 }
12048
12049 return this;
12050 }
12051 });
12052
12053
12054 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
12055 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
12056 function tileLayerWMS(url, options) {
12057 return new TileLayerWMS(url, options);
12058 }
12059
12060 TileLayer.WMS = TileLayerWMS;
12061 tileLayer.wms = tileLayerWMS;
12062
12063 /*
12064 * @class Renderer
12065 * @inherits Layer
12066 * @aka L.Renderer
12067 *
12068 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
12069 * DOM container of the renderer, its bounds, and its zoom animation.
12070 *
12071 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
12072 * itself can be added or removed to the map. All paths use a renderer, which can
12073 * be implicit (the map will decide the type of renderer and use it automatically)
12074 * or explicit (using the [`renderer`](#path-renderer) option of the path).
12075 *
12076 * Do not use this class directly, use `SVG` and `Canvas` instead.
12077 *
12078 * @event update: Event
12079 * Fired when the renderer updates its bounds, center and zoom, for example when
12080 * its map has moved
12081 */
12082
12083 var Renderer = Layer.extend({
12084
12085 // @section
12086 // @aka Renderer options
12087 options: {
12088 // @option padding: Number = 0.1
12089 // How much to extend the clip area around the map view (relative to its size)
12090 // e.g. 0.1 would be 10% of map view in each direction
12091 padding: 0.1
12092 },
12093
12094 initialize: function (options) {
12095 setOptions(this, options);
12096 stamp(this);
12097 this._layers = this._layers || {};
12098 },
12099
12100 onAdd: function () {
12101 if (!this._container) {
12102 this._initContainer(); // defined by renderer implementations
12103
12104 if (this._zoomAnimated) {
12105 addClass(this._container, 'leaflet-zoom-animated');
12106 }
12107 }
12108
12109 this.getPane().appendChild(this._container);
12110 this._update();
12111 this.on('update', this._updatePaths, this);
12112 },
12113
12114 onRemove: function () {
12115 this.off('update', this._updatePaths, this);
12116 this._destroyContainer();
12117 },
12118
12119 getEvents: function () {
12120 var events = {
12121 viewreset: this._reset,
12122 zoom: this._onZoom,
12123 moveend: this._update,
12124 zoomend: this._onZoomEnd
12125 };
12126 if (this._zoomAnimated) {
12127 events.zoomanim = this._onAnimZoom;
12128 }
12129 return events;
12130 },
12131
12132 _onAnimZoom: function (ev) {
12133 this._updateTransform(ev.center, ev.zoom);
12134 },
12135
12136 _onZoom: function () {
12137 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12138 },
12139
12140 _updateTransform: function (center, zoom) {
12141 var scale = this._map.getZoomScale(zoom, this._zoom),
12142 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12143 currentCenterPoint = this._map.project(this._center, zoom),
12144
12145 topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
12146 .subtract(this._map._getNewPixelOrigin(center, zoom));
12147
12148 if (Browser.any3d) {
12149 setTransform(this._container, topLeftOffset, scale);
12150 } else {
12151 setPosition(this._container, topLeftOffset);
12152 }
12153 },
12154
12155 _reset: function () {
12156 this._update();
12157 this._updateTransform(this._center, this._zoom);
12158
12159 for (var id in this._layers) {
12160 this._layers[id]._reset();
12161 }
12162 },
12163
12164 _onZoomEnd: function () {
12165 for (var id in this._layers) {
12166 this._layers[id]._project();
12167 }
12168 },
12169
12170 _updatePaths: function () {
12171 for (var id in this._layers) {
12172 this._layers[id]._update();
12173 }
12174 },
12175
12176 _update: function () {
12177 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12178 // Subclasses are responsible of firing the 'update' event.
12179 var p = this.options.padding,
12180 size = this._map.getSize(),
12181 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12182
12183 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12184
12185 this._center = this._map.getCenter();
12186 this._zoom = this._map.getZoom();
12187 }
12188 });
12189
12190 /*
12191 * @class Canvas
12192 * @inherits Renderer
12193 * @aka L.Canvas
12194 *
12195 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12196 * Inherits `Renderer`.
12197 *
12198 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
12199 * available in all web browsers, notably IE8, and overlapping geometries might
12200 * not display properly in some edge cases.
12201 *
12202 * @example
12203 *
12204 * Use Canvas by default for all paths in the map:
12205 *
12206 * ```js
12207 * var map = L.map('map', {
12208 * renderer: L.canvas()
12209 * });
12210 * ```
12211 *
12212 * Use a Canvas renderer with extra padding for specific vector geometries:
12213 *
12214 * ```js
12215 * var map = L.map('map');
12216 * var myRenderer = L.canvas({ padding: 0.5 });
12217 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12218 * var circle = L.circle( center, { renderer: myRenderer } );
12219 * ```
12220 */
12221
12222 var Canvas = Renderer.extend({
12223
12224 // @section
12225 // @aka Canvas options
12226 options: {
12227 // @option tolerance: Number = 0
12228 // How much to extend the click tolerance around a path/object on the map.
12229 tolerance: 0
12230 },
12231
12232 getEvents: function () {
12233 var events = Renderer.prototype.getEvents.call(this);
12234 events.viewprereset = this._onViewPreReset;
12235 return events;
12236 },
12237
12238 _onViewPreReset: function () {
12239 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12240 this._postponeUpdatePaths = true;
12241 },
12242
12243 onAdd: function () {
12244 Renderer.prototype.onAdd.call(this);
12245
12246 // Redraw vectors since canvas is cleared upon removal,
12247 // in case of removing the renderer itself from the map.
12248 this._draw();
12249 },
12250
12251 _initContainer: function () {
12252 var container = this._container = document.createElement('canvas');
12253
12254 on(container, 'mousemove', this._onMouseMove, this);
12255 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12256 on(container, 'mouseout', this._handleMouseOut, this);
12257 container['_leaflet_disable_events'] = true;
12258
12259 this._ctx = container.getContext('2d');
12260 },
12261
12262 _destroyContainer: function () {
12263 cancelAnimFrame(this._redrawRequest);
12264 delete this._ctx;
12265 remove(this._container);
12266 off(this._container);
12267 delete this._container;
12268 },
12269
12270 _updatePaths: function () {
12271 if (this._postponeUpdatePaths) { return; }
12272
12273 var layer;
12274 this._redrawBounds = null;
12275 for (var id in this._layers) {
12276 layer = this._layers[id];
12277 layer._update();
12278 }
12279 this._redraw();
12280 },
12281
12282 _update: function () {
12283 if (this._map._animatingZoom && this._bounds) { return; }
12284
12285 Renderer.prototype._update.call(this);
12286
12287 var b = this._bounds,
12288 container = this._container,
12289 size = b.getSize(),
12290 m = Browser.retina ? 2 : 1;
12291
12292 setPosition(container, b.min);
12293
12294 // set canvas size (also clearing it); use double size on retina
12295 container.width = m * size.x;
12296 container.height = m * size.y;
12297 container.style.width = size.x + 'px';
12298 container.style.height = size.y + 'px';
12299
12300 if (Browser.retina) {
12301 this._ctx.scale(2, 2);
12302 }
12303
12304 // translate so we use the same path coordinates after canvas element moves
12305 this._ctx.translate(-b.min.x, -b.min.y);
12306
12307 // Tell paths to redraw themselves
12308 this.fire('update');
12309 },
12310
12311 _reset: function () {
12312 Renderer.prototype._reset.call(this);
12313
12314 if (this._postponeUpdatePaths) {
12315 this._postponeUpdatePaths = false;
12316 this._updatePaths();
12317 }
12318 },
12319
12320 _initPath: function (layer) {
12321 this._updateDashArray(layer);
12322 this._layers[stamp(layer)] = layer;
12323
12324 var order = layer._order = {
12325 layer: layer,
12326 prev: this._drawLast,
12327 next: null
12328 };
12329 if (this._drawLast) { this._drawLast.next = order; }
12330 this._drawLast = order;
12331 this._drawFirst = this._drawFirst || this._drawLast;
12332 },
12333
12334 _addPath: function (layer) {
12335 this._requestRedraw(layer);
12336 },
12337
12338 _removePath: function (layer) {
12339 var order = layer._order;
12340 var next = order.next;
12341 var prev = order.prev;
12342
12343 if (next) {
12344 next.prev = prev;
12345 } else {
12346 this._drawLast = prev;
12347 }
12348 if (prev) {
12349 prev.next = next;
12350 } else {
12351 this._drawFirst = next;
12352 }
12353
12354 delete layer._order;
12355
12356 delete this._layers[stamp(layer)];
12357
12358 this._requestRedraw(layer);
12359 },
12360
12361 _updatePath: function (layer) {
12362 // Redraw the union of the layer's old pixel
12363 // bounds and the new pixel bounds.
12364 this._extendRedrawBounds(layer);
12365 layer._project();
12366 layer._update();
12367 // The redraw will extend the redraw bounds
12368 // with the new pixel bounds.
12369 this._requestRedraw(layer);
12370 },
12371
12372 _updateStyle: function (layer) {
12373 this._updateDashArray(layer);
12374 this._requestRedraw(layer);
12375 },
12376
12377 _updateDashArray: function (layer) {
12378 if (typeof layer.options.dashArray === 'string') {
12379 var parts = layer.options.dashArray.split(/[, ]+/),
12380 dashArray = [],
12381 dashValue,
12382 i;
12383 for (i = 0; i < parts.length; i++) {
12384 dashValue = Number(parts[i]);
12385 // Ignore dash array containing invalid lengths
12386 if (isNaN(dashValue)) { return; }
12387 dashArray.push(dashValue);
12388 }
12389 layer.options._dashArray = dashArray;
12390 } else {
12391 layer.options._dashArray = layer.options.dashArray;
12392 }
12393 },
12394
12395 _requestRedraw: function (layer) {
12396 if (!this._map) { return; }
12397
12398 this._extendRedrawBounds(layer);
12399 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12400 },
12401
12402 _extendRedrawBounds: function (layer) {
12403 if (layer._pxBounds) {
12404 var padding = (layer.options.weight || 0) + 1;
12405 this._redrawBounds = this._redrawBounds || new Bounds();
12406 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12407 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12408 }
12409 },
12410
12411 _redraw: function () {
12412 this._redrawRequest = null;
12413
12414 if (this._redrawBounds) {
12415 this._redrawBounds.min._floor();
12416 this._redrawBounds.max._ceil();
12417 }
12418
12419 this._clear(); // clear layers in redraw bounds
12420 this._draw(); // draw layers
12421
12422 this._redrawBounds = null;
12423 },
12424
12425 _clear: function () {
12426 var bounds = this._redrawBounds;
12427 if (bounds) {
12428 var size = bounds.getSize();
12429 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12430 } else {
12431 this._ctx.save();
12432 this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12433 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12434 this._ctx.restore();
12435 }
12436 },
12437
12438 _draw: function () {
12439 var layer, bounds = this._redrawBounds;
12440 this._ctx.save();
12441 if (bounds) {
12442 var size = bounds.getSize();
12443 this._ctx.beginPath();
12444 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12445 this._ctx.clip();
12446 }
12447
12448 this._drawing = true;
12449
12450 for (var order = this._drawFirst; order; order = order.next) {
12451 layer = order.layer;
12452 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12453 layer._updatePath();
12454 }
12455 }
12456
12457 this._drawing = false;
12458
12459 this._ctx.restore(); // Restore state before clipping.
12460 },
12461
12462 _updatePoly: function (layer, closed) {
12463 if (!this._drawing) { return; }
12464
12465 var i, j, len2, p,
12466 parts = layer._parts,
12467 len = parts.length,
12468 ctx = this._ctx;
12469
12470 if (!len) { return; }
12471
12472 ctx.beginPath();
12473
12474 for (i = 0; i < len; i++) {
12475 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12476 p = parts[i][j];
12477 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12478 }
12479 if (closed) {
12480 ctx.closePath();
12481 }
12482 }
12483
12484 this._fillStroke(ctx, layer);
12485
12486 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12487 },
12488
12489 _updateCircle: function (layer) {
12490
12491 if (!this._drawing || layer._empty()) { return; }
12492
12493 var p = layer._point,
12494 ctx = this._ctx,
12495 r = Math.max(Math.round(layer._radius), 1),
12496 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12497
12498 if (s !== 1) {
12499 ctx.save();
12500 ctx.scale(1, s);
12501 }
12502
12503 ctx.beginPath();
12504 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12505
12506 if (s !== 1) {
12507 ctx.restore();
12508 }
12509
12510 this._fillStroke(ctx, layer);
12511 },
12512
12513 _fillStroke: function (ctx, layer) {
12514 var options = layer.options;
12515
12516 if (options.fill) {
12517 ctx.globalAlpha = options.fillOpacity;
12518 ctx.fillStyle = options.fillColor || options.color;
12519 ctx.fill(options.fillRule || 'evenodd');
12520 }
12521
12522 if (options.stroke && options.weight !== 0) {
12523 if (ctx.setLineDash) {
12524 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12525 }
12526 ctx.globalAlpha = options.opacity;
12527 ctx.lineWidth = options.weight;
12528 ctx.strokeStyle = options.color;
12529 ctx.lineCap = options.lineCap;
12530 ctx.lineJoin = options.lineJoin;
12531 ctx.stroke();
12532 }
12533 },
12534
12535 // Canvas obviously doesn't have mouse events for individual drawn objects,
12536 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12537
12538 _onClick: function (e) {
12539 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12540
12541 for (var order = this._drawFirst; order; order = order.next) {
12542 layer = order.layer;
12543 if (layer.options.interactive && layer._containsPoint(point)) {
12544 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
12545 clickedLayer = layer;
12546 }
12547 }
12548 }
12549 this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
12550 },
12551
12552 _onMouseMove: function (e) {
12553 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12554
12555 var point = this._map.mouseEventToLayerPoint(e);
12556 this._handleMouseHover(e, point);
12557 },
12558
12559
12560 _handleMouseOut: function (e) {
12561 var layer = this._hoveredLayer;
12562 if (layer) {
12563 // if we're leaving the layer, fire mouseout
12564 removeClass(this._container, 'leaflet-interactive');
12565 this._fireEvent([layer], e, 'mouseout');
12566 this._hoveredLayer = null;
12567 this._mouseHoverThrottled = false;
12568 }
12569 },
12570
12571 _handleMouseHover: function (e, point) {
12572 if (this._mouseHoverThrottled) {
12573 return;
12574 }
12575
12576 var layer, candidateHoveredLayer;
12577
12578 for (var order = this._drawFirst; order; order = order.next) {
12579 layer = order.layer;
12580 if (layer.options.interactive && layer._containsPoint(point)) {
12581 candidateHoveredLayer = layer;
12582 }
12583 }
12584
12585 if (candidateHoveredLayer !== this._hoveredLayer) {
12586 this._handleMouseOut(e);
12587
12588 if (candidateHoveredLayer) {
12589 addClass(this._container, 'leaflet-interactive'); // change cursor
12590 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12591 this._hoveredLayer = candidateHoveredLayer;
12592 }
12593 }
12594
12595 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
12596
12597 this._mouseHoverThrottled = true;
12598 setTimeout(bind(function () {
12599 this._mouseHoverThrottled = false;
12600 }, this), 32);
12601 },
12602
12603 _fireEvent: function (layers, e, type) {
12604 this._map._fireDOMEvent(e, type || e.type, layers);
12605 },
12606
12607 _bringToFront: function (layer) {
12608 var order = layer._order;
12609
12610 if (!order) { return; }
12611
12612 var next = order.next;
12613 var prev = order.prev;
12614
12615 if (next) {
12616 next.prev = prev;
12617 } else {
12618 // Already last
12619 return;
12620 }
12621 if (prev) {
12622 prev.next = next;
12623 } else if (next) {
12624 // Update first entry unless this is the
12625 // single entry
12626 this._drawFirst = next;
12627 }
12628
12629 order.prev = this._drawLast;
12630 this._drawLast.next = order;
12631
12632 order.next = null;
12633 this._drawLast = order;
12634
12635 this._requestRedraw(layer);
12636 },
12637
12638 _bringToBack: function (layer) {
12639 var order = layer._order;
12640
12641 if (!order) { return; }
12642
12643 var next = order.next;
12644 var prev = order.prev;
12645
12646 if (prev) {
12647 prev.next = next;
12648 } else {
12649 // Already first
12650 return;
12651 }
12652 if (next) {
12653 next.prev = prev;
12654 } else if (prev) {
12655 // Update last entry unless this is the
12656 // single entry
12657 this._drawLast = prev;
12658 }
12659
12660 order.prev = null;
12661
12662 order.next = this._drawFirst;
12663 this._drawFirst.prev = order;
12664 this._drawFirst = order;
12665
12666 this._requestRedraw(layer);
12667 }
12668 });
12669
12670 // @factory L.canvas(options?: Renderer options)
12671 // Creates a Canvas renderer with the given options.
12672 function canvas(options) {
12673 return Browser.canvas ? new Canvas(options) : null;
12674 }
12675
12676 /*
12677 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12678 */
12679
12680
12681 var vmlCreate = (function () {
12682 try {
12683 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12684 return function (name) {
12685 return document.createElement('<lvml:' + name + ' class="lvml">');
12686 };
12687 } catch (e) {
12688 // Do not return fn from catch block so `e` can be garbage collected
12689 // See https://github.com/Leaflet/Leaflet/pull/7279
12690 }
12691 return function (name) {
12692 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12693 };
12694 })();
12695
12696
12697 /*
12698 * @class SVG
12699 *
12700 *
12701 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12702 * with old versions of Internet Explorer.
12703 */
12704
12705 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12706 var vmlMixin = {
12707
12708 _initContainer: function () {
12709 this._container = create$1('div', 'leaflet-vml-container');
12710 },
12711
12712 _update: function () {
12713 if (this._map._animatingZoom) { return; }
12714 Renderer.prototype._update.call(this);
12715 this.fire('update');
12716 },
12717
12718 _initPath: function (layer) {
12719 var container = layer._container = vmlCreate('shape');
12720
12721 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12722
12723 container.coordsize = '1 1';
12724
12725 layer._path = vmlCreate('path');
12726 container.appendChild(layer._path);
12727
12728 this._updateStyle(layer);
12729 this._layers[stamp(layer)] = layer;
12730 },
12731
12732 _addPath: function (layer) {
12733 var container = layer._container;
12734 this._container.appendChild(container);
12735
12736 if (layer.options.interactive) {
12737 layer.addInteractiveTarget(container);
12738 }
12739 },
12740
12741 _removePath: function (layer) {
12742 var container = layer._container;
12743 remove(container);
12744 layer.removeInteractiveTarget(container);
12745 delete this._layers[stamp(layer)];
12746 },
12747
12748 _updateStyle: function (layer) {
12749 var stroke = layer._stroke,
12750 fill = layer._fill,
12751 options = layer.options,
12752 container = layer._container;
12753
12754 container.stroked = !!options.stroke;
12755 container.filled = !!options.fill;
12756
12757 if (options.stroke) {
12758 if (!stroke) {
12759 stroke = layer._stroke = vmlCreate('stroke');
12760 }
12761 container.appendChild(stroke);
12762 stroke.weight = options.weight + 'px';
12763 stroke.color = options.color;
12764 stroke.opacity = options.opacity;
12765
12766 if (options.dashArray) {
12767 stroke.dashStyle = isArray(options.dashArray) ?
12768 options.dashArray.join(' ') :
12769 options.dashArray.replace(/( *, *)/g, ' ');
12770 } else {
12771 stroke.dashStyle = '';
12772 }
12773 stroke.endcap = options.lineCap.replace('butt', 'flat');
12774 stroke.joinstyle = options.lineJoin;
12775
12776 } else if (stroke) {
12777 container.removeChild(stroke);
12778 layer._stroke = null;
12779 }
12780
12781 if (options.fill) {
12782 if (!fill) {
12783 fill = layer._fill = vmlCreate('fill');
12784 }
12785 container.appendChild(fill);
12786 fill.color = options.fillColor || options.color;
12787 fill.opacity = options.fillOpacity;
12788
12789 } else if (fill) {
12790 container.removeChild(fill);
12791 layer._fill = null;
12792 }
12793 },
12794
12795 _updateCircle: function (layer) {
12796 var p = layer._point.round(),
12797 r = Math.round(layer._radius),
12798 r2 = Math.round(layer._radiusY || r);
12799
12800 this._setPath(layer, layer._empty() ? 'M0 0' :
12801 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12802 },
12803
12804 _setPath: function (layer, path) {
12805 layer._path.v = path;
12806 },
12807
12808 _bringToFront: function (layer) {
12809 toFront(layer._container);
12810 },
12811
12812 _bringToBack: function (layer) {
12813 toBack(layer._container);
12814 }
12815 };
12816
12817 var create = Browser.vml ? vmlCreate : svgCreate;
12818
12819 /*
12820 * @class SVG
12821 * @inherits Renderer
12822 * @aka L.SVG
12823 *
12824 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12825 * Inherits `Renderer`.
12826 *
12827 * Due to [technical limitations](https://caniuse.com/svg), SVG is not
12828 * available in all web browsers, notably Android 2.x and 3.x.
12829 *
12830 * Although SVG is not available on IE7 and IE8, these browsers support
12831 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12832 * (a now deprecated technology), and the SVG renderer will fall back to VML in
12833 * this case.
12834 *
12835 * @example
12836 *
12837 * Use SVG by default for all paths in the map:
12838 *
12839 * ```js
12840 * var map = L.map('map', {
12841 * renderer: L.svg()
12842 * });
12843 * ```
12844 *
12845 * Use a SVG renderer with extra padding for specific vector geometries:
12846 *
12847 * ```js
12848 * var map = L.map('map');
12849 * var myRenderer = L.svg({ padding: 0.5 });
12850 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12851 * var circle = L.circle( center, { renderer: myRenderer } );
12852 * ```
12853 */
12854
12855 var SVG = Renderer.extend({
12856
12857 _initContainer: function () {
12858 this._container = create('svg');
12859
12860 // makes it possible to click through svg root; we'll reset it back in individual paths
12861 this._container.setAttribute('pointer-events', 'none');
12862
12863 this._rootGroup = create('g');
12864 this._container.appendChild(this._rootGroup);
12865 },
12866
12867 _destroyContainer: function () {
12868 remove(this._container);
12869 off(this._container);
12870 delete this._container;
12871 delete this._rootGroup;
12872 delete this._svgSize;
12873 },
12874
12875 _update: function () {
12876 if (this._map._animatingZoom && this._bounds) { return; }
12877
12878 Renderer.prototype._update.call(this);
12879
12880 var b = this._bounds,
12881 size = b.getSize(),
12882 container = this._container;
12883
12884 // set size of svg-container if changed
12885 if (!this._svgSize || !this._svgSize.equals(size)) {
12886 this._svgSize = size;
12887 container.setAttribute('width', size.x);
12888 container.setAttribute('height', size.y);
12889 }
12890
12891 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12892 setPosition(container, b.min);
12893 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12894
12895 this.fire('update');
12896 },
12897
12898 // methods below are called by vector layers implementations
12899
12900 _initPath: function (layer) {
12901 var path = layer._path = create('path');
12902
12903 // @namespace Path
12904 // @option className: String = null
12905 // Custom class name set on an element. Only for SVG renderer.
12906 if (layer.options.className) {
12907 addClass(path, layer.options.className);
12908 }
12909
12910 if (layer.options.interactive) {
12911 addClass(path, 'leaflet-interactive');
12912 }
12913
12914 this._updateStyle(layer);
12915 this._layers[stamp(layer)] = layer;
12916 },
12917
12918 _addPath: function (layer) {
12919 if (!this._rootGroup) { this._initContainer(); }
12920 this._rootGroup.appendChild(layer._path);
12921 layer.addInteractiveTarget(layer._path);
12922 },
12923
12924 _removePath: function (layer) {
12925 remove(layer._path);
12926 layer.removeInteractiveTarget(layer._path);
12927 delete this._layers[stamp(layer)];
12928 },
12929
12930 _updatePath: function (layer) {
12931 layer._project();
12932 layer._update();
12933 },
12934
12935 _updateStyle: function (layer) {
12936 var path = layer._path,
12937 options = layer.options;
12938
12939 if (!path) { return; }
12940
12941 if (options.stroke) {
12942 path.setAttribute('stroke', options.color);
12943 path.setAttribute('stroke-opacity', options.opacity);
12944 path.setAttribute('stroke-width', options.weight);
12945 path.setAttribute('stroke-linecap', options.lineCap);
12946 path.setAttribute('stroke-linejoin', options.lineJoin);
12947
12948 if (options.dashArray) {
12949 path.setAttribute('stroke-dasharray', options.dashArray);
12950 } else {
12951 path.removeAttribute('stroke-dasharray');
12952 }
12953
12954 if (options.dashOffset) {
12955 path.setAttribute('stroke-dashoffset', options.dashOffset);
12956 } else {
12957 path.removeAttribute('stroke-dashoffset');
12958 }
12959 } else {
12960 path.setAttribute('stroke', 'none');
12961 }
12962
12963 if (options.fill) {
12964 path.setAttribute('fill', options.fillColor || options.color);
12965 path.setAttribute('fill-opacity', options.fillOpacity);
12966 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12967 } else {
12968 path.setAttribute('fill', 'none');
12969 }
12970 },
12971
12972 _updatePoly: function (layer, closed) {
12973 this._setPath(layer, pointsToPath(layer._parts, closed));
12974 },
12975
12976 _updateCircle: function (layer) {
12977 var p = layer._point,
12978 r = Math.max(Math.round(layer._radius), 1),
12979 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12980 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12981
12982 // drawing a circle with two half-arcs
12983 var d = layer._empty() ? 'M0 0' :
12984 'M' + (p.x - r) + ',' + p.y +
12985 arc + (r * 2) + ',0 ' +
12986 arc + (-r * 2) + ',0 ';
12987
12988 this._setPath(layer, d);
12989 },
12990
12991 _setPath: function (layer, path) {
12992 layer._path.setAttribute('d', path);
12993 },
12994
12995 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12996 _bringToFront: function (layer) {
12997 toFront(layer._path);
12998 },
12999
13000 _bringToBack: function (layer) {
13001 toBack(layer._path);
13002 }
13003 });
13004
13005 if (Browser.vml) {
13006 SVG.include(vmlMixin);
13007 }
13008
13009 // @namespace SVG
13010 // @factory L.svg(options?: Renderer options)
13011 // Creates a SVG renderer with the given options.
13012 function svg(options) {
13013 return Browser.svg || Browser.vml ? new SVG(options) : null;
13014 }
13015
13016 Map.include({
13017 // @namespace Map; @method getRenderer(layer: Path): Renderer
13018 // Returns the instance of `Renderer` that should be used to render the given
13019 // `Path`. It will ensure that the `renderer` options of the map and paths
13020 // are respected, and that the renderers do exist on the map.
13021 getRenderer: function (layer) {
13022 // @namespace Path; @option renderer: Renderer
13023 // Use this specific instance of `Renderer` for this path. Takes
13024 // precedence over the map's [default renderer](#map-renderer).
13025 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
13026
13027 if (!renderer) {
13028 renderer = this._renderer = this._createRenderer();
13029 }
13030
13031 if (!this.hasLayer(renderer)) {
13032 this.addLayer(renderer);
13033 }
13034 return renderer;
13035 },
13036
13037 _getPaneRenderer: function (name) {
13038 if (name === 'overlayPane' || name === undefined) {
13039 return false;
13040 }
13041
13042 var renderer = this._paneRenderers[name];
13043 if (renderer === undefined) {
13044 renderer = this._createRenderer({pane: name});
13045 this._paneRenderers[name] = renderer;
13046 }
13047 return renderer;
13048 },
13049
13050 _createRenderer: function (options) {
13051 // @namespace Map; @option preferCanvas: Boolean = false
13052 // Whether `Path`s should be rendered on a `Canvas` renderer.
13053 // By default, all `Path`s are rendered in a `SVG` renderer.
13054 return (this.options.preferCanvas && canvas(options)) || svg(options);
13055 }
13056 });
13057
13058 /*
13059 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
13060 */
13061
13062 /*
13063 * @class Rectangle
13064 * @aka L.Rectangle
13065 * @inherits Polygon
13066 *
13067 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
13068 *
13069 * @example
13070 *
13071 * ```js
13072 * // define rectangle geographical bounds
13073 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
13074 *
13075 * // create an orange rectangle
13076 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
13077 *
13078 * // zoom the map to the rectangle bounds
13079 * map.fitBounds(bounds);
13080 * ```
13081 *
13082 */
13083
13084
13085 var Rectangle = Polygon.extend({
13086 initialize: function (latLngBounds, options) {
13087 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13088 },
13089
13090 // @method setBounds(latLngBounds: LatLngBounds): this
13091 // Redraws the rectangle with the passed bounds.
13092 setBounds: function (latLngBounds) {
13093 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13094 },
13095
13096 _boundsToLatLngs: function (latLngBounds) {
13097 latLngBounds = toLatLngBounds(latLngBounds);
13098 return [
13099 latLngBounds.getSouthWest(),
13100 latLngBounds.getNorthWest(),
13101 latLngBounds.getNorthEast(),
13102 latLngBounds.getSouthEast()
13103 ];
13104 }
13105 });
13106
13107
13108 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13109 function rectangle(latLngBounds, options) {
13110 return new Rectangle(latLngBounds, options);
13111 }
13112
13113 SVG.create = create;
13114 SVG.pointsToPath = pointsToPath;
13115
13116 GeoJSON.geometryToLayer = geometryToLayer;
13117 GeoJSON.coordsToLatLng = coordsToLatLng;
13118 GeoJSON.coordsToLatLngs = coordsToLatLngs;
13119 GeoJSON.latLngToCoords = latLngToCoords;
13120 GeoJSON.latLngsToCoords = latLngsToCoords;
13121 GeoJSON.getFeature = getFeature;
13122 GeoJSON.asFeature = asFeature;
13123
13124 /*
13125 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13126 * (zoom to a selected bounding box), enabled by default.
13127 */
13128
13129 // @namespace Map
13130 // @section Interaction Options
13131 Map.mergeOptions({
13132 // @option boxZoom: Boolean = true
13133 // Whether the map can be zoomed to a rectangular area specified by
13134 // dragging the mouse while pressing the shift key.
13135 boxZoom: true
13136 });
13137
13138 var BoxZoom = Handler.extend({
13139 initialize: function (map) {
13140 this._map = map;
13141 this._container = map._container;
13142 this._pane = map._panes.overlayPane;
13143 this._resetStateTimeout = 0;
13144 map.on('unload', this._destroy, this);
13145 },
13146
13147 addHooks: function () {
13148 on(this._container, 'mousedown', this._onMouseDown, this);
13149 },
13150
13151 removeHooks: function () {
13152 off(this._container, 'mousedown', this._onMouseDown, this);
13153 },
13154
13155 moved: function () {
13156 return this._moved;
13157 },
13158
13159 _destroy: function () {
13160 remove(this._pane);
13161 delete this._pane;
13162 },
13163
13164 _resetState: function () {
13165 this._resetStateTimeout = 0;
13166 this._moved = false;
13167 },
13168
13169 _clearDeferredResetState: function () {
13170 if (this._resetStateTimeout !== 0) {
13171 clearTimeout(this._resetStateTimeout);
13172 this._resetStateTimeout = 0;
13173 }
13174 },
13175
13176 _onMouseDown: function (e) {
13177 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13178
13179 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13180 // will interrupt the interaction and orphan a box element in the container.
13181 this._clearDeferredResetState();
13182 this._resetState();
13183
13184 disableTextSelection();
13185 disableImageDrag();
13186
13187 this._startPoint = this._map.mouseEventToContainerPoint(e);
13188
13189 on(document, {
13190 contextmenu: stop,
13191 mousemove: this._onMouseMove,
13192 mouseup: this._onMouseUp,
13193 keydown: this._onKeyDown
13194 }, this);
13195 },
13196
13197 _onMouseMove: function (e) {
13198 if (!this._moved) {
13199 this._moved = true;
13200
13201 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13202 addClass(this._container, 'leaflet-crosshair');
13203
13204 this._map.fire('boxzoomstart');
13205 }
13206
13207 this._point = this._map.mouseEventToContainerPoint(e);
13208
13209 var bounds = new Bounds(this._point, this._startPoint),
13210 size = bounds.getSize();
13211
13212 setPosition(this._box, bounds.min);
13213
13214 this._box.style.width = size.x + 'px';
13215 this._box.style.height = size.y + 'px';
13216 },
13217
13218 _finish: function () {
13219 if (this._moved) {
13220 remove(this._box);
13221 removeClass(this._container, 'leaflet-crosshair');
13222 }
13223
13224 enableTextSelection();
13225 enableImageDrag();
13226
13227 off(document, {
13228 contextmenu: stop,
13229 mousemove: this._onMouseMove,
13230 mouseup: this._onMouseUp,
13231 keydown: this._onKeyDown
13232 }, this);
13233 },
13234
13235 _onMouseUp: function (e) {
13236 if ((e.which !== 1) && (e.button !== 1)) { return; }
13237
13238 this._finish();
13239
13240 if (!this._moved) { return; }
13241 // Postpone to next JS tick so internal click event handling
13242 // still see it as "moved".
13243 this._clearDeferredResetState();
13244 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13245
13246 var bounds = new LatLngBounds(
13247 this._map.containerPointToLatLng(this._startPoint),
13248 this._map.containerPointToLatLng(this._point));
13249
13250 this._map
13251 .fitBounds(bounds)
13252 .fire('boxzoomend', {boxZoomBounds: bounds});
13253 },
13254
13255 _onKeyDown: function (e) {
13256 if (e.keyCode === 27) {
13257 this._finish();
13258 this._clearDeferredResetState();
13259 this._resetState();
13260 }
13261 }
13262 });
13263
13264 // @section Handlers
13265 // @property boxZoom: Handler
13266 // Box (shift-drag with mouse) zoom handler.
13267 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13268
13269 /*
13270 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13271 */
13272
13273 // @namespace Map
13274 // @section Interaction Options
13275
13276 Map.mergeOptions({
13277 // @option doubleClickZoom: Boolean|String = true
13278 // Whether the map can be zoomed in by double clicking on it and
13279 // zoomed out by double clicking while holding shift. If passed
13280 // `'center'`, double-click zoom will zoom to the center of the
13281 // view regardless of where the mouse was.
13282 doubleClickZoom: true
13283 });
13284
13285 var DoubleClickZoom = Handler.extend({
13286 addHooks: function () {
13287 this._map.on('dblclick', this._onDoubleClick, this);
13288 },
13289
13290 removeHooks: function () {
13291 this._map.off('dblclick', this._onDoubleClick, this);
13292 },
13293
13294 _onDoubleClick: function (e) {
13295 var map = this._map,
13296 oldZoom = map.getZoom(),
13297 delta = map.options.zoomDelta,
13298 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13299
13300 if (map.options.doubleClickZoom === 'center') {
13301 map.setZoom(zoom);
13302 } else {
13303 map.setZoomAround(e.containerPoint, zoom);
13304 }
13305 }
13306 });
13307
13308 // @section Handlers
13309 //
13310 // Map properties include interaction handlers that allow you to control
13311 // interaction behavior in runtime, enabling or disabling certain features such
13312 // as dragging or touch zoom (see `Handler` methods). For example:
13313 //
13314 // ```js
13315 // map.doubleClickZoom.disable();
13316 // ```
13317 //
13318 // @property doubleClickZoom: Handler
13319 // Double click zoom handler.
13320 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13321
13322 /*
13323 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13324 */
13325
13326 // @namespace Map
13327 // @section Interaction Options
13328 Map.mergeOptions({
13329 // @option dragging: Boolean = true
13330 // Whether the map is draggable with mouse/touch or not.
13331 dragging: true,
13332
13333 // @section Panning Inertia Options
13334 // @option inertia: Boolean = *
13335 // If enabled, panning of the map will have an inertia effect where
13336 // the map builds momentum while dragging and continues moving in
13337 // the same direction for some time. Feels especially nice on touch
13338 // devices. Enabled by default.
13339 inertia: true,
13340
13341 // @option inertiaDeceleration: Number = 3000
13342 // The rate with which the inertial movement slows down, in pixels/second².
13343 inertiaDeceleration: 3400, // px/s^2
13344
13345 // @option inertiaMaxSpeed: Number = Infinity
13346 // Max speed of the inertial movement, in pixels/second.
13347 inertiaMaxSpeed: Infinity, // px/s
13348
13349 // @option easeLinearity: Number = 0.2
13350 easeLinearity: 0.2,
13351
13352 // TODO refactor, move to CRS
13353 // @option worldCopyJump: Boolean = false
13354 // With this option enabled, the map tracks when you pan to another "copy"
13355 // of the world and seamlessly jumps to the original one so that all overlays
13356 // like markers and vector layers are still visible.
13357 worldCopyJump: false,
13358
13359 // @option maxBoundsViscosity: Number = 0.0
13360 // If `maxBounds` is set, this option will control how solid the bounds
13361 // are when dragging the map around. The default value of `0.0` allows the
13362 // user to drag outside the bounds at normal speed, higher values will
13363 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13364 // solid, preventing the user from dragging outside the bounds.
13365 maxBoundsViscosity: 0.0
13366 });
13367
13368 var Drag = Handler.extend({
13369 addHooks: function () {
13370 if (!this._draggable) {
13371 var map = this._map;
13372
13373 this._draggable = new Draggable(map._mapPane, map._container);
13374
13375 this._draggable.on({
13376 dragstart: this._onDragStart,
13377 drag: this._onDrag,
13378 dragend: this._onDragEnd
13379 }, this);
13380
13381 this._draggable.on('predrag', this._onPreDragLimit, this);
13382 if (map.options.worldCopyJump) {
13383 this._draggable.on('predrag', this._onPreDragWrap, this);
13384 map.on('zoomend', this._onZoomEnd, this);
13385
13386 map.whenReady(this._onZoomEnd, this);
13387 }
13388 }
13389 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13390 this._draggable.enable();
13391 this._positions = [];
13392 this._times = [];
13393 },
13394
13395 removeHooks: function () {
13396 removeClass(this._map._container, 'leaflet-grab');
13397 removeClass(this._map._container, 'leaflet-touch-drag');
13398 this._draggable.disable();
13399 },
13400
13401 moved: function () {
13402 return this._draggable && this._draggable._moved;
13403 },
13404
13405 moving: function () {
13406 return this._draggable && this._draggable._moving;
13407 },
13408
13409 _onDragStart: function () {
13410 var map = this._map;
13411
13412 map._stop();
13413 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13414 var bounds = toLatLngBounds(this._map.options.maxBounds);
13415
13416 this._offsetLimit = toBounds(
13417 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13418 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13419 .add(this._map.getSize()));
13420
13421 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13422 } else {
13423 this._offsetLimit = null;
13424 }
13425
13426 map
13427 .fire('movestart')
13428 .fire('dragstart');
13429
13430 if (map.options.inertia) {
13431 this._positions = [];
13432 this._times = [];
13433 }
13434 },
13435
13436 _onDrag: function (e) {
13437 if (this._map.options.inertia) {
13438 var time = this._lastTime = +new Date(),
13439 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13440
13441 this._positions.push(pos);
13442 this._times.push(time);
13443
13444 this._prunePositions(time);
13445 }
13446
13447 this._map
13448 .fire('move', e)
13449 .fire('drag', e);
13450 },
13451
13452 _prunePositions: function (time) {
13453 while (this._positions.length > 1 && time - this._times[0] > 50) {
13454 this._positions.shift();
13455 this._times.shift();
13456 }
13457 },
13458
13459 _onZoomEnd: function () {
13460 var pxCenter = this._map.getSize().divideBy(2),
13461 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13462
13463 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13464 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13465 },
13466
13467 _viscousLimit: function (value, threshold) {
13468 return value - (value - threshold) * this._viscosity;
13469 },
13470
13471 _onPreDragLimit: function () {
13472 if (!this._viscosity || !this._offsetLimit) { return; }
13473
13474 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13475
13476 var limit = this._offsetLimit;
13477 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13478 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13479 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13480 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13481
13482 this._draggable._newPos = this._draggable._startPos.add(offset);
13483 },
13484
13485 _onPreDragWrap: function () {
13486 // TODO refactor to be able to adjust map pane position after zoom
13487 var worldWidth = this._worldWidth,
13488 halfWidth = Math.round(worldWidth / 2),
13489 dx = this._initialWorldOffset,
13490 x = this._draggable._newPos.x,
13491 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13492 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13493 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13494
13495 this._draggable._absPos = this._draggable._newPos.clone();
13496 this._draggable._newPos.x = newX;
13497 },
13498
13499 _onDragEnd: function (e) {
13500 var map = this._map,
13501 options = map.options,
13502
13503 noInertia = !options.inertia || e.noInertia || this._times.length < 2;
13504
13505 map.fire('dragend', e);
13506
13507 if (noInertia) {
13508 map.fire('moveend');
13509
13510 } else {
13511 this._prunePositions(+new Date());
13512
13513 var direction = this._lastPos.subtract(this._positions[0]),
13514 duration = (this._lastTime - this._times[0]) / 1000,
13515 ease = options.easeLinearity,
13516
13517 speedVector = direction.multiplyBy(ease / duration),
13518 speed = speedVector.distanceTo([0, 0]),
13519
13520 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13521 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13522
13523 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13524 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13525
13526 if (!offset.x && !offset.y) {
13527 map.fire('moveend');
13528
13529 } else {
13530 offset = map._limitOffset(offset, map.options.maxBounds);
13531
13532 requestAnimFrame(function () {
13533 map.panBy(offset, {
13534 duration: decelerationDuration,
13535 easeLinearity: ease,
13536 noMoveStart: true,
13537 animate: true
13538 });
13539 });
13540 }
13541 }
13542 }
13543 });
13544
13545 // @section Handlers
13546 // @property dragging: Handler
13547 // Map dragging handler (by both mouse and touch).
13548 Map.addInitHook('addHandler', 'dragging', Drag);
13549
13550 /*
13551 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13552 */
13553
13554 // @namespace Map
13555 // @section Keyboard Navigation Options
13556 Map.mergeOptions({
13557 // @option keyboard: Boolean = true
13558 // Makes the map focusable and allows users to navigate the map with keyboard
13559 // arrows and `+`/`-` keys.
13560 keyboard: true,
13561
13562 // @option keyboardPanDelta: Number = 80
13563 // Amount of pixels to pan when pressing an arrow key.
13564 keyboardPanDelta: 80
13565 });
13566
13567 var Keyboard = Handler.extend({
13568
13569 keyCodes: {
13570 left: [37],
13571 right: [39],
13572 down: [40],
13573 up: [38],
13574 zoomIn: [187, 107, 61, 171],
13575 zoomOut: [189, 109, 54, 173]
13576 },
13577
13578 initialize: function (map) {
13579 this._map = map;
13580
13581 this._setPanDelta(map.options.keyboardPanDelta);
13582 this._setZoomDelta(map.options.zoomDelta);
13583 },
13584
13585 addHooks: function () {
13586 var container = this._map._container;
13587
13588 // make the container focusable by tabbing
13589 if (container.tabIndex <= 0) {
13590 container.tabIndex = '0';
13591 }
13592
13593 on(container, {
13594 focus: this._onFocus,
13595 blur: this._onBlur,
13596 mousedown: this._onMouseDown
13597 }, this);
13598
13599 this._map.on({
13600 focus: this._addHooks,
13601 blur: this._removeHooks
13602 }, this);
13603 },
13604
13605 removeHooks: function () {
13606 this._removeHooks();
13607
13608 off(this._map._container, {
13609 focus: this._onFocus,
13610 blur: this._onBlur,
13611 mousedown: this._onMouseDown
13612 }, this);
13613
13614 this._map.off({
13615 focus: this._addHooks,
13616 blur: this._removeHooks
13617 }, this);
13618 },
13619
13620 _onMouseDown: function () {
13621 if (this._focused) { return; }
13622
13623 var body = document.body,
13624 docEl = document.documentElement,
13625 top = body.scrollTop || docEl.scrollTop,
13626 left = body.scrollLeft || docEl.scrollLeft;
13627
13628 this._map._container.focus();
13629
13630 window.scrollTo(left, top);
13631 },
13632
13633 _onFocus: function () {
13634 this._focused = true;
13635 this._map.fire('focus');
13636 },
13637
13638 _onBlur: function () {
13639 this._focused = false;
13640 this._map.fire('blur');
13641 },
13642
13643 _setPanDelta: function (panDelta) {
13644 var keys = this._panKeys = {},
13645 codes = this.keyCodes,
13646 i, len;
13647
13648 for (i = 0, len = codes.left.length; i < len; i++) {
13649 keys[codes.left[i]] = [-1 * panDelta, 0];
13650 }
13651 for (i = 0, len = codes.right.length; i < len; i++) {
13652 keys[codes.right[i]] = [panDelta, 0];
13653 }
13654 for (i = 0, len = codes.down.length; i < len; i++) {
13655 keys[codes.down[i]] = [0, panDelta];
13656 }
13657 for (i = 0, len = codes.up.length; i < len; i++) {
13658 keys[codes.up[i]] = [0, -1 * panDelta];
13659 }
13660 },
13661
13662 _setZoomDelta: function (zoomDelta) {
13663 var keys = this._zoomKeys = {},
13664 codes = this.keyCodes,
13665 i, len;
13666
13667 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13668 keys[codes.zoomIn[i]] = zoomDelta;
13669 }
13670 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13671 keys[codes.zoomOut[i]] = -zoomDelta;
13672 }
13673 },
13674
13675 _addHooks: function () {
13676 on(document, 'keydown', this._onKeyDown, this);
13677 },
13678
13679 _removeHooks: function () {
13680 off(document, 'keydown', this._onKeyDown, this);
13681 },
13682
13683 _onKeyDown: function (e) {
13684 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13685
13686 var key = e.keyCode,
13687 map = this._map,
13688 offset;
13689
13690 if (key in this._panKeys) {
13691 if (!map._panAnim || !map._panAnim._inProgress) {
13692 offset = this._panKeys[key];
13693 if (e.shiftKey) {
13694 offset = toPoint(offset).multiplyBy(3);
13695 }
13696
13697 map.panBy(offset);
13698
13699 if (map.options.maxBounds) {
13700 map.panInsideBounds(map.options.maxBounds);
13701 }
13702 }
13703 } else if (key in this._zoomKeys) {
13704 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13705
13706 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13707 map.closePopup();
13708
13709 } else {
13710 return;
13711 }
13712
13713 stop(e);
13714 }
13715 });
13716
13717 // @section Handlers
13718 // @section Handlers
13719 // @property keyboard: Handler
13720 // Keyboard navigation handler.
13721 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13722
13723 /*
13724 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13725 */
13726
13727 // @namespace Map
13728 // @section Interaction Options
13729 Map.mergeOptions({
13730 // @section Mouse wheel options
13731 // @option scrollWheelZoom: Boolean|String = true
13732 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13733 // it will zoom to the center of the view regardless of where the mouse was.
13734 scrollWheelZoom: true,
13735
13736 // @option wheelDebounceTime: Number = 40
13737 // Limits the rate at which a wheel can fire (in milliseconds). By default
13738 // user can't zoom via wheel more often than once per 40 ms.
13739 wheelDebounceTime: 40,
13740
13741 // @option wheelPxPerZoomLevel: Number = 60
13742 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13743 // mean a change of one full zoom level. Smaller values will make wheel-zooming
13744 // faster (and vice versa).
13745 wheelPxPerZoomLevel: 60
13746 });
13747
13748 var ScrollWheelZoom = Handler.extend({
13749 addHooks: function () {
13750 on(this._map._container, 'wheel', this._onWheelScroll, this);
13751
13752 this._delta = 0;
13753 },
13754
13755 removeHooks: function () {
13756 off(this._map._container, 'wheel', this._onWheelScroll, this);
13757 },
13758
13759 _onWheelScroll: function (e) {
13760 var delta = getWheelDelta(e);
13761
13762 var debounce = this._map.options.wheelDebounceTime;
13763
13764 this._delta += delta;
13765 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13766
13767 if (!this._startTime) {
13768 this._startTime = +new Date();
13769 }
13770
13771 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13772
13773 clearTimeout(this._timer);
13774 this._timer = setTimeout(bind(this._performZoom, this), left);
13775
13776 stop(e);
13777 },
13778
13779 _performZoom: function () {
13780 var map = this._map,
13781 zoom = map.getZoom(),
13782 snap = this._map.options.zoomSnap || 0;
13783
13784 map._stop(); // stop panning and fly animations if any
13785
13786 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13787 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13788 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13789 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13790 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13791
13792 this._delta = 0;
13793 this._startTime = null;
13794
13795 if (!delta) { return; }
13796
13797 if (map.options.scrollWheelZoom === 'center') {
13798 map.setZoom(zoom + delta);
13799 } else {
13800 map.setZoomAround(this._lastMousePos, zoom + delta);
13801 }
13802 }
13803 });
13804
13805 // @section Handlers
13806 // @property scrollWheelZoom: Handler
13807 // Scroll wheel zoom handler.
13808 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13809
13810 /*
13811 * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
13812 * which otherwise is not fired by mobile Safari.
13813 */
13814
13815 var tapHoldDelay = 600;
13816
13817 // @namespace Map
13818 // @section Interaction Options
13819 Map.mergeOptions({
13820 // @section Touch interaction options
13821 // @option tapHold: Boolean
13822 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
13823 tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
13824
13825 // @option tapTolerance: Number = 15
13826 // The max number of pixels a user can shift his finger during touch
13827 // for it to be considered a valid tap.
13828 tapTolerance: 15
13829 });
13830
13831 var TapHold = Handler.extend({
13832 addHooks: function () {
13833 on(this._map._container, 'touchstart', this._onDown, this);
13834 },
13835
13836 removeHooks: function () {
13837 off(this._map._container, 'touchstart', this._onDown, this);
13838 },
13839
13840 _onDown: function (e) {
13841 clearTimeout(this._holdTimeout);
13842 if (e.touches.length !== 1) { return; }
13843
13844 var first = e.touches[0];
13845 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13846
13847 this._holdTimeout = setTimeout(bind(function () {
13848 this._cancel();
13849 if (!this._isTapValid()) { return; }
13850
13851 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
13852 on(document, 'touchend', preventDefault);
13853 on(document, 'touchend touchcancel', this._cancelClickPrevent);
13854 this._simulateEvent('contextmenu', first);
13855 }, this), tapHoldDelay);
13856
13857 on(document, 'touchend touchcancel contextmenu', this._cancel, this);
13858 on(document, 'touchmove', this._onMove, this);
13859 },
13860
13861 _cancelClickPrevent: function cancelClickPrevent() {
13862 off(document, 'touchend', preventDefault);
13863 off(document, 'touchend touchcancel', cancelClickPrevent);
13864 },
13865
13866 _cancel: function () {
13867 clearTimeout(this._holdTimeout);
13868 off(document, 'touchend touchcancel contextmenu', this._cancel, this);
13869 off(document, 'touchmove', this._onMove, this);
13870 },
13871
13872 _onMove: function (e) {
13873 var first = e.touches[0];
13874 this._newPos = new Point(first.clientX, first.clientY);
13875 },
13876
13877 _isTapValid: function () {
13878 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13879 },
13880
13881 _simulateEvent: function (type, e) {
13882 var simulatedEvent = new MouseEvent(type, {
13883 bubbles: true,
13884 cancelable: true,
13885 view: window,
13886 // detail: 1,
13887 screenX: e.screenX,
13888 screenY: e.screenY,
13889 clientX: e.clientX,
13890 clientY: e.clientY,
13891 // button: 2,
13892 // buttons: 2
13893 });
13894
13895 simulatedEvent._simulated = true;
13896
13897 e.target.dispatchEvent(simulatedEvent);
13898 }
13899 });
13900
13901 // @section Handlers
13902 // @property tapHold: Handler
13903 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
13904 Map.addInitHook('addHandler', 'tapHold', TapHold);
13905
13906 /*
13907 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13908 */
13909
13910 // @namespace Map
13911 // @section Interaction Options
13912 Map.mergeOptions({
13913 // @section Touch interaction options
13914 // @option touchZoom: Boolean|String = *
13915 // Whether the map can be zoomed by touch-dragging with two fingers. If
13916 // passed `'center'`, it will zoom to the center of the view regardless of
13917 // where the touch events (fingers) were. Enabled for touch-capable web
13918 // browsers.
13919 touchZoom: Browser.touch,
13920
13921 // @option bounceAtZoomLimits: Boolean = true
13922 // Set it to false if you don't want the map to zoom beyond min/max zoom
13923 // and then bounce back when pinch-zooming.
13924 bounceAtZoomLimits: true
13925 });
13926
13927 var TouchZoom = Handler.extend({
13928 addHooks: function () {
13929 addClass(this._map._container, 'leaflet-touch-zoom');
13930 on(this._map._container, 'touchstart', this._onTouchStart, this);
13931 },
13932
13933 removeHooks: function () {
13934 removeClass(this._map._container, 'leaflet-touch-zoom');
13935 off(this._map._container, 'touchstart', this._onTouchStart, this);
13936 },
13937
13938 _onTouchStart: function (e) {
13939 var map = this._map;
13940 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13941
13942 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13943 p2 = map.mouseEventToContainerPoint(e.touches[1]);
13944
13945 this._centerPoint = map.getSize()._divideBy(2);
13946 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13947 if (map.options.touchZoom !== 'center') {
13948 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13949 }
13950
13951 this._startDist = p1.distanceTo(p2);
13952 this._startZoom = map.getZoom();
13953
13954 this._moved = false;
13955 this._zooming = true;
13956
13957 map._stop();
13958
13959 on(document, 'touchmove', this._onTouchMove, this);
13960 on(document, 'touchend touchcancel', this._onTouchEnd, this);
13961
13962 preventDefault(e);
13963 },
13964
13965 _onTouchMove: function (e) {
13966 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13967
13968 var map = this._map,
13969 p1 = map.mouseEventToContainerPoint(e.touches[0]),
13970 p2 = map.mouseEventToContainerPoint(e.touches[1]),
13971 scale = p1.distanceTo(p2) / this._startDist;
13972
13973 this._zoom = map.getScaleZoom(scale, this._startZoom);
13974
13975 if (!map.options.bounceAtZoomLimits && (
13976 (this._zoom < map.getMinZoom() && scale < 1) ||
13977 (this._zoom > map.getMaxZoom() && scale > 1))) {
13978 this._zoom = map._limitZoom(this._zoom);
13979 }
13980
13981 if (map.options.touchZoom === 'center') {
13982 this._center = this._startLatLng;
13983 if (scale === 1) { return; }
13984 } else {
13985 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13986 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13987 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13988 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13989 }
13990
13991 if (!this._moved) {
13992 map._moveStart(true, false);
13993 this._moved = true;
13994 }
13995
13996 cancelAnimFrame(this._animRequest);
13997
13998 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13999 this._animRequest = requestAnimFrame(moveFn, this, true);
14000
14001 preventDefault(e);
14002 },
14003
14004 _onTouchEnd: function () {
14005 if (!this._moved || !this._zooming) {
14006 this._zooming = false;
14007 return;
14008 }
14009
14010 this._zooming = false;
14011 cancelAnimFrame(this._animRequest);
14012
14013 off(document, 'touchmove', this._onTouchMove, this);
14014 off(document, 'touchend touchcancel', this._onTouchEnd, this);
14015
14016 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
14017 if (this._map.options.zoomAnimation) {
14018 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
14019 } else {
14020 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
14021 }
14022 }
14023 });
14024
14025 // @section Handlers
14026 // @property touchZoom: Handler
14027 // Touch zoom handler.
14028 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
14029
14030 Map.BoxZoom = BoxZoom;
14031 Map.DoubleClickZoom = DoubleClickZoom;
14032 Map.Drag = Drag;
14033 Map.Keyboard = Keyboard;
14034 Map.ScrollWheelZoom = ScrollWheelZoom;
14035 Map.TapHold = TapHold;
14036 Map.TouchZoom = TouchZoom;
14037
14038 exports.Bounds = Bounds;
14039 exports.Browser = Browser;
14040 exports.CRS = CRS;
14041 exports.Canvas = Canvas;
14042 exports.Circle = Circle;
14043 exports.CircleMarker = CircleMarker;
14044 exports.Class = Class;
14045 exports.Control = Control;
14046 exports.DivIcon = DivIcon;
14047 exports.DivOverlay = DivOverlay;
14048 exports.DomEvent = DomEvent;
14049 exports.DomUtil = DomUtil;
14050 exports.Draggable = Draggable;
14051 exports.Evented = Evented;
14052 exports.FeatureGroup = FeatureGroup;
14053 exports.GeoJSON = GeoJSON;
14054 exports.GridLayer = GridLayer;
14055 exports.Handler = Handler;
14056 exports.Icon = Icon;
14057 exports.ImageOverlay = ImageOverlay;
14058 exports.LatLng = LatLng;
14059 exports.LatLngBounds = LatLngBounds;
14060 exports.Layer = Layer;
14061 exports.LayerGroup = LayerGroup;
14062 exports.LineUtil = LineUtil;
14063 exports.Map = Map;
14064 exports.Marker = Marker;
14065 exports.Mixin = Mixin;
14066 exports.Path = Path;
14067 exports.Point = Point;
14068 exports.PolyUtil = PolyUtil;
14069 exports.Polygon = Polygon;
14070 exports.Polyline = Polyline;
14071 exports.Popup = Popup;
14072 exports.PosAnimation = PosAnimation;
14073 exports.Projection = index;
14074 exports.Rectangle = Rectangle;
14075 exports.Renderer = Renderer;
14076 exports.SVG = SVG;
14077 exports.SVGOverlay = SVGOverlay;
14078 exports.TileLayer = TileLayer;
14079 exports.Tooltip = Tooltip;
14080 exports.Transformation = Transformation;
14081 exports.Util = Util;
14082 exports.VideoOverlay = VideoOverlay;
14083 exports.bind = bind;
14084 exports.bounds = toBounds;
14085 exports.canvas = canvas;
14086 exports.circle = circle;
14087 exports.circleMarker = circleMarker;
14088 exports.control = control;
14089 exports.divIcon = divIcon;
14090 exports.extend = extend;
14091 exports.featureGroup = featureGroup;
14092 exports.geoJSON = geoJSON;
14093 exports.geoJson = geoJson;
14094 exports.gridLayer = gridLayer;
14095 exports.icon = icon;
14096 exports.imageOverlay = imageOverlay;
14097 exports.latLng = toLatLng;
14098 exports.latLngBounds = toLatLngBounds;
14099 exports.layerGroup = layerGroup;
14100 exports.map = createMap;
14101 exports.marker = marker;
14102 exports.point = toPoint;
14103 exports.polygon = polygon;
14104 exports.polyline = polyline;
14105 exports.popup = popup;
14106 exports.rectangle = rectangle;
14107 exports.setOptions = setOptions;
14108 exports.stamp = stamp;
14109 exports.svg = svg;
14110 exports.svgOverlay = svgOverlay;
14111 exports.tileLayer = tileLayer;
14112 exports.tooltip = tooltip;
14113 exports.transformation = toTransformation;
14114 exports.version = version;
14115 exports.videoOverlay = videoOverlay;
14116
14117 var oldL = window.L;
14118 exports.noConflict = function() {
14119 window.L = oldL;
14120 return this;
14121 }
14122 // Always export us to window global (see #2364)
14123 window.L = exports;
14124
14125}));
14126//# sourceMappingURL=leaflet-src.js.map