PluginProbe
WP Directory Kit / 1.3.2
WP Directory Kit v1.3.2
1.5.6 1.5.5 1.5.4 1.5.3 trunk 1.1.0 1.1.6 1.1.8 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.8 1.4.0 1.4.1 All 35 releases
wpdirectorykit / public / js / openstreetmap / leaflet.js

leaflet.js in WP Directory Kit 1.3.2, at public/js/openstreetmap/leaflet.js

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