2 * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com
3 * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade
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';
12 var version = "1.9.4";
17 * Various utility functions, used by Leaflet internally.
\r
20 // @function extend(dest: Object, src?: Object): Object
\r
21 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
\r
22 function extend(dest) {
\r
25 for (j = 1, len = arguments.length; j < len; j++) {
\r
34 // @function create(proto: Object, properties?: Object): Object
\r
35 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
\r
36 var create$2 = Object.create || (function () {
\r
38 return function (proto) {
\r
39 F.prototype = proto;
\r
44 // @function bind(fn: Function, …): Function
\r
45 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
\r
46 // Has a `L.bind()` shortcut.
\r
47 function bind(fn, obj) {
\r
48 var slice = Array.prototype.slice;
\r
51 return fn.bind.apply(fn, slice.call(arguments, 1));
\r
54 var args = slice.call(arguments, 2);
\r
56 return function () {
\r
57 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
\r
61 // @property lastId: Number
\r
62 // Last unique ID used by [`stamp()`](#util-stamp)
\r
65 // @function stamp(obj: Object): Number
\r
66 // Returns the unique ID of an object, assigning it one if it doesn't have it.
\r
67 function stamp(obj) {
\r
68 if (!('_leaflet_id' in obj)) {
\r
69 obj['_leaflet_id'] = ++lastId;
\r
71 return obj._leaflet_id;
\r
74 // @function throttle(fn: Function, time: Number, context: Object): Function
\r
75 // Returns a function which executes function `fn` with the given scope `context`
\r
76 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
\r
77 // `fn` will be called no more than one time per given amount of `time`. The arguments
\r
78 // received by the bound function will be any arguments passed when binding the
\r
79 // function, followed by any arguments passed when invoking the bound function.
\r
80 // Has an `L.throttle` shortcut.
\r
81 function throttle(fn, time, context) {
\r
82 var lock, args, wrapperFn, later;
\r
84 later = function () {
\r
85 // reset lock and call if queued
\r
88 wrapperFn.apply(context, args);
\r
93 wrapperFn = function () {
\r
95 // called too soon, queue to call later
\r
99 // call and lock until later
\r
100 fn.apply(context, arguments);
\r
101 setTimeout(later, time);
\r
109 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
\r
110 // Returns the number `num` modulo `range` in such a way so it lies within
\r
111 // `range[0]` and `range[1]`. The returned value will be always smaller than
\r
112 // `range[1]` unless `includeMax` is set to `true`.
\r
113 function wrapNum(x, range, includeMax) {
\r
114 var max = range[1],
\r
117 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
\r
120 // @function falseFn(): Function
\r
121 // Returns a function which always returns `false`.
\r
122 function falseFn() { return false; }
\r
124 // @function formatNum(num: Number, precision?: Number|false): Number
\r
125 // Returns the number `num` rounded with specified `precision`.
\r
126 // The default `precision` value is 6 decimal places.
\r
127 // `false` can be passed to skip any processing (can be useful to avoid round-off errors).
\r
128 function formatNum(num, precision) {
\r
129 if (precision === false) { return num; }
\r
130 var pow = Math.pow(10, precision === undefined ? 6 : precision);
\r
131 return Math.round(num * pow) / pow;
\r
134 // @function trim(str: String): String
\r
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
\r
136 function trim(str) {
\r
137 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
\r
140 // @function splitWords(str: String): String[]
\r
141 // Trims and splits the string on whitespace and returns the array of parts.
\r
142 function splitWords(str) {
\r
143 return trim(str).split(/\s+/);
\r
146 // @function setOptions(obj: Object, options: Object): Object
\r
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
\r
148 function setOptions(obj, options) {
\r
149 if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
\r
150 obj.options = obj.options ? create$2(obj.options) : {};
\r
152 for (var i in options) {
\r
153 obj.options[i] = options[i];
\r
155 return obj.options;
\r
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
\r
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
\r
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
\r
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
\r
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
\r
163 function getParamString(obj, existingUrl, uppercase) {
\r
165 for (var i in obj) {
\r
166 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
\r
168 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
\r
171 var templateRe = /\{ *([\w_ -]+) *\}/g;
\r
173 // @function template(str: String, data: Object): String
\r
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
\r
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
\r
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
\r
177 // data values — they will be evaluated passing `data` as an argument.
\r
178 function template(str, data) {
\r
179 return str.replace(templateRe, function (str, key) {
\r
180 var value = data[key];
\r
182 if (value === undefined) {
\r
183 throw new Error('No value provided for variable ' + str);
\r
185 } else if (typeof value === 'function') {
\r
186 value = value(data);
\r
192 // @function isArray(obj): Boolean
\r
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
\r
194 var isArray = Array.isArray || function (obj) {
\r
195 return (Object.prototype.toString.call(obj) === '[object Array]');
\r
198 // @function indexOf(array: Array, el: Object): Number
\r
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
\r
200 function indexOf(array, el) {
\r
201 for (var i = 0; i < array.length; i++) {
\r
202 if (array[i] === el) { return i; }
\r
207 // @property emptyImageUrl: String
\r
208 // Data URI string containing a base64-encoded empty GIF image.
\r
209 // Used as a hack to free memory from unused images on WebKit-powered
\r
210 // mobile devices (by setting image `src` to this string).
\r
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
\r
213 // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/
\r
215 function getPrefixed(name) {
\r
216 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
\r
221 // fallback for IE 7-8
\r
222 function timeoutDefer(fn) {
\r
223 var time = +new Date(),
\r
224 timeToCall = Math.max(0, 16 - (time - lastTime));
\r
226 lastTime = time + timeToCall;
\r
227 return window.setTimeout(fn, timeToCall);
\r
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
\r
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
\r
232 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
\r
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
\r
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
\r
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
\r
237 // the browser doesn't have native support for
\r
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
\r
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
\r
240 function requestAnimFrame(fn, context, immediate) {
\r
241 if (immediate && requestFn === timeoutDefer) {
\r
244 return requestFn.call(window, bind(fn, context));
\r
248 // @function cancelAnimFrame(id: Number): undefined
\r
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
\r
250 function cancelAnimFrame(id) {
\r
252 cancelFn.call(window, id);
\r
261 get lastId () { return lastId; },
266 formatNum: formatNum,
268 splitWords: splitWords,
269 setOptions: setOptions,
270 getParamString: getParamString,
274 emptyImageUrl: emptyImageUrl,
275 requestFn: requestFn,
277 requestAnimFrame: requestAnimFrame,
278 cancelAnimFrame: cancelAnimFrame
287 // Thanks to John Resig and Dean Edwards for inspiration!
\r
289 function Class() {}
\r
291 Class.extend = function (props) {
\r
293 // @function extend(props: Object): Function
\r
294 // [Extends the current class](#class-inheritance) given the properties to be included.
\r
295 // Returns a Javascript function that is a class constructor (to be called with `new`).
\r
296 var NewClass = function () {
\r
300 // call the constructor
\r
301 if (this.initialize) {
\r
302 this.initialize.apply(this, arguments);
\r
305 // call all constructor hooks
\r
306 this.callInitHooks();
\r
309 var parentProto = NewClass.__super__ = this.prototype;
\r
311 var proto = create$2(parentProto);
\r
312 proto.constructor = NewClass;
\r
314 NewClass.prototype = proto;
\r
316 // inherit parent's statics
\r
317 for (var i in this) {
\r
318 if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
\r
319 NewClass[i] = this[i];
\r
323 // mix static properties into the class
\r
324 if (props.statics) {
\r
325 extend(NewClass, props.statics);
\r
328 // mix includes into the prototype
\r
329 if (props.includes) {
\r
330 checkDeprecatedMixinEvents(props.includes);
\r
331 extend.apply(null, [proto].concat(props.includes));
\r
334 // mix given properties into the prototype
\r
335 extend(proto, props);
\r
336 delete proto.statics;
\r
337 delete proto.includes;
\r
340 if (proto.options) {
\r
341 proto.options = parentProto.options ? create$2(parentProto.options) : {};
\r
342 extend(proto.options, props.options);
\r
345 proto._initHooks = [];
\r
347 // add method for calling all hooks
\r
348 proto.callInitHooks = function () {
\r
350 if (this._initHooksCalled) { return; }
\r
352 if (parentProto.callInitHooks) {
\r
353 parentProto.callInitHooks.call(this);
\r
356 this._initHooksCalled = true;
\r
358 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
\r
359 proto._initHooks[i].call(this);
\r
367 // @function include(properties: Object): this
\r
368 // [Includes a mixin](#class-includes) into the current class.
\r
369 Class.include = function (props) {
\r
370 var parentOptions = this.prototype.options;
\r
371 extend(this.prototype, props);
\r
372 if (props.options) {
\r
373 this.prototype.options = parentOptions;
\r
374 this.mergeOptions(props.options);
\r
379 // @function mergeOptions(options: Object): this
\r
380 // [Merges `options`](#class-options) into the defaults of the class.
\r
381 Class.mergeOptions = function (options) {
\r
382 extend(this.prototype.options, options);
\r
386 // @function addInitHook(fn: Function): this
\r
387 // Adds a [constructor hook](#class-constructor-hooks) to the class.
\r
388 Class.addInitHook = function (fn) { // (Function) || (String, args...)
\r
389 var args = Array.prototype.slice.call(arguments, 1);
\r
391 var init = typeof fn === 'function' ? fn : function () {
\r
392 this[fn].apply(this, args);
\r
395 this.prototype._initHooks = this.prototype._initHooks || [];
\r
396 this.prototype._initHooks.push(init);
\r
400 function checkDeprecatedMixinEvents(includes) {
\r
401 /* global L: true */
\r
402 if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
\r
404 includes = isArray(includes) ? includes : [includes];
\r
406 for (var i = 0; i < includes.length; i++) {
\r
407 if (includes[i] === L.Mixin.Events) {
\r
408 console.warn('Deprecated include of L.Mixin.Events: ' +
\r
409 'this property will be removed in future releases, ' +
\r
410 'please inherit from L.Evented instead.', new Error().stack);
\r
420 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
\r
425 * map.on('click', function(e) {
\r
430 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
\r
433 * function onClick(e) { ... }
\r
435 * map.on('click', onClick);
\r
436 * map.off('click', onClick);
\r
441 /* @method on(type: String, fn: Function, context?: Object): this
\r
442 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
\r
445 * @method on(eventMap: Object): this
\r
446 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
\r
448 on: function (types, fn, context) {
\r
450 // types can be a map of types/handlers
\r
451 if (typeof types === 'object') {
\r
452 for (var type in types) {
\r
453 // we don't process space-separated events here for performance;
\r
454 // it's a hot path since Layer uses the on(obj) syntax
\r
455 this._on(type, types[type], fn);
\r
459 // types can be a string of space-separated words
\r
460 types = splitWords(types);
\r
462 for (var i = 0, len = types.length; i < len; i++) {
\r
463 this._on(types[i], fn, context);
\r
470 /* @method off(type: String, fn?: Function, context?: Object): this
\r
471 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
\r
474 * @method off(eventMap: Object): this
\r
475 * Removes a set of type/listener pairs.
\r
478 * @method off: this
\r
479 * Removes all listeners to all events on the object. This includes implicitly attached events.
\r
481 off: function (types, fn, context) {
\r
483 if (!arguments.length) {
\r
484 // clear all listeners if called without arguments
\r
485 delete this._events;
\r
487 } else if (typeof types === 'object') {
\r
488 for (var type in types) {
\r
489 this._off(type, types[type], fn);
\r
493 types = splitWords(types);
\r
495 var removeAll = arguments.length === 1;
\r
496 for (var i = 0, len = types.length; i < len; i++) {
\r
498 this._off(types[i]);
\r
500 this._off(types[i], fn, context);
\r
508 // attach listener (without syntactic sugar now)
\r
509 _on: function (type, fn, context, _once) {
\r
510 if (typeof fn !== 'function') {
\r
511 console.warn('wrong listener type: ' + typeof fn);
\r
515 // check if fn already there
\r
516 if (this._listens(type, fn, context) !== false) {
\r
520 if (context === this) {
\r
521 // Less memory footprint.
\r
522 context = undefined;
\r
525 var newListener = {fn: fn, ctx: context};
\r
527 newListener.once = true;
\r
530 this._events = this._events || {};
\r
531 this._events[type] = this._events[type] || [];
\r
532 this._events[type].push(newListener);
\r
535 _off: function (type, fn, context) {
\r
540 if (!this._events) {
\r
544 listeners = this._events[type];
\r
549 if (arguments.length === 1) { // remove all
\r
550 if (this._firingCount) {
\r
551 // Set all removed listeners to noop
\r
552 // so they are not called if remove happens in fire
\r
553 for (i = 0, len = listeners.length; i < len; i++) {
\r
554 listeners[i].fn = falseFn;
\r
557 // clear all listeners for a type if function isn't specified
\r
558 delete this._events[type];
\r
562 if (typeof fn !== 'function') {
\r
563 console.warn('wrong listener type: ' + typeof fn);
\r
567 // find fn and remove it
\r
568 var index = this._listens(type, fn, context);
\r
569 if (index !== false) {
\r
570 var listener = listeners[index];
\r
571 if (this._firingCount) {
\r
572 // set the removed listener to noop so that's not called if remove happens in fire
\r
573 listener.fn = falseFn;
\r
575 /* copy array in case events are being fired */
\r
576 this._events[type] = listeners = listeners.slice();
\r
578 listeners.splice(index, 1);
\r
582 // @method fire(type: String, data?: Object, propagate?: Boolean): this
\r
583 // Fires an event of the specified type. You can optionally provide a data
\r
584 // object — the first argument of the listener function will contain its
\r
585 // properties. The event can optionally be propagated to event parents.
\r
586 fire: function (type, data, propagate) {
\r
587 if (!this.listens(type, propagate)) { return this; }
\r
589 var event = extend({}, data, {
\r
592 sourceTarget: data && data.sourceTarget || this
\r
595 if (this._events) {
\r
596 var listeners = this._events[type];
\r
598 this._firingCount = (this._firingCount + 1) || 1;
\r
599 for (var i = 0, len = listeners.length; i < len; i++) {
\r
600 var l = listeners[i];
\r
601 // off overwrites l.fn, so we need to copy fn to a var
\r
604 this.off(type, fn, l.ctx);
\r
606 fn.call(l.ctx || this, event);
\r
609 this._firingCount--;
\r
614 // propagate the event to parents (set with addEventParent)
\r
615 this._propagateEvent(event);
\r
621 // @method listens(type: String, propagate?: Boolean): Boolean
\r
622 // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
\r
623 // Returns `true` if a particular event type has any listeners attached to it.
\r
624 // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
\r
625 listens: function (type, fn, context, propagate) {
\r
626 if (typeof type !== 'string') {
\r
627 console.warn('"string" type argument expected');
\r
630 // we don't overwrite the input `fn` value, because we need to use it for propagation
\r
632 if (typeof fn !== 'function') {
\r
635 context = undefined;
\r
638 var listeners = this._events && this._events[type];
\r
639 if (listeners && listeners.length) {
\r
640 if (this._listens(type, _fn, context) !== false) {
\r
646 // also check parents for listeners if event propagates
\r
647 for (var id in this._eventParents) {
\r
648 if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }
\r
654 // returns the index (number) or false
\r
655 _listens: function (type, fn, context) {
\r
656 if (!this._events) {
\r
660 var listeners = this._events[type] || [];
\r
662 return !!listeners.length;
\r
665 if (context === this) {
\r
666 // Less memory footprint.
\r
667 context = undefined;
\r
670 for (var i = 0, len = listeners.length; i < len; i++) {
\r
671 if (listeners[i].fn === fn && listeners[i].ctx === context) {
\r
679 // @method once(…): this
\r
680 // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
\r
681 once: function (types, fn, context) {
\r
683 // types can be a map of types/handlers
\r
684 if (typeof types === 'object') {
\r
685 for (var type in types) {
\r
686 // we don't process space-separated events here for performance;
\r
687 // it's a hot path since Layer uses the on(obj) syntax
\r
688 this._on(type, types[type], fn, true);
\r
692 // types can be a string of space-separated words
\r
693 types = splitWords(types);
\r
695 for (var i = 0, len = types.length; i < len; i++) {
\r
696 this._on(types[i], fn, context, true);
\r
703 // @method addEventParent(obj: Evented): this
\r
704 // Adds an event parent - an `Evented` that will receive propagated events
\r
705 addEventParent: function (obj) {
\r
706 this._eventParents = this._eventParents || {};
\r
707 this._eventParents[stamp(obj)] = obj;
\r
711 // @method removeEventParent(obj: Evented): this
\r
712 // Removes an event parent, so it will stop receiving propagated events
\r
713 removeEventParent: function (obj) {
\r
714 if (this._eventParents) {
\r
715 delete this._eventParents[stamp(obj)];
\r
720 _propagateEvent: function (e) {
\r
721 for (var id in this._eventParents) {
\r
722 this._eventParents[id].fire(e.type, extend({
\r
724 propagatedFrom: e.target
\r
730 // aliases; we should ditch those eventually
\r
732 // @method addEventListener(…): this
\r
733 // Alias to [`on(…)`](#evented-on)
\r
734 Events.addEventListener = Events.on;
\r
736 // @method removeEventListener(…): this
\r
737 // Alias to [`off(…)`](#evented-off)
\r
739 // @method clearAllEventListeners(…): this
\r
740 // Alias to [`off()`](#evented-off)
\r
741 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
\r
743 // @method addOneTimeEventListener(…): this
\r
744 // Alias to [`once(…)`](#evented-once)
\r
745 Events.addOneTimeEventListener = Events.once;
\r
747 // @method fireEvent(…): this
\r
748 // Alias to [`fire(…)`](#evented-fire)
\r
749 Events.fireEvent = Events.fire;
\r
751 // @method hasEventListeners(…): Boolean
\r
752 // Alias to [`listens(…)`](#evented-listens)
\r
753 Events.hasEventListeners = Events.listens;
\r
755 var Evented = Class.extend(Events);
761 * Represents a point with `x` and `y` coordinates in pixels.
\r
766 * var point = L.point(200, 300);
\r
769 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
\r
772 * map.panBy([200, 300]);
\r
773 * map.panBy(L.point(200, 300));
\r
776 * Note that `Point` does not inherit from Leaflet's `Class` object,
\r
777 * which means new classes can't inherit from it, and new methods
\r
778 * can't be added to it with the `include` function.
\r
781 function Point(x, y, round) {
\r
782 // @property x: Number; The `x` coordinate of the point
\r
783 this.x = (round ? Math.round(x) : x);
\r
784 // @property y: Number; The `y` coordinate of the point
\r
785 this.y = (round ? Math.round(y) : y);
\r
788 var trunc = Math.trunc || function (v) {
\r
789 return v > 0 ? Math.floor(v) : Math.ceil(v);
\r
792 Point.prototype = {
\r
794 // @method clone(): Point
\r
795 // Returns a copy of the current point.
\r
796 clone: function () {
\r
797 return new Point(this.x, this.y);
\r
800 // @method add(otherPoint: Point): Point
\r
801 // Returns the result of addition of the current and the given points.
\r
802 add: function (point) {
\r
803 // non-destructive, returns a new point
\r
804 return this.clone()._add(toPoint(point));
\r
807 _add: function (point) {
\r
808 // destructive, used directly for performance in situations where it's safe to modify existing point
\r
814 // @method subtract(otherPoint: Point): Point
\r
815 // Returns the result of subtraction of the given point from the current.
\r
816 subtract: function (point) {
\r
817 return this.clone()._subtract(toPoint(point));
\r
820 _subtract: function (point) {
\r
826 // @method divideBy(num: Number): Point
\r
827 // Returns the result of division of the current point by the given number.
\r
828 divideBy: function (num) {
\r
829 return this.clone()._divideBy(num);
\r
832 _divideBy: function (num) {
\r
838 // @method multiplyBy(num: Number): Point
\r
839 // Returns the result of multiplication of the current point by the given number.
\r
840 multiplyBy: function (num) {
\r
841 return this.clone()._multiplyBy(num);
\r
844 _multiplyBy: function (num) {
\r
850 // @method scaleBy(scale: Point): Point
\r
851 // Multiply each coordinate of the current point by each coordinate of
\r
852 // `scale`. In linear algebra terms, multiply the point by the
\r
853 // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
\r
854 // defined by `scale`.
\r
855 scaleBy: function (point) {
\r
856 return new Point(this.x * point.x, this.y * point.y);
\r
859 // @method unscaleBy(scale: Point): Point
\r
860 // Inverse of `scaleBy`. Divide each coordinate of the current point by
\r
861 // each coordinate of `scale`.
\r
862 unscaleBy: function (point) {
\r
863 return new Point(this.x / point.x, this.y / point.y);
\r
866 // @method round(): Point
\r
867 // Returns a copy of the current point with rounded coordinates.
\r
868 round: function () {
\r
869 return this.clone()._round();
\r
872 _round: function () {
\r
873 this.x = Math.round(this.x);
\r
874 this.y = Math.round(this.y);
\r
878 // @method floor(): Point
\r
879 // Returns a copy of the current point with floored coordinates (rounded down).
\r
880 floor: function () {
\r
881 return this.clone()._floor();
\r
884 _floor: function () {
\r
885 this.x = Math.floor(this.x);
\r
886 this.y = Math.floor(this.y);
\r
890 // @method ceil(): Point
\r
891 // Returns a copy of the current point with ceiled coordinates (rounded up).
\r
892 ceil: function () {
\r
893 return this.clone()._ceil();
\r
896 _ceil: function () {
\r
897 this.x = Math.ceil(this.x);
\r
898 this.y = Math.ceil(this.y);
\r
902 // @method trunc(): Point
\r
903 // Returns a copy of the current point with truncated coordinates (rounded towards zero).
\r
904 trunc: function () {
\r
905 return this.clone()._trunc();
\r
908 _trunc: function () {
\r
909 this.x = trunc(this.x);
\r
910 this.y = trunc(this.y);
\r
914 // @method distanceTo(otherPoint: Point): Number
\r
915 // Returns the cartesian distance between the current and the given points.
\r
916 distanceTo: function (point) {
\r
917 point = toPoint(point);
\r
919 var x = point.x - this.x,
\r
920 y = point.y - this.y;
\r
922 return Math.sqrt(x * x + y * y);
\r
925 // @method equals(otherPoint: Point): Boolean
\r
926 // Returns `true` if the given point has the same coordinates.
\r
927 equals: function (point) {
\r
928 point = toPoint(point);
\r
930 return point.x === this.x &&
\r
931 point.y === this.y;
\r
934 // @method contains(otherPoint: Point): Boolean
\r
935 // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
\r
936 contains: function (point) {
\r
937 point = toPoint(point);
\r
939 return Math.abs(point.x) <= Math.abs(this.x) &&
\r
940 Math.abs(point.y) <= Math.abs(this.y);
\r
943 // @method toString(): String
\r
944 // Returns a string representation of the point for debugging purposes.
\r
945 toString: function () {
\r
947 formatNum(this.x) + ', ' +
\r
948 formatNum(this.y) + ')';
\r
952 // @factory L.point(x: Number, y: Number, round?: Boolean)
\r
953 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
\r
956 // @factory L.point(coords: Number[])
\r
957 // Expects an array of the form `[x, y]` instead.
\r
960 // @factory L.point(coords: Object)
\r
961 // Expects a plain object of the form `{x: Number, y: Number}` instead.
\r
962 function toPoint(x, y, round) {
\r
963 if (x instanceof Point) {
\r
967 return new Point(x[0], x[1]);
\r
969 if (x === undefined || x === null) {
\r
972 if (typeof x === 'object' && 'x' in x && 'y' in x) {
\r
973 return new Point(x.x, x.y);
\r
975 return new Point(x, y, round);
\r
982 * Represents a rectangular area in pixel coordinates.
\r
987 * var p1 = L.point(10, 10),
\r
988 * p2 = L.point(40, 60),
\r
989 * bounds = L.bounds(p1, p2);
\r
992 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
\r
995 * otherBounds.intersects([[10, 10], [40, 60]]);
\r
998 * Note that `Bounds` does not inherit from Leaflet's `Class` object,
\r
999 * which means new classes can't inherit from it, and new methods
\r
1000 * can't be added to it with the `include` function.
\r
1003 function Bounds(a, b) {
\r
1004 if (!a) { return; }
\r
1006 var points = b ? [a, b] : a;
\r
1008 for (var i = 0, len = points.length; i < len; i++) {
\r
1009 this.extend(points[i]);
\r
1013 Bounds.prototype = {
\r
1014 // @method extend(point: Point): this
\r
1015 // Extends the bounds to contain the given point.
\r
1018 // @method extend(otherBounds: Bounds): this
\r
1019 // Extend the bounds to contain the given bounds
\r
1020 extend: function (obj) {
\r
1022 if (!obj) { return this; }
\r
1024 if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {
\r
1025 min2 = max2 = toPoint(obj);
\r
1027 obj = toBounds(obj);
\r
1031 if (!min2 || !max2) { return this; }
\r
1034 // @property min: Point
\r
1035 // The top left corner of the rectangle.
\r
1036 // @property max: Point
\r
1037 // The bottom right corner of the rectangle.
\r
1038 if (!this.min && !this.max) {
\r
1039 this.min = min2.clone();
\r
1040 this.max = max2.clone();
\r
1042 this.min.x = Math.min(min2.x, this.min.x);
\r
1043 this.max.x = Math.max(max2.x, this.max.x);
\r
1044 this.min.y = Math.min(min2.y, this.min.y);
\r
1045 this.max.y = Math.max(max2.y, this.max.y);
\r
1050 // @method getCenter(round?: Boolean): Point
\r
1051 // Returns the center point of the bounds.
\r
1052 getCenter: function (round) {
\r
1054 (this.min.x + this.max.x) / 2,
\r
1055 (this.min.y + this.max.y) / 2, round);
\r
1058 // @method getBottomLeft(): Point
\r
1059 // Returns the bottom-left point of the bounds.
\r
1060 getBottomLeft: function () {
\r
1061 return toPoint(this.min.x, this.max.y);
\r
1064 // @method getTopRight(): Point
\r
1065 // Returns the top-right point of the bounds.
\r
1066 getTopRight: function () { // -> Point
\r
1067 return toPoint(this.max.x, this.min.y);
\r
1070 // @method getTopLeft(): Point
\r
1071 // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
\r
1072 getTopLeft: function () {
\r
1073 return this.min; // left, top
\r
1076 // @method getBottomRight(): Point
\r
1077 // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
\r
1078 getBottomRight: function () {
\r
1079 return this.max; // right, bottom
\r
1082 // @method getSize(): Point
\r
1083 // Returns the size of the given bounds
\r
1084 getSize: function () {
\r
1085 return this.max.subtract(this.min);
\r
1088 // @method contains(otherBounds: Bounds): Boolean
\r
1089 // Returns `true` if the rectangle contains the given one.
\r
1091 // @method contains(point: Point): Boolean
\r
1092 // Returns `true` if the rectangle contains the given point.
\r
1093 contains: function (obj) {
\r
1096 if (typeof obj[0] === 'number' || obj instanceof Point) {
\r
1097 obj = toPoint(obj);
\r
1099 obj = toBounds(obj);
\r
1102 if (obj instanceof Bounds) {
\r
1109 return (min.x >= this.min.x) &&
\r
1110 (max.x <= this.max.x) &&
\r
1111 (min.y >= this.min.y) &&
\r
1112 (max.y <= this.max.y);
\r
1115 // @method intersects(otherBounds: Bounds): Boolean
\r
1116 // Returns `true` if the rectangle intersects the given bounds. Two bounds
\r
1117 // intersect if they have at least one point in common.
\r
1118 intersects: function (bounds) { // (Bounds) -> Boolean
\r
1119 bounds = toBounds(bounds);
\r
1121 var min = this.min,
\r
1123 min2 = bounds.min,
\r
1124 max2 = bounds.max,
\r
1125 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
\r
1126 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
\r
1128 return xIntersects && yIntersects;
\r
1131 // @method overlaps(otherBounds: Bounds): Boolean
\r
1132 // Returns `true` if the rectangle overlaps the given bounds. Two bounds
\r
1133 // overlap if their intersection is an area.
\r
1134 overlaps: function (bounds) { // (Bounds) -> Boolean
\r
1135 bounds = toBounds(bounds);
\r
1137 var min = this.min,
\r
1139 min2 = bounds.min,
\r
1140 max2 = bounds.max,
\r
1141 xOverlaps = (max2.x > min.x) && (min2.x < max.x),
\r
1142 yOverlaps = (max2.y > min.y) && (min2.y < max.y);
\r
1144 return xOverlaps && yOverlaps;
\r
1147 // @method isValid(): Boolean
\r
1148 // Returns `true` if the bounds are properly initialized.
\r
1149 isValid: function () {
\r
1150 return !!(this.min && this.max);
\r
1154 // @method pad(bufferRatio: Number): Bounds
\r
1155 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
\r
1156 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
\r
1157 // Negative values will retract the bounds.
\r
1158 pad: function (bufferRatio) {
\r
1159 var min = this.min,
\r
1161 heightBuffer = Math.abs(min.x - max.x) * bufferRatio,
\r
1162 widthBuffer = Math.abs(min.y - max.y) * bufferRatio;
\r
1166 toPoint(min.x - heightBuffer, min.y - widthBuffer),
\r
1167 toPoint(max.x + heightBuffer, max.y + widthBuffer));
\r
1171 // @method equals(otherBounds: Bounds): Boolean
\r
1172 // Returns `true` if the rectangle is equivalent to the given bounds.
\r
1173 equals: function (bounds) {
\r
1174 if (!bounds) { return false; }
\r
1176 bounds = toBounds(bounds);
\r
1178 return this.min.equals(bounds.getTopLeft()) &&
\r
1179 this.max.equals(bounds.getBottomRight());
\r
1184 // @factory L.bounds(corner1: Point, corner2: Point)
\r
1185 // Creates a Bounds object from two corners coordinate pairs.
\r
1187 // @factory L.bounds(points: Point[])
\r
1188 // Creates a Bounds object from the given array of points.
\r
1189 function toBounds(a, b) {
\r
1190 if (!a || a instanceof Bounds) {
\r
1193 return new Bounds(a, b);
\r
1197 * @class LatLngBounds
\r
1198 * @aka L.LatLngBounds
\r
1200 * Represents a rectangular geographical area on a map.
\r
1205 * var corner1 = L.latLng(40.712, -74.227),
\r
1206 * corner2 = L.latLng(40.774, -74.125),
\r
1207 * bounds = L.latLngBounds(corner1, corner2);
\r
1210 * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
\r
1214 * [40.712, -74.227],
\r
1215 * [40.774, -74.125]
\r
1219 * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
\r
1221 * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
\r
1222 * which means new classes can't inherit from it, and new methods
\r
1223 * can't be added to it with the `include` function.
\r
1226 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
\r
1227 if (!corner1) { return; }
\r
1229 var latlngs = corner2 ? [corner1, corner2] : corner1;
\r
1231 for (var i = 0, len = latlngs.length; i < len; i++) {
\r
1232 this.extend(latlngs[i]);
\r
1236 LatLngBounds.prototype = {
\r
1238 // @method extend(latlng: LatLng): this
\r
1239 // Extend the bounds to contain the given point
\r
1242 // @method extend(otherBounds: LatLngBounds): this
\r
1243 // Extend the bounds to contain the given bounds
\r
1244 extend: function (obj) {
\r
1245 var sw = this._southWest,
\r
1246 ne = this._northEast,
\r
1249 if (obj instanceof LatLng) {
\r
1253 } else if (obj instanceof LatLngBounds) {
\r
1254 sw2 = obj._southWest;
\r
1255 ne2 = obj._northEast;
\r
1257 if (!sw2 || !ne2) { return this; }
\r
1260 return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
\r
1264 this._southWest = new LatLng(sw2.lat, sw2.lng);
\r
1265 this._northEast = new LatLng(ne2.lat, ne2.lng);
\r
1267 sw.lat = Math.min(sw2.lat, sw.lat);
\r
1268 sw.lng = Math.min(sw2.lng, sw.lng);
\r
1269 ne.lat = Math.max(ne2.lat, ne.lat);
\r
1270 ne.lng = Math.max(ne2.lng, ne.lng);
\r
1276 // @method pad(bufferRatio: Number): LatLngBounds
\r
1277 // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
\r
1278 // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
\r
1279 // Negative values will retract the bounds.
\r
1280 pad: function (bufferRatio) {
\r
1281 var sw = this._southWest,
\r
1282 ne = this._northEast,
\r
1283 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
\r
1284 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
\r
1286 return new LatLngBounds(
\r
1287 new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
\r
1288 new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
\r
1291 // @method getCenter(): LatLng
\r
1292 // Returns the center point of the bounds.
\r
1293 getCenter: function () {
\r
1294 return new LatLng(
\r
1295 (this._southWest.lat + this._northEast.lat) / 2,
\r
1296 (this._southWest.lng + this._northEast.lng) / 2);
\r
1299 // @method getSouthWest(): LatLng
\r
1300 // Returns the south-west point of the bounds.
\r
1301 getSouthWest: function () {
\r
1302 return this._southWest;
\r
1305 // @method getNorthEast(): LatLng
\r
1306 // Returns the north-east point of the bounds.
\r
1307 getNorthEast: function () {
\r
1308 return this._northEast;
\r
1311 // @method getNorthWest(): LatLng
\r
1312 // Returns the north-west point of the bounds.
\r
1313 getNorthWest: function () {
\r
1314 return new LatLng(this.getNorth(), this.getWest());
\r
1317 // @method getSouthEast(): LatLng
\r
1318 // Returns the south-east point of the bounds.
\r
1319 getSouthEast: function () {
\r
1320 return new LatLng(this.getSouth(), this.getEast());
\r
1323 // @method getWest(): Number
\r
1324 // Returns the west longitude of the bounds
\r
1325 getWest: function () {
\r
1326 return this._southWest.lng;
\r
1329 // @method getSouth(): Number
\r
1330 // Returns the south latitude of the bounds
\r
1331 getSouth: function () {
\r
1332 return this._southWest.lat;
\r
1335 // @method getEast(): Number
\r
1336 // Returns the east longitude of the bounds
\r
1337 getEast: function () {
\r
1338 return this._northEast.lng;
\r
1341 // @method getNorth(): Number
\r
1342 // Returns the north latitude of the bounds
\r
1343 getNorth: function () {
\r
1344 return this._northEast.lat;
\r
1347 // @method contains(otherBounds: LatLngBounds): Boolean
\r
1348 // Returns `true` if the rectangle contains the given one.
\r
1351 // @method contains (latlng: LatLng): Boolean
\r
1352 // Returns `true` if the rectangle contains the given point.
\r
1353 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
\r
1354 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
\r
1355 obj = toLatLng(obj);
\r
1357 obj = toLatLngBounds(obj);
\r
1360 var sw = this._southWest,
\r
1361 ne = this._northEast,
\r
1364 if (obj instanceof LatLngBounds) {
\r
1365 sw2 = obj.getSouthWest();
\r
1366 ne2 = obj.getNorthEast();
\r
1371 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
\r
1372 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
\r
1375 // @method intersects(otherBounds: LatLngBounds): Boolean
\r
1376 // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
\r
1377 intersects: function (bounds) {
\r
1378 bounds = toLatLngBounds(bounds);
\r
1380 var sw = this._southWest,
\r
1381 ne = this._northEast,
\r
1382 sw2 = bounds.getSouthWest(),
\r
1383 ne2 = bounds.getNorthEast(),
\r
1385 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
\r
1386 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
\r
1388 return latIntersects && lngIntersects;
\r
1391 // @method overlaps(otherBounds: LatLngBounds): Boolean
\r
1392 // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
\r
1393 overlaps: function (bounds) {
\r
1394 bounds = toLatLngBounds(bounds);
\r
1396 var sw = this._southWest,
\r
1397 ne = this._northEast,
\r
1398 sw2 = bounds.getSouthWest(),
\r
1399 ne2 = bounds.getNorthEast(),
\r
1401 latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
\r
1402 lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
\r
1404 return latOverlaps && lngOverlaps;
\r
1407 // @method toBBoxString(): String
\r
1408 // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
\r
1409 toBBoxString: function () {
\r
1410 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
\r
1413 // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
\r
1414 // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
\r
1415 equals: function (bounds, maxMargin) {
\r
1416 if (!bounds) { return false; }
\r
1418 bounds = toLatLngBounds(bounds);
\r
1420 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
\r
1421 this._northEast.equals(bounds.getNorthEast(), maxMargin);
\r
1424 // @method isValid(): Boolean
\r
1425 // Returns `true` if the bounds are properly initialized.
\r
1426 isValid: function () {
\r
1427 return !!(this._southWest && this._northEast);
\r
1431 // TODO International date line?
\r
1433 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
\r
1434 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
\r
1437 // @factory L.latLngBounds(latlngs: LatLng[])
\r
1438 // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
\r
1439 function toLatLngBounds(a, b) {
\r
1440 if (a instanceof LatLngBounds) {
\r
1443 return new LatLngBounds(a, b);
\r
1449 * Represents a geographical point with a certain latitude and longitude.
\r
1454 * var latlng = L.latLng(50.5, 30.5);
\r
1457 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
\r
1460 * map.panTo([50, 30]);
\r
1461 * map.panTo({lon: 30, lat: 50});
\r
1462 * map.panTo({lat: 50, lng: 30});
\r
1463 * map.panTo(L.latLng(50, 30));
\r
1466 * Note that `LatLng` does not inherit from Leaflet's `Class` object,
\r
1467 * which means new classes can't inherit from it, and new methods
\r
1468 * can't be added to it with the `include` function.
\r
1471 function LatLng(lat, lng, alt) {
\r
1472 if (isNaN(lat) || isNaN(lng)) {
\r
1473 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
\r
1476 // @property lat: Number
\r
1477 // Latitude in degrees
\r
1480 // @property lng: Number
\r
1481 // Longitude in degrees
\r
1484 // @property alt: Number
\r
1485 // Altitude in meters (optional)
\r
1486 if (alt !== undefined) {
\r
1491 LatLng.prototype = {
\r
1492 // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
\r
1493 // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
\r
1494 equals: function (obj, maxMargin) {
\r
1495 if (!obj) { return false; }
\r
1497 obj = toLatLng(obj);
\r
1499 var margin = Math.max(
\r
1500 Math.abs(this.lat - obj.lat),
\r
1501 Math.abs(this.lng - obj.lng));
\r
1503 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
\r
1506 // @method toString(): String
\r
1507 // Returns a string representation of the point (for debugging purposes).
\r
1508 toString: function (precision) {
\r
1509 return 'LatLng(' +
\r
1510 formatNum(this.lat, precision) + ', ' +
\r
1511 formatNum(this.lng, precision) + ')';
\r
1514 // @method distanceTo(otherLatLng: LatLng): Number
\r
1515 // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
\r
1516 distanceTo: function (other) {
\r
1517 return Earth.distance(this, toLatLng(other));
\r
1520 // @method wrap(): LatLng
\r
1521 // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
\r
1522 wrap: function () {
\r
1523 return Earth.wrapLatLng(this);
\r
1526 // @method toBounds(sizeInMeters: Number): LatLngBounds
\r
1527 // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
\r
1528 toBounds: function (sizeInMeters) {
\r
1529 var latAccuracy = 180 * sizeInMeters / 40075017,
\r
1530 lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
\r
1532 return toLatLngBounds(
\r
1533 [this.lat - latAccuracy, this.lng - lngAccuracy],
\r
1534 [this.lat + latAccuracy, this.lng + lngAccuracy]);
\r
1537 clone: function () {
\r
1538 return new LatLng(this.lat, this.lng, this.alt);
\r
1544 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
\r
1545 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
\r
1548 // @factory L.latLng(coords: Array): LatLng
\r
1549 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
\r
1552 // @factory L.latLng(coords: Object): LatLng
\r
1553 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
\r
1555 function toLatLng(a, b, c) {
\r
1556 if (a instanceof LatLng) {
\r
1559 if (isArray(a) && typeof a[0] !== 'object') {
\r
1560 if (a.length === 3) {
\r
1561 return new LatLng(a[0], a[1], a[2]);
\r
1563 if (a.length === 2) {
\r
1564 return new LatLng(a[0], a[1]);
\r
1568 if (a === undefined || a === null) {
\r
1571 if (typeof a === 'object' && 'lat' in a) {
\r
1572 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
\r
1574 if (b === undefined) {
\r
1577 return new LatLng(a, b, c);
\r
1583 * Object that defines coordinate reference systems for projecting
\r
1584 * geographical points into pixel (screen) coordinates and back (and to
\r
1585 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
\r
1586 * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
\r
1588 * Leaflet defines the most usual CRSs by default. If you want to use a
\r
1589 * CRS not defined by default, take a look at the
\r
1590 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
\r
1592 * Note that the CRS instances do not inherit from Leaflet's `Class` object,
\r
1593 * and can't be instantiated. Also, new classes can't inherit from them,
\r
1594 * and methods can't be added to them with the `include` function.
\r
1598 // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
\r
1599 // Projects geographical coordinates into pixel coordinates for a given zoom.
\r
1600 latLngToPoint: function (latlng, zoom) {
\r
1601 var projectedPoint = this.projection.project(latlng),
\r
1602 scale = this.scale(zoom);
\r
1604 return this.transformation._transform(projectedPoint, scale);
\r
1607 // @method pointToLatLng(point: Point, zoom: Number): LatLng
\r
1608 // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
\r
1609 // zoom into geographical coordinates.
\r
1610 pointToLatLng: function (point, zoom) {
\r
1611 var scale = this.scale(zoom),
\r
1612 untransformedPoint = this.transformation.untransform(point, scale);
\r
1614 return this.projection.unproject(untransformedPoint);
\r
1617 // @method project(latlng: LatLng): Point
\r
1618 // Projects geographical coordinates into coordinates in units accepted for
\r
1619 // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
\r
1620 project: function (latlng) {
\r
1621 return this.projection.project(latlng);
\r
1624 // @method unproject(point: Point): LatLng
\r
1625 // Given a projected coordinate returns the corresponding LatLng.
\r
1626 // The inverse of `project`.
\r
1627 unproject: function (point) {
\r
1628 return this.projection.unproject(point);
\r
1631 // @method scale(zoom: Number): Number
\r
1632 // Returns the scale used when transforming projected coordinates into
\r
1633 // pixel coordinates for a particular zoom. For example, it returns
\r
1634 // `256 * 2^zoom` for Mercator-based CRS.
\r
1635 scale: function (zoom) {
\r
1636 return 256 * Math.pow(2, zoom);
\r
1639 // @method zoom(scale: Number): Number
\r
1640 // Inverse of `scale()`, returns the zoom level corresponding to a scale
\r
1641 // factor of `scale`.
\r
1642 zoom: function (scale) {
\r
1643 return Math.log(scale / 256) / Math.LN2;
\r
1646 // @method getProjectedBounds(zoom: Number): Bounds
\r
1647 // Returns the projection's bounds scaled and transformed for the provided `zoom`.
\r
1648 getProjectedBounds: function (zoom) {
\r
1649 if (this.infinite) { return null; }
\r
1651 var b = this.projection.bounds,
\r
1652 s = this.scale(zoom),
\r
1653 min = this.transformation.transform(b.min, s),
\r
1654 max = this.transformation.transform(b.max, s);
\r
1656 return new Bounds(min, max);
\r
1659 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
\r
1660 // Returns the distance between two geographical coordinates.
\r
1662 // @property code: String
\r
1663 // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
\r
1665 // @property wrapLng: Number[]
\r
1666 // An array of two numbers defining whether the longitude (horizontal) coordinate
\r
1667 // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
\r
1668 // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
\r
1670 // @property wrapLat: Number[]
\r
1671 // Like `wrapLng`, but for the latitude (vertical) axis.
\r
1673 // wrapLng: [min, max],
\r
1674 // wrapLat: [min, max],
\r
1676 // @property infinite: Boolean
\r
1677 // If true, the coordinate space will be unbounded (infinite in both axes)
\r
1680 // @method wrapLatLng(latlng: LatLng): LatLng
\r
1681 // Returns a `LatLng` where lat and lng has been wrapped according to the
\r
1682 // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
\r
1683 wrapLatLng: function (latlng) {
\r
1684 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
\r
1685 lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
\r
1688 return new LatLng(lat, lng, alt);
\r
1691 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
\r
1692 // Returns a `LatLngBounds` with the same size as the given one, ensuring
\r
1693 // that its center is within the CRS's bounds.
\r
1694 // Only accepts actual `L.LatLngBounds` instances, not arrays.
\r
1695 wrapLatLngBounds: function (bounds) {
\r
1696 var center = bounds.getCenter(),
\r
1697 newCenter = this.wrapLatLng(center),
\r
1698 latShift = center.lat - newCenter.lat,
\r
1699 lngShift = center.lng - newCenter.lng;
\r
1701 if (latShift === 0 && lngShift === 0) {
\r
1705 var sw = bounds.getSouthWest(),
\r
1706 ne = bounds.getNorthEast(),
\r
1707 newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
\r
1708 newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
\r
1710 return new LatLngBounds(newSw, newNe);
\r
1718 * Serves as the base for CRS that are global such that they cover the earth.
1719 * Can only be used as the base for other CRS and cannot be used directly,
1720 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1724 var Earth = extend({}, CRS, {
1725 wrapLng: [-180, 180],
1727 // Mean Earth Radius, as recommended for use by
1728 // the International Union of Geodesy and Geophysics,
1729 // see https://rosettacode.org/wiki/Haversine_formula
1732 // distance between two geographical points using spherical law of cosines approximation
1733 distance: function (latlng1, latlng2) {
1734 var rad = Math.PI / 180,
1735 lat1 = latlng1.lat * rad,
1736 lat2 = latlng2.lat * rad,
1737 sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1738 sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1739 a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1740 c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1746 * @namespace Projection
\r
1747 * @projection L.Projection.SphericalMercator
\r
1749 * Spherical Mercator projection — the most common projection for online maps,
\r
1750 * used by almost all free and commercial tile providers. Assumes that Earth is
\r
1751 * a sphere. Used by the `EPSG:3857` CRS.
\r
1754 var earthRadius = 6378137;
\r
1756 var SphericalMercator = {
\r
1759 MAX_LATITUDE: 85.0511287798,
\r
1761 project: function (latlng) {
\r
1762 var d = Math.PI / 180,
\r
1763 max = this.MAX_LATITUDE,
\r
1764 lat = Math.max(Math.min(max, latlng.lat), -max),
\r
1765 sin = Math.sin(lat * d);
\r
1768 this.R * latlng.lng * d,
\r
1769 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
\r
1772 unproject: function (point) {
\r
1773 var d = 180 / Math.PI;
\r
1775 return new LatLng(
\r
1776 (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
\r
1777 point.x * d / this.R);
\r
1780 bounds: (function () {
\r
1781 var d = earthRadius * Math.PI;
\r
1782 return new Bounds([-d, -d], [d, d]);
\r
1787 * @class Transformation
\r
1788 * @aka L.Transformation
\r
1790 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
\r
1791 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
\r
1792 * the reverse. Used by Leaflet in its projections code.
\r
1797 * var transformation = L.transformation(2, 5, -1, 10),
\r
1798 * p = L.point(1, 2),
\r
1799 * p2 = transformation.transform(p), // L.point(7, 8)
\r
1800 * p3 = transformation.untransform(p2); // L.point(1, 2)
\r
1805 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
\r
1806 // Creates a `Transformation` object with the given coefficients.
\r
1807 function Transformation(a, b, c, d) {
\r
1809 // use array properties
\r
1822 Transformation.prototype = {
\r
1823 // @method transform(point: Point, scale?: Number): Point
\r
1824 // Returns a transformed point, optionally multiplied by the given scale.
\r
1825 // Only accepts actual `L.Point` instances, not arrays.
\r
1826 transform: function (point, scale) { // (Point, Number) -> Point
\r
1827 return this._transform(point.clone(), scale);
\r
1830 // destructive transform (faster)
\r
1831 _transform: function (point, scale) {
\r
1832 scale = scale || 1;
\r
1833 point.x = scale * (this._a * point.x + this._b);
\r
1834 point.y = scale * (this._c * point.y + this._d);
\r
1838 // @method untransform(point: Point, scale?: Number): Point
\r
1839 // Returns the reverse transformation of the given point, optionally divided
\r
1840 // by the given scale. Only accepts actual `L.Point` instances, not arrays.
\r
1841 untransform: function (point, scale) {
\r
1842 scale = scale || 1;
\r
1844 (point.x / scale - this._b) / this._a,
\r
1845 (point.y / scale - this._d) / this._c);
\r
1849 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
\r
1851 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
\r
1852 // Instantiates a Transformation object with the given coefficients.
\r
1855 // @factory L.transformation(coefficients: Array): Transformation
\r
1856 // Expects an coefficients array of the form
\r
1857 // `[a: Number, b: Number, c: Number, d: Number]`.
\r
1859 function toTransformation(a, b, c, d) {
\r
1860 return new Transformation(a, b, c, d);
\r
1865 * @crs L.CRS.EPSG3857
\r
1867 * The most common CRS for online maps, used by almost all free and commercial
\r
1868 * tile providers. Uses Spherical Mercator projection. Set in by default in
\r
1869 * Map's `crs` option.
\r
1872 var EPSG3857 = extend({}, Earth, {
\r
1873 code: 'EPSG:3857',
\r
1874 projection: SphericalMercator,
\r
1876 transformation: (function () {
\r
1877 var scale = 0.5 / (Math.PI * SphericalMercator.R);
\r
1878 return toTransformation(scale, 0.5, -scale, 0.5);
\r
1882 var EPSG900913 = extend({}, EPSG3857, {
\r
1883 code: 'EPSG:900913'
\r
1886 // @namespace SVG; @section
1887 // There are several static functions which can be called without instantiating L.SVG:
1889 // @function create(name: String): SVGElement
1890 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1891 // corresponding to the class name passed. For example, using 'line' will return
1892 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1893 function svgCreate(name) {
1894 return document.createElementNS('http://www.w3.org/2000/svg', name);
1897 // @function pointsToPath(rings: Point[], closed: Boolean): String
1898 // Generates a SVG path string for multiple rings, with each ring turning
1899 // into "M..L..L.." instructions
1900 function pointsToPath(rings, closed) {
1902 i, j, len, len2, points, p;
1904 for (i = 0, len = rings.length; i < len; i++) {
1907 for (j = 0, len2 = points.length; j < len2; j++) {
1909 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1912 // closes the ring for polygons; "x" is VML syntax
1913 str += closed ? (Browser.svg ? 'z' : 'x') : '';
1916 // SVG complains about empty path strings
1917 return str || 'M0 0';
1921 * @namespace Browser
\r
1924 * A namespace with static properties for browser/feature detection used by Leaflet internally.
\r
1929 * if (L.Browser.ielt9) {
\r
1930 * alert('Upgrade your browser, dude!');
\r
1935 var style = document.documentElement.style;
\r
1937 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
\r
1938 var ie = 'ActiveXObject' in window;
\r
1940 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
\r
1941 var ielt9 = ie && !document.addEventListener;
\r
1943 // @property edge: Boolean; `true` for the Edge web browser.
\r
1944 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
\r
1946 // @property webkit: Boolean;
\r
1947 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
\r
1948 var webkit = userAgentContains('webkit');
\r
1950 // @property android: Boolean
\r
1951 // **Deprecated.** `true` for any browser running on an Android platform.
\r
1952 var android = userAgentContains('android');
\r
1954 // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
\r
1955 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
\r
1957 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
\r
1958 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
\r
1959 // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)
\r
1960 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
\r
1962 // @property opera: Boolean; `true` for the Opera browser
\r
1963 var opera = !!window.opera;
\r
1965 // @property chrome: Boolean; `true` for the Chrome browser.
\r
1966 var chrome = !edge && userAgentContains('chrome');
\r
1968 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
\r
1969 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
\r
1971 // @property safari: Boolean; `true` for the Safari browser.
\r
1972 var safari = !chrome && userAgentContains('safari');
\r
1974 var phantom = userAgentContains('phantom');
\r
1976 // @property opera12: Boolean
\r
1977 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
\r
1978 var opera12 = 'OTransition' in style;
\r
1980 // @property win: Boolean; `true` when the browser is running in a Windows platform
\r
1981 var win = navigator.platform.indexOf('Win') === 0;
\r
1983 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
\r
1984 var ie3d = ie && ('transition' in style);
\r
1986 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
\r
1987 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
\r
1989 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
\r
1990 var gecko3d = 'MozPerspective' in style;
\r
1992 // @property any3d: Boolean
\r
1993 // `true` for all browsers supporting CSS transforms.
\r
1994 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
\r
1996 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
\r
1997 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
\r
1999 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
\r
2000 var mobileWebkit = mobile && webkit;
\r
2002 // @property mobileWebkit3d: Boolean
\r
2003 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
\r
2004 var mobileWebkit3d = mobile && webkit3d;
\r
2006 // @property msPointer: Boolean
\r
2007 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
\r
2008 var msPointer = !window.PointerEvent && window.MSPointerEvent;
\r
2010 // @property pointer: Boolean
\r
2011 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
\r
2012 var pointer = !!(window.PointerEvent || msPointer);
\r
2014 // @property touchNative: Boolean
\r
2015 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
\r
2016 // **This does not necessarily mean** that the browser is running in a computer with
\r
2017 // a touchscreen, it only means that the browser is capable of understanding
\r
2019 var touchNative = 'ontouchstart' in window || !!window.TouchEvent;
\r
2021 // @property touch: Boolean
\r
2022 // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.
\r
2023 // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.
\r
2024 var touch = !window.L_NO_TOUCH && (touchNative || pointer);
\r
2026 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
\r
2027 var mobileOpera = mobile && opera;
\r
2029 // @property mobileGecko: Boolean
\r
2030 // `true` for gecko-based browsers running in a mobile device.
\r
2031 var mobileGecko = mobile && gecko;
\r
2033 // @property retina: Boolean
\r
2034 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
\r
2035 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
\r
2037 // @property passiveEvents: Boolean
\r
2038 // `true` for browsers that support passive events.
\r
2039 var passiveEvents = (function () {
\r
2040 var supportsPassiveOption = false;
\r
2042 var opts = Object.defineProperty({}, 'passive', {
\r
2043 get: function () { // eslint-disable-line getter-return
\r
2044 supportsPassiveOption = true;
\r
2047 window.addEventListener('testPassiveEventSupport', falseFn, opts);
\r
2048 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
\r
2050 // Errors can safely be ignored since this is only a browser support test.
\r
2052 return supportsPassiveOption;
\r
2055 // @property canvas: Boolean
\r
2056 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
\r
2057 var canvas$1 = (function () {
\r
2058 return !!document.createElement('canvas').getContext;
\r
2061 // @property svg: Boolean
\r
2062 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
\r
2063 var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);
\r
2065 var inlineSvg = !!svg$1 && (function () {
\r
2066 var div = document.createElement('div');
\r
2067 div.innerHTML = '<svg/>';
\r
2068 return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';
\r
2071 // @property vml: Boolean
\r
2072 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
\r
2073 var vml = !svg$1 && (function () {
\r
2075 var div = document.createElement('div');
\r
2076 div.innerHTML = '<v:shape adj="1"/>';
\r
2078 var shape = div.firstChild;
\r
2079 shape.style.behavior = 'url(#default#VML)';
\r
2081 return shape && (typeof shape.adj === 'object');
\r
2089 // @property mac: Boolean; `true` when the browser is running in a Mac platform
\r
2090 var mac = navigator.platform.indexOf('Mac') === 0;
\r
2092 // @property mac: Boolean; `true` when the browser is running in a Linux platform
\r
2093 var linux = navigator.platform.indexOf('Linux') === 0;
\r
2095 function userAgentContains(str) {
\r
2096 return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
\r
2106 android23: android23,
\r
2107 androidStock: androidStock,
\r
2116 webkit3d: webkit3d,
\r
2120 mobileWebkit: mobileWebkit,
\r
2121 mobileWebkit3d: mobileWebkit3d,
\r
2122 msPointer: msPointer,
\r
2125 touchNative: touchNative,
\r
2126 mobileOpera: mobileOpera,
\r
2127 mobileGecko: mobileGecko,
\r
2129 passiveEvents: passiveEvents,
\r
2133 inlineSvg: inlineSvg,
\r
2139 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2142 var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown';
2143 var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove';
2144 var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup';
2145 var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
2147 touchstart : POINTER_DOWN,
2148 touchmove : POINTER_MOVE,
2149 touchend : POINTER_UP,
2150 touchcancel : POINTER_CANCEL
2153 touchstart : _onPointerStart,
2154 touchmove : _handlePointer,
2155 touchend : _handlePointer,
2156 touchcancel : _handlePointer
2159 var _pointerDocListener = false;
2161 // Provides a touch events wrapper for (ms)pointer events.
2162 // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2164 function addPointerListener(obj, type, handler) {
2165 if (type === 'touchstart') {
2166 _addPointerDocListener();
2168 if (!handle[type]) {
2169 console.warn('wrong event specified:', type);
2172 handler = handle[type].bind(this, handler);
2173 obj.addEventListener(pEvent[type], handler, false);
2177 function removePointerListener(obj, type, handler) {
2178 if (!pEvent[type]) {
2179 console.warn('wrong event specified:', type);
2182 obj.removeEventListener(pEvent[type], handler, false);
2185 function _globalPointerDown(e) {
2186 _pointers[e.pointerId] = e;
2189 function _globalPointerMove(e) {
2190 if (_pointers[e.pointerId]) {
2191 _pointers[e.pointerId] = e;
2195 function _globalPointerUp(e) {
2196 delete _pointers[e.pointerId];
2199 function _addPointerDocListener() {
2200 // need to keep track of what pointers and how many are active to provide e.touches emulation
2201 if (!_pointerDocListener) {
2202 // we listen document as any drags that end by moving the touch off the screen get fired there
2203 document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2204 document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2205 document.addEventListener(POINTER_UP, _globalPointerUp, true);
2206 document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2208 _pointerDocListener = true;
2212 function _handlePointer(handler, e) {
2213 if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }
2216 for (var i in _pointers) {
2217 e.touches.push(_pointers[i]);
2219 e.changedTouches = [e];
2224 function _onPointerStart(handler, e) {
2225 // IE10 specific: MsTouch needs preventDefault. See #2000
2226 if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
2229 _handlePointer(handler, e);
2233 * Extends the event handling code with double tap support for mobile browsers.
\r
2235 * Note: currently most browsers fire native dblclick, with only a few exceptions
\r
2236 * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)
\r
2239 function makeDblclick(event) {
\r
2240 // in modern browsers `type` cannot be just overridden:
\r
2241 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only
\r
2242 var newEvent = {},
\r
2244 for (i in event) {
\r
2246 newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;
\r
2249 newEvent.type = 'dblclick';
\r
2250 newEvent.detail = 2;
\r
2251 newEvent.isTrusted = false;
\r
2252 newEvent._simulated = true; // for debug purposes
\r
2257 function addDoubleTapListener(obj, handler) {
\r
2258 // Most browsers handle double tap natively
\r
2259 obj.addEventListener('dblclick', handler);
\r
2261 // On some platforms the browser doesn't fire native dblclicks for touch events.
\r
2262 // It seems that in all such cases `detail` property of `click` event is always `1`.
\r
2263 // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.
\r
2266 function simDblclick(e) {
\r
2267 if (e.detail !== 1) {
\r
2268 detail = e.detail; // keep in sync to avoid false dblclick in some cases
\r
2272 if (e.pointerType === 'mouse' ||
\r
2273 (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {
\r
2278 // When clicking on an <input>, the browser generates a click on its
\r
2279 // <label> (and vice versa) triggering two clicks in quick succession.
\r
2280 // This ignores clicks on elements which are a label with a 'for'
\r
2281 // attribute (or children of such a label), but not children of
\r
2283 var path = getPropagationPath(e);
\r
2284 if (path.some(function (el) {
\r
2285 return el instanceof HTMLLabelElement && el.attributes.for;
\r
2287 !path.some(function (el) {
\r
2289 el instanceof HTMLInputElement ||
\r
2290 el instanceof HTMLSelectElement
\r
2297 var now = Date.now();
\r
2298 if (now - last <= delay) {
\r
2300 if (detail === 2) {
\r
2301 handler(makeDblclick(e));
\r
2309 obj.addEventListener('click', simDblclick);
\r
2312 dblclick: handler,
\r
2313 simDblclick: simDblclick
\r
2317 function removeDoubleTapListener(obj, handlers) {
\r
2318 obj.removeEventListener('dblclick', handlers.dblclick);
\r
2319 obj.removeEventListener('click', handlers.simDblclick);
\r
2323 * @namespace DomUtil
\r
2325 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
\r
2326 * tree, used by Leaflet internally.
\r
2328 * Most functions expecting or returning a `HTMLElement` also work for
\r
2329 * SVG elements. The only difference is that classes refer to CSS classes
\r
2330 * in HTML and SVG classes in SVG.
\r
2334 // @property TRANSFORM: String
\r
2335 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
\r
2336 var TRANSFORM = testProp(
\r
2337 ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
\r
2339 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
\r
2340 // the same for the transitionend event, in particular the Android 4.1 stock browser
\r
2342 // @property TRANSITION: String
\r
2343 // Vendor-prefixed transition style name.
\r
2344 var TRANSITION = testProp(
\r
2345 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
\r
2347 // @property TRANSITION_END: String
\r
2348 // Vendor-prefixed transitionend event name.
\r
2349 var TRANSITION_END =
\r
2350 TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
\r
2353 // @function get(id: String|HTMLElement): HTMLElement
\r
2354 // Returns an element given its DOM id, or returns the element itself
\r
2355 // if it was passed directly.
\r
2356 function get(id) {
\r
2357 return typeof id === 'string' ? document.getElementById(id) : id;
\r
2360 // @function getStyle(el: HTMLElement, styleAttrib: String): String
\r
2361 // Returns the value for a certain style attribute on an element,
\r
2362 // including computed values or values set through CSS.
\r
2363 function getStyle(el, style) {
\r
2364 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
\r
2366 if ((!value || value === 'auto') && document.defaultView) {
\r
2367 var css = document.defaultView.getComputedStyle(el, null);
\r
2368 value = css ? css[style] : null;
\r
2370 return value === 'auto' ? null : value;
\r
2373 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
\r
2374 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
\r
2375 function create$1(tagName, className, container) {
\r
2376 var el = document.createElement(tagName);
\r
2377 el.className = className || '';
\r
2380 container.appendChild(el);
\r
2385 // @function remove(el: HTMLElement)
\r
2386 // Removes `el` from its parent element
\r
2387 function remove(el) {
\r
2388 var parent = el.parentNode;
\r
2390 parent.removeChild(el);
\r
2394 // @function empty(el: HTMLElement)
\r
2395 // Removes all of `el`'s children elements from `el`
\r
2396 function empty(el) {
\r
2397 while (el.firstChild) {
\r
2398 el.removeChild(el.firstChild);
\r
2402 // @function toFront(el: HTMLElement)
\r
2403 // Makes `el` the last child of its parent, so it renders in front of the other children.
\r
2404 function toFront(el) {
\r
2405 var parent = el.parentNode;
\r
2406 if (parent && parent.lastChild !== el) {
\r
2407 parent.appendChild(el);
\r
2411 // @function toBack(el: HTMLElement)
\r
2412 // Makes `el` the first child of its parent, so it renders behind the other children.
\r
2413 function toBack(el) {
\r
2414 var parent = el.parentNode;
\r
2415 if (parent && parent.firstChild !== el) {
\r
2416 parent.insertBefore(el, parent.firstChild);
\r
2420 // @function hasClass(el: HTMLElement, name: String): Boolean
\r
2421 // Returns `true` if the element's class attribute contains `name`.
\r
2422 function hasClass(el, name) {
\r
2423 if (el.classList !== undefined) {
\r
2424 return el.classList.contains(name);
\r
2426 var className = getClass(el);
\r
2427 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
\r
2430 // @function addClass(el: HTMLElement, name: String)
\r
2431 // Adds `name` to the element's class attribute.
\r
2432 function addClass(el, name) {
\r
2433 if (el.classList !== undefined) {
\r
2434 var classes = splitWords(name);
\r
2435 for (var i = 0, len = classes.length; i < len; i++) {
\r
2436 el.classList.add(classes[i]);
\r
2438 } else if (!hasClass(el, name)) {
\r
2439 var className = getClass(el);
\r
2440 setClass(el, (className ? className + ' ' : '') + name);
\r
2444 // @function removeClass(el: HTMLElement, name: String)
\r
2445 // Removes `name` from the element's class attribute.
\r
2446 function removeClass(el, name) {
\r
2447 if (el.classList !== undefined) {
\r
2448 el.classList.remove(name);
\r
2450 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
\r
2454 // @function setClass(el: HTMLElement, name: String)
\r
2455 // Sets the element's class.
\r
2456 function setClass(el, name) {
\r
2457 if (el.className.baseVal === undefined) {
\r
2458 el.className = name;
\r
2460 // in case of SVG element
\r
2461 el.className.baseVal = name;
\r
2465 // @function getClass(el: HTMLElement): String
\r
2466 // Returns the element's class.
\r
2467 function getClass(el) {
\r
2468 // Check if the element is an SVGElementInstance and use the correspondingElement instead
\r
2469 // (Required for linked SVG elements in IE11.)
\r
2470 if (el.correspondingElement) {
\r
2471 el = el.correspondingElement;
\r
2473 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
\r
2476 // @function setOpacity(el: HTMLElement, opacity: Number)
\r
2477 // Set the opacity of an element (including old IE support).
\r
2478 // `opacity` must be a number from `0` to `1`.
\r
2479 function setOpacity(el, value) {
\r
2480 if ('opacity' in el.style) {
\r
2481 el.style.opacity = value;
\r
2482 } else if ('filter' in el.style) {
\r
2483 _setOpacityIE(el, value);
\r
2487 function _setOpacityIE(el, value) {
\r
2488 var filter = false,
\r
2489 filterName = 'DXImageTransform.Microsoft.Alpha';
\r
2491 // filters collection throws an error if we try to retrieve a filter that doesn't exist
\r
2493 filter = el.filters.item(filterName);
\r
2495 // don't set opacity to 1 if we haven't already set an opacity,
\r
2496 // it isn't needed and breaks transparent pngs.
\r
2497 if (value === 1) { return; }
\r
2500 value = Math.round(value * 100);
\r
2503 filter.Enabled = (value !== 100);
\r
2504 filter.Opacity = value;
\r
2506 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
\r
2510 // @function testProp(props: String[]): String|false
\r
2511 // Goes through the array of style names and returns the first name
\r
2512 // that is a valid style name for an element. If no such name is found,
\r
2513 // it returns false. Useful for vendor-prefixed styles like `transform`.
\r
2514 function testProp(props) {
\r
2515 var style = document.documentElement.style;
\r
2517 for (var i = 0; i < props.length; i++) {
\r
2518 if (props[i] in style) {
\r
2525 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
\r
2526 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
\r
2527 // and optionally scaled by `scale`. Does not have an effect if the
\r
2528 // browser doesn't support 3D CSS transforms.
\r
2529 function setTransform(el, offset, scale) {
\r
2530 var pos = offset || new Point(0, 0);
\r
2532 el.style[TRANSFORM] =
\r
2534 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
\r
2535 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
\r
2536 (scale ? ' scale(' + scale + ')' : '');
\r
2539 // @function setPosition(el: HTMLElement, position: Point)
\r
2540 // Sets the position of `el` to coordinates specified by `position`,
\r
2541 // using CSS translate or top/left positioning depending on the browser
\r
2542 // (used by Leaflet internally to position its layers).
\r
2543 function setPosition(el, point) {
\r
2545 /*eslint-disable */
\r
2546 el._leaflet_pos = point;
\r
2547 /* eslint-enable */
\r
2549 if (Browser.any3d) {
\r
2550 setTransform(el, point);
\r
2552 el.style.left = point.x + 'px';
\r
2553 el.style.top = point.y + 'px';
\r
2557 // @function getPosition(el: HTMLElement): Point
\r
2558 // Returns the coordinates of an element previously positioned with setPosition.
\r
2559 function getPosition(el) {
\r
2560 // this method is only used for elements previously positioned using setPosition,
\r
2561 // so it's safe to cache the position for performance
\r
2563 return el._leaflet_pos || new Point(0, 0);
\r
2566 // @function disableTextSelection()
\r
2567 // Prevents the user from generating `selectstart` DOM events, usually generated
\r
2568 // when the user drags the mouse through a page with text. Used internally
\r
2569 // by Leaflet to override the behaviour of any click-and-drag interaction on
\r
2570 // the map. Affects drag interactions on the whole document.
\r
2572 // @function enableTextSelection()
\r
2573 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
\r
2574 var disableTextSelection;
\r
2575 var enableTextSelection;
\r
2577 if ('onselectstart' in document) {
\r
2578 disableTextSelection = function () {
\r
2579 on(window, 'selectstart', preventDefault);
\r
2581 enableTextSelection = function () {
\r
2582 off(window, 'selectstart', preventDefault);
\r
2585 var userSelectProperty = testProp(
\r
2586 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
\r
2588 disableTextSelection = function () {
\r
2589 if (userSelectProperty) {
\r
2590 var style = document.documentElement.style;
\r
2591 _userSelect = style[userSelectProperty];
\r
2592 style[userSelectProperty] = 'none';
\r
2595 enableTextSelection = function () {
\r
2596 if (userSelectProperty) {
\r
2597 document.documentElement.style[userSelectProperty] = _userSelect;
\r
2598 _userSelect = undefined;
\r
2603 // @function disableImageDrag()
\r
2604 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
\r
2605 // for `dragstart` DOM events, usually generated when the user drags an image.
\r
2606 function disableImageDrag() {
\r
2607 on(window, 'dragstart', preventDefault);
\r
2610 // @function enableImageDrag()
\r
2611 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
\r
2612 function enableImageDrag() {
\r
2613 off(window, 'dragstart', preventDefault);
\r
2616 var _outlineElement, _outlineStyle;
\r
2617 // @function preventOutline(el: HTMLElement)
\r
2618 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
\r
2619 // of the element `el` invisible. Used internally by Leaflet to prevent
\r
2620 // focusable elements from displaying an outline when the user performs a
\r
2621 // drag interaction on them.
\r
2622 function preventOutline(element) {
\r
2623 while (element.tabIndex === -1) {
\r
2624 element = element.parentNode;
\r
2626 if (!element.style) { return; }
\r
2628 _outlineElement = element;
\r
2629 _outlineStyle = element.style.outlineStyle;
\r
2630 element.style.outlineStyle = 'none';
\r
2631 on(window, 'keydown', restoreOutline);
\r
2634 // @function restoreOutline()
\r
2635 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
\r
2636 function restoreOutline() {
\r
2637 if (!_outlineElement) { return; }
\r
2638 _outlineElement.style.outlineStyle = _outlineStyle;
\r
2639 _outlineElement = undefined;
\r
2640 _outlineStyle = undefined;
\r
2641 off(window, 'keydown', restoreOutline);
\r
2644 // @function getSizedParentNode(el: HTMLElement): HTMLElement
\r
2645 // Finds the closest parent node which size (width and height) is not null.
\r
2646 function getSizedParentNode(element) {
\r
2648 element = element.parentNode;
\r
2649 } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
\r
2653 // @function getScale(el: HTMLElement): Object
\r
2654 // Computes the CSS scale currently applied on the element.
\r
2655 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
\r
2656 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
\r
2657 function getScale(element) {
\r
2658 var rect = element.getBoundingClientRect(); // Read-only in old browsers.
\r
2661 x: rect.width / element.offsetWidth || 1,
\r
2662 y: rect.height / element.offsetHeight || 1,
\r
2663 boundingClientRect: rect
\r
2669 TRANSFORM: TRANSFORM,
2670 TRANSITION: TRANSITION,
2671 TRANSITION_END: TRANSITION_END,
2681 removeClass: removeClass,
2684 setOpacity: setOpacity,
2686 setTransform: setTransform,
2687 setPosition: setPosition,
2688 getPosition: getPosition,
2689 get disableTextSelection () { return disableTextSelection; },
2690 get enableTextSelection () { return enableTextSelection; },
2691 disableImageDrag: disableImageDrag,
2692 enableImageDrag: enableImageDrag,
2693 preventOutline: preventOutline,
2694 restoreOutline: restoreOutline,
2695 getSizedParentNode: getSizedParentNode,
2700 * @namespace DomEvent
\r
2701 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
\r
2704 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
\r
2706 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
\r
2707 // Adds a listener function (`fn`) to a particular DOM event type of the
\r
2708 // element `el`. You can optionally specify the context of the listener
\r
2709 // (object the `this` keyword will point to). You can also pass several
\r
2710 // space-separated types (e.g. `'click dblclick'`).
\r
2713 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
\r
2714 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
\r
2715 function on(obj, types, fn, context) {
\r
2717 if (types && typeof types === 'object') {
\r
2718 for (var type in types) {
\r
2719 addOne(obj, type, types[type], fn);
\r
2722 types = splitWords(types);
\r
2724 for (var i = 0, len = types.length; i < len; i++) {
\r
2725 addOne(obj, types[i], fn, context);
\r
2732 var eventsKey = '_leaflet_events';
\r
2734 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
\r
2735 // Removes a previously added listener function.
\r
2736 // Note that if you passed a custom context to on, you must pass the same
\r
2737 // context to `off` in order to remove the listener.
\r
2740 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
\r
2741 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
\r
2744 // @function off(el: HTMLElement, types: String): this
\r
2745 // Removes all previously added listeners of given types.
\r
2748 // @function off(el: HTMLElement): this
\r
2749 // Removes all previously added listeners from given HTMLElement
\r
2750 function off(obj, types, fn, context) {
\r
2752 if (arguments.length === 1) {
\r
2754 delete obj[eventsKey];
\r
2756 } else if (types && typeof types === 'object') {
\r
2757 for (var type in types) {
\r
2758 removeOne(obj, type, types[type], fn);
\r
2762 types = splitWords(types);
\r
2764 if (arguments.length === 2) {
\r
2765 batchRemove(obj, function (type) {
\r
2766 return indexOf(types, type) !== -1;
\r
2769 for (var i = 0, len = types.length; i < len; i++) {
\r
2770 removeOne(obj, types[i], fn, context);
\r
2778 function batchRemove(obj, filterFn) {
\r
2779 for (var id in obj[eventsKey]) {
\r
2780 var type = id.split(/\d/)[0];
\r
2781 if (!filterFn || filterFn(type)) {
\r
2782 removeOne(obj, type, null, null, id);
\r
2787 var mouseSubst = {
\r
2788 mouseenter: 'mouseover',
\r
2789 mouseleave: 'mouseout',
\r
2790 wheel: !('onwheel' in window) && 'mousewheel'
\r
2793 function addOne(obj, type, fn, context) {
\r
2794 var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
\r
2796 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
\r
2798 var handler = function (e) {
\r
2799 return fn.call(context || obj, e || window.event);
\r
2802 var originalHandler = handler;
\r
2804 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
\r
2805 // Needs DomEvent.Pointer.js
\r
2806 handler = addPointerListener(obj, type, handler);
\r
2808 } else if (Browser.touch && (type === 'dblclick')) {
\r
2809 handler = addDoubleTapListener(obj, handler);
\r
2811 } else if ('addEventListener' in obj) {
\r
2813 if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' || type === 'mousewheel') {
\r
2814 obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);
\r
2816 } else if (type === 'mouseenter' || type === 'mouseleave') {
\r
2817 handler = function (e) {
\r
2818 e = e || window.event;
\r
2819 if (isExternalTarget(obj, e)) {
\r
2820 originalHandler(e);
\r
2823 obj.addEventListener(mouseSubst[type], handler, false);
\r
2826 obj.addEventListener(type, originalHandler, false);
\r
2830 obj.attachEvent('on' + type, handler);
\r
2833 obj[eventsKey] = obj[eventsKey] || {};
\r
2834 obj[eventsKey][id] = handler;
\r
2837 function removeOne(obj, type, fn, context, id) {
\r
2838 id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');
\r
2839 var handler = obj[eventsKey] && obj[eventsKey][id];
\r
2841 if (!handler) { return this; }
\r
2843 if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
\r
2844 removePointerListener(obj, type, handler);
\r
2846 } else if (Browser.touch && (type === 'dblclick')) {
\r
2847 removeDoubleTapListener(obj, handler);
\r
2849 } else if ('removeEventListener' in obj) {
\r
2851 obj.removeEventListener(mouseSubst[type] || type, handler, false);
\r
2854 obj.detachEvent('on' + type, handler);
\r
2857 obj[eventsKey][id] = null;
\r
2860 // @function stopPropagation(ev: DOMEvent): this
\r
2861 // Stop the given event from propagation to parent elements. Used inside the listener functions:
\r
2863 // L.DomEvent.on(div, 'click', function (ev) {
\r
2864 // L.DomEvent.stopPropagation(ev);
\r
2867 function stopPropagation(e) {
\r
2869 if (e.stopPropagation) {
\r
2870 e.stopPropagation();
\r
2871 } else if (e.originalEvent) { // In case of Leaflet event.
\r
2872 e.originalEvent._stopped = true;
\r
2874 e.cancelBubble = true;
\r
2880 // @function disableScrollPropagation(el: HTMLElement): this
\r
2881 // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
\r
2882 function disableScrollPropagation(el) {
\r
2883 addOne(el, 'wheel', stopPropagation);
\r
2887 // @function disableClickPropagation(el: HTMLElement): this
\r
2888 // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,
\r
2889 // `'mousedown'` and `'touchstart'` events (plus browser variants).
\r
2890 function disableClickPropagation(el) {
\r
2891 on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);
\r
2892 el['_leaflet_disable_click'] = true;
\r
2896 // @function preventDefault(ev: DOMEvent): this
\r
2897 // Prevents the default action of the DOM Event `ev` from happening (such as
\r
2898 // following a link in the href of the a element, or doing a POST request
\r
2899 // with page reload when a `<form>` is submitted).
\r
2900 // Use it inside listener functions.
\r
2901 function preventDefault(e) {
\r
2902 if (e.preventDefault) {
\r
2903 e.preventDefault();
\r
2905 e.returnValue = false;
\r
2910 // @function stop(ev: DOMEvent): this
\r
2911 // Does `stopPropagation` and `preventDefault` at the same time.
\r
2912 function stop(e) {
\r
2913 preventDefault(e);
\r
2914 stopPropagation(e);
\r
2918 // @function getPropagationPath(ev: DOMEvent): Array
\r
2919 // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath).
\r
2920 // Returns an array containing the `HTMLElement`s that the given DOM event
\r
2921 // should propagate to (if not stopped).
\r
2922 function getPropagationPath(ev) {
\r
2923 if (ev.composedPath) {
\r
2924 return ev.composedPath();
\r
2928 var el = ev.target;
\r
2932 el = el.parentNode;
\r
2938 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
\r
2939 // Gets normalized mouse position from a DOM event relative to the
\r
2940 // `container` (border excluded) or to the whole page if not specified.
\r
2941 function getMousePosition(e, container) {
\r
2943 return new Point(e.clientX, e.clientY);
\r
2946 var scale = getScale(container),
\r
2947 offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
\r
2950 // offset.left/top values are in page scale (like clientX/Y),
\r
2951 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
\r
2952 (e.clientX - offset.left) / scale.x - container.clientLeft,
\r
2953 (e.clientY - offset.top) / scale.y - container.clientTop
\r
2958 // except , Safari and
\r
2959 // We need double the scroll pixels (see #7403 and #4538) for all Browsers
\r
2960 // except OSX (Mac) -> 3x, Chrome running on Linux 1x
\r
2962 var wheelPxFactor =
\r
2963 (Browser.linux && Browser.chrome) ? window.devicePixelRatio :
\r
2964 Browser.mac ? window.devicePixelRatio * 3 :
\r
2965 window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1;
\r
2966 // @function getWheelDelta(ev: DOMEvent): Number
\r
2967 // Gets normalized wheel delta from a wheel DOM event, in vertical
\r
2968 // pixels scrolled (negative if scrolling down).
\r
2969 // Events from pointing devices without precise scrolling are mapped to
\r
2970 // a best guess of 60 pixels.
\r
2971 function getWheelDelta(e) {
\r
2972 return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
\r
2973 (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
\r
2974 (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
\r
2975 (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
\r
2976 (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
\r
2977 e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
\r
2978 (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
\r
2979 e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
\r
2983 // check if element really left/entered the event target (for mouseenter/mouseleave)
\r
2984 function isExternalTarget(el, e) {
\r
2986 var related = e.relatedTarget;
\r
2988 if (!related) { return true; }
\r
2991 while (related && (related !== el)) {
\r
2992 related = related.parentNode;
\r
2997 return (related !== el);
\r
3004 stopPropagation: stopPropagation,
3005 disableScrollPropagation: disableScrollPropagation,
3006 disableClickPropagation: disableClickPropagation,
3007 preventDefault: preventDefault,
3009 getPropagationPath: getPropagationPath,
3010 getMousePosition: getMousePosition,
3011 getWheelDelta: getWheelDelta,
3012 isExternalTarget: isExternalTarget,
3018 * @class PosAnimation
3019 * @aka L.PosAnimation
3021 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
3025 * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map);
3027 * myPositionMarker.on("click", function() {
3028 * var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng());
3030 * var fx = new L.PosAnimation();
3032 * fx.once('end',function() {
3034 * fx.run(myPositionMarker._icon, pos, 0.8);
3037 * fx.run(myPositionMarker._icon, pos, 0.3);
3042 * @constructor L.PosAnimation()
3043 * Creates a `PosAnimation` object.
3047 var PosAnimation = Evented.extend({
3049 // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
3050 // Run an animation of a given element to a new position, optionally setting
3051 // duration in seconds (`0.25` by default) and easing linearity factor (3rd
3052 // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
3053 // `0.5` by default).
3054 run: function (el, newPos, duration, easeLinearity) {
3058 this._inProgress = true;
3059 this._duration = duration || 0.25;
3060 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
3062 this._startPos = getPosition(el);
3063 this._offset = newPos.subtract(this._startPos);
3064 this._startTime = +new Date();
3066 // @event start: Event
3067 // Fired when the animation starts
3074 // Stops the animation (if currently running).
3076 if (!this._inProgress) { return; }
3082 _animate: function () {
3084 this._animId = requestAnimFrame(this._animate, this);
3088 _step: function (round) {
3089 var elapsed = (+new Date()) - this._startTime,
3090 duration = this._duration * 1000;
3092 if (elapsed < duration) {
3093 this._runFrame(this._easeOut(elapsed / duration), round);
3100 _runFrame: function (progress, round) {
3101 var pos = this._startPos.add(this._offset.multiplyBy(progress));
3105 setPosition(this._el, pos);
3107 // @event step: Event
3108 // Fired continuously during the animation.
3112 _complete: function () {
3113 cancelAnimFrame(this._animId);
3115 this._inProgress = false;
3116 // @event end: Event
3117 // Fired when the animation ends.
3121 _easeOut: function (t) {
3122 return 1 - Math.pow(1 - t, this._easeOutPower);
3129 * @inherits Evented
\r
3131 * The central class of the API — it is used to create a map on a page and manipulate it.
\r
3136 * // initialize the map on the "map" div with a given center and zoom
\r
3137 * var map = L.map('map', {
\r
3138 * center: [51.505, -0.09],
\r
3145 var Map = Evented.extend({
\r
3148 // @section Map State Options
\r
3149 // @option crs: CRS = L.CRS.EPSG3857
\r
3150 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
\r
3151 // sure what it means.
\r
3154 // @option center: LatLng = undefined
\r
3155 // Initial geographic center of the map
\r
3156 center: undefined,
\r
3158 // @option zoom: Number = undefined
\r
3159 // Initial map zoom level
\r
3162 // @option minZoom: Number = *
\r
3163 // Minimum zoom level of the map.
\r
3164 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
\r
3165 // the lowest of their `minZoom` options will be used instead.
\r
3166 minZoom: undefined,
\r
3168 // @option maxZoom: Number = *
\r
3169 // Maximum zoom level of the map.
\r
3170 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
\r
3171 // the highest of their `maxZoom` options will be used instead.
\r
3172 maxZoom: undefined,
\r
3174 // @option layers: Layer[] = []
\r
3175 // Array of layers that will be added to the map initially
\r
3178 // @option maxBounds: LatLngBounds = null
\r
3179 // When this option is set, the map restricts the view to the given
\r
3180 // geographical bounds, bouncing the user back if the user tries to pan
\r
3181 // outside the view. To set the restriction dynamically, use
\r
3182 // [`setMaxBounds`](#map-setmaxbounds) method.
\r
3183 maxBounds: undefined,
\r
3185 // @option renderer: Renderer = *
\r
3186 // The default method for drawing vector layers on the map. `L.SVG`
\r
3187 // or `L.Canvas` by default depending on browser support.
\r
3188 renderer: undefined,
\r
3191 // @section Animation Options
\r
3192 // @option zoomAnimation: Boolean = true
\r
3193 // Whether the map zoom animation is enabled. By default it's enabled
\r
3194 // in all browsers that support CSS3 Transitions except Android.
\r
3195 zoomAnimation: true,
\r
3197 // @option zoomAnimationThreshold: Number = 4
\r
3198 // Won't animate zoom if the zoom difference exceeds this value.
\r
3199 zoomAnimationThreshold: 4,
\r
3201 // @option fadeAnimation: Boolean = true
\r
3202 // Whether the tile fade animation is enabled. By default it's enabled
\r
3203 // in all browsers that support CSS3 Transitions except Android.
\r
3204 fadeAnimation: true,
\r
3206 // @option markerZoomAnimation: Boolean = true
\r
3207 // Whether markers animate their zoom with the zoom animation, if disabled
\r
3208 // they will disappear for the length of the animation. By default it's
\r
3209 // enabled in all browsers that support CSS3 Transitions except Android.
\r
3210 markerZoomAnimation: true,
\r
3212 // @option transform3DLimit: Number = 2^23
\r
3213 // Defines the maximum size of a CSS translation transform. The default
\r
3214 // value should not be changed unless a web browser positions layers in
\r
3215 // the wrong place after doing a large `panBy`.
\r
3216 transform3DLimit: 8388608, // Precision limit of a 32-bit float
\r
3218 // @section Interaction Options
\r
3219 // @option zoomSnap: Number = 1
\r
3220 // Forces the map's zoom level to always be a multiple of this, particularly
\r
3221 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
\r
3222 // By default, the zoom level snaps to the nearest integer; lower values
\r
3223 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
\r
3224 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
\r
3227 // @option zoomDelta: Number = 1
\r
3228 // Controls how much the map's zoom level will change after a
\r
3229 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
\r
3230 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
\r
3231 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
\r
3234 // @option trackResize: Boolean = true
\r
3235 // Whether the map automatically handles browser window resize to update itself.
\r
3239 initialize: function (id, options) { // (HTMLElement or String, Object)
\r
3240 options = setOptions(this, options);
\r
3242 // Make sure to assign internal flags at the beginning,
\r
3243 // to avoid inconsistent state in some edge cases.
\r
3244 this._handlers = [];
\r
3245 this._layers = {};
\r
3246 this._zoomBoundLayers = {};
\r
3247 this._sizeChanged = true;
\r
3249 this._initContainer(id);
\r
3250 this._initLayout();
\r
3252 // hack for https://github.com/Leaflet/Leaflet/issues/1980
\r
3253 this._onResize = bind(this._onResize, this);
\r
3255 this._initEvents();
\r
3257 if (options.maxBounds) {
\r
3258 this.setMaxBounds(options.maxBounds);
\r
3261 if (options.zoom !== undefined) {
\r
3262 this._zoom = this._limitZoom(options.zoom);
\r
3265 if (options.center && options.zoom !== undefined) {
\r
3266 this.setView(toLatLng(options.center), options.zoom, {reset: true});
\r
3269 this.callInitHooks();
\r
3271 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
\r
3272 this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&
\r
3273 this.options.zoomAnimation;
\r
3275 // zoom transitions run with the same duration for all layers, so if one of transitionend events
\r
3276 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
\r
3277 if (this._zoomAnimated) {
\r
3278 this._createAnimProxy();
\r
3279 on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
\r
3282 this._addLayers(this.options.layers);
\r
3286 // @section Methods for modifying map state
\r
3288 // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
\r
3289 // Sets the view of the map (geographical center and zoom) with the given
\r
3290 // animation options.
\r
3291 setView: function (center, zoom, options) {
\r
3293 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
\r
3294 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
\r
3295 options = options || {};
\r
3299 if (this._loaded && !options.reset && options !== true) {
\r
3301 if (options.animate !== undefined) {
\r
3302 options.zoom = extend({animate: options.animate}, options.zoom);
\r
3303 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
\r
3306 // try animating pan or zoom
\r
3307 var moved = (this._zoom !== zoom) ?
\r
3308 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
\r
3309 this._tryAnimatedPan(center, options.pan);
\r
3312 // prevent resize handler call, the view will refresh after animation anyway
\r
3313 clearTimeout(this._sizeTimer);
\r
3318 // animation didn't start, just reset the map view
\r
3319 this._resetView(center, zoom, options.pan && options.pan.noMoveStart);
\r
3324 // @method setZoom(zoom: Number, options?: Zoom/pan options): this
\r
3325 // Sets the zoom of the map.
\r
3326 setZoom: function (zoom, options) {
\r
3327 if (!this._loaded) {
\r
3328 this._zoom = zoom;
\r
3331 return this.setView(this.getCenter(), zoom, {zoom: options});
\r
3334 // @method zoomIn(delta?: Number, options?: Zoom options): this
\r
3335 // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
\r
3336 zoomIn: function (delta, options) {
\r
3337 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
\r
3338 return this.setZoom(this._zoom + delta, options);
\r
3341 // @method zoomOut(delta?: Number, options?: Zoom options): this
\r
3342 // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
\r
3343 zoomOut: function (delta, options) {
\r
3344 delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
\r
3345 return this.setZoom(this._zoom - delta, options);
\r
3348 // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
\r
3349 // Zooms the map while keeping a specified geographical point on the map
\r
3350 // stationary (e.g. used internally for scroll zoom and double-click zoom).
\r
3352 // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
\r
3353 // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
\r
3354 setZoomAround: function (latlng, zoom, options) {
\r
3355 var scale = this.getZoomScale(zoom),
\r
3356 viewHalf = this.getSize().divideBy(2),
\r
3357 containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
\r
3359 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
\r
3360 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
\r
3362 return this.setView(newCenter, zoom, {zoom: options});
\r
3365 _getBoundsCenterZoom: function (bounds, options) {
\r
3367 options = options || {};
\r
3368 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
\r
3370 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
\r
3371 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
\r
3373 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
\r
3375 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
\r
3377 if (zoom === Infinity) {
\r
3379 center: bounds.getCenter(),
\r
3384 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
\r
3386 swPoint = this.project(bounds.getSouthWest(), zoom),
\r
3387 nePoint = this.project(bounds.getNorthEast(), zoom),
\r
3388 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
\r
3396 // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
\r
3397 // Sets a map view that contains the given geographical bounds with the
\r
3398 // maximum zoom level possible.
\r
3399 fitBounds: function (bounds, options) {
\r
3401 bounds = toLatLngBounds(bounds);
\r
3403 if (!bounds.isValid()) {
\r
3404 throw new Error('Bounds are not valid.');
\r
3407 var target = this._getBoundsCenterZoom(bounds, options);
\r
3408 return this.setView(target.center, target.zoom, options);
\r
3411 // @method fitWorld(options?: fitBounds options): this
\r
3412 // Sets a map view that mostly contains the whole world with the maximum
\r
3413 // zoom level possible.
\r
3414 fitWorld: function (options) {
\r
3415 return this.fitBounds([[-90, -180], [90, 180]], options);
\r
3418 // @method panTo(latlng: LatLng, options?: Pan options): this
\r
3419 // Pans the map to a given center.
\r
3420 panTo: function (center, options) { // (LatLng)
\r
3421 return this.setView(center, this._zoom, {pan: options});
\r
3424 // @method panBy(offset: Point, options?: Pan options): this
\r
3425 // Pans the map by a given number of pixels (animated).
\r
3426 panBy: function (offset, options) {
\r
3427 offset = toPoint(offset).round();
\r
3428 options = options || {};
\r
3430 if (!offset.x && !offset.y) {
\r
3431 return this.fire('moveend');
\r
3433 // If we pan too far, Chrome gets issues with tiles
\r
3434 // and makes them disappear or appear in the wrong place (slightly offset) #2602
\r
3435 if (options.animate !== true && !this.getSize().contains(offset)) {
\r
3436 this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
\r
3440 if (!this._panAnim) {
\r
3441 this._panAnim = new PosAnimation();
\r
3443 this._panAnim.on({
\r
3444 'step': this._onPanTransitionStep,
\r
3445 'end': this._onPanTransitionEnd
\r
3449 // don't fire movestart if animating inertia
\r
3450 if (!options.noMoveStart) {
\r
3451 this.fire('movestart');
\r
3454 // animate pan unless animate: false specified
\r
3455 if (options.animate !== false) {
\r
3456 addClass(this._mapPane, 'leaflet-pan-anim');
\r
3458 var newPos = this._getMapPanePos().subtract(offset).round();
\r
3459 this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
\r
3461 this._rawPanBy(offset);
\r
3462 this.fire('move').fire('moveend');
\r
3468 // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
\r
3469 // Sets the view of the map (geographical center and zoom) performing a smooth
\r
3470 // pan-zoom animation.
\r
3471 flyTo: function (targetCenter, targetZoom, options) {
\r
3473 options = options || {};
\r
3474 if (options.animate === false || !Browser.any3d) {
\r
3475 return this.setView(targetCenter, targetZoom, options);
\r
3480 var from = this.project(this.getCenter()),
\r
3481 to = this.project(targetCenter),
\r
3482 size = this.getSize(),
\r
3483 startZoom = this._zoom;
\r
3485 targetCenter = toLatLng(targetCenter);
\r
3486 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
\r
3488 var w0 = Math.max(size.x, size.y),
\r
3489 w1 = w0 * this.getZoomScale(startZoom, targetZoom),
\r
3490 u1 = (to.distanceTo(from)) || 1,
\r
3495 var s1 = i ? -1 : 1,
\r
3497 t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
\r
3498 b1 = 2 * s2 * rho2 * u1,
\r
3500 sq = Math.sqrt(b * b + 1) - b;
\r
3502 // workaround for floating point precision bug when sq = 0, log = -Infinite,
\r
3503 // thus triggering an infinite loop in flyTo
\r
3504 var log = sq < 0.000000001 ? -18 : Math.log(sq);
\r
3509 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
\r
3510 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
\r
3511 function tanh(n) { return sinh(n) / cosh(n); }
\r
3515 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
\r
3516 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
\r
3518 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
\r
3520 var start = Date.now(),
\r
3521 S = (r(1) - r0) / rho,
\r
3522 duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
\r
3524 function frame() {
\r
3525 var t = (Date.now() - start) / duration,
\r
3526 s = easeOut(t) * S;
\r
3529 this._flyToFrame = requestAnimFrame(frame, this);
\r
3532 this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
\r
3533 this.getScaleZoom(w0 / w(s), startZoom),
\r
3538 ._move(targetCenter, targetZoom)
\r
3543 this._moveStart(true, options.noMoveStart);
\r
3549 // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
\r
3550 // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
\r
3551 // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
\r
3552 flyToBounds: function (bounds, options) {
\r
3553 var target = this._getBoundsCenterZoom(bounds, options);
\r
3554 return this.flyTo(target.center, target.zoom, options);
\r
3557 // @method setMaxBounds(bounds: LatLngBounds): this
\r
3558 // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
\r
3559 setMaxBounds: function (bounds) {
\r
3560 bounds = toLatLngBounds(bounds);
\r
3562 if (this.listens('moveend', this._panInsideMaxBounds)) {
\r
3563 this.off('moveend', this._panInsideMaxBounds);
\r
3566 if (!bounds.isValid()) {
\r
3567 this.options.maxBounds = null;
\r
3571 this.options.maxBounds = bounds;
\r
3573 if (this._loaded) {
\r
3574 this._panInsideMaxBounds();
\r
3577 return this.on('moveend', this._panInsideMaxBounds);
\r
3580 // @method setMinZoom(zoom: Number): this
\r
3581 // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
\r
3582 setMinZoom: function (zoom) {
\r
3583 var oldZoom = this.options.minZoom;
\r
3584 this.options.minZoom = zoom;
\r
3586 if (this._loaded && oldZoom !== zoom) {
\r
3587 this.fire('zoomlevelschange');
\r
3589 if (this.getZoom() < this.options.minZoom) {
\r
3590 return this.setZoom(zoom);
\r
3597 // @method setMaxZoom(zoom: Number): this
\r
3598 // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
\r
3599 setMaxZoom: function (zoom) {
\r
3600 var oldZoom = this.options.maxZoom;
\r
3601 this.options.maxZoom = zoom;
\r
3603 if (this._loaded && oldZoom !== zoom) {
\r
3604 this.fire('zoomlevelschange');
\r
3606 if (this.getZoom() > this.options.maxZoom) {
\r
3607 return this.setZoom(zoom);
\r
3614 // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
\r
3615 // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
\r
3616 panInsideBounds: function (bounds, options) {
\r
3617 this._enforcingBounds = true;
\r
3618 var center = this.getCenter(),
\r
3619 newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
\r
3621 if (!center.equals(newCenter)) {
\r
3622 this.panTo(newCenter, options);
\r
3625 this._enforcingBounds = false;
\r
3629 // @method panInside(latlng: LatLng, options?: padding options): this
\r
3630 // Pans the map the minimum amount to make the `latlng` visible. Use
\r
3631 // padding options to fit the display to more restricted bounds.
\r
3632 // If `latlng` is already within the (optionally padded) display bounds,
\r
3633 // the map will not be panned.
\r
3634 panInside: function (latlng, options) {
\r
3635 options = options || {};
\r
3637 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
\r
3638 paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
\r
3639 pixelCenter = this.project(this.getCenter()),
\r
3640 pixelPoint = this.project(latlng),
\r
3641 pixelBounds = this.getPixelBounds(),
\r
3642 paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
\r
3643 paddedSize = paddedBounds.getSize();
\r
3645 if (!paddedBounds.contains(pixelPoint)) {
\r
3646 this._enforcingBounds = true;
\r
3647 var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
\r
3648 var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
\r
3649 pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
\r
3650 pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
\r
3651 this.panTo(this.unproject(pixelCenter), options);
\r
3652 this._enforcingBounds = false;
\r
3657 // @method invalidateSize(options: Zoom/pan options): this
\r
3658 // Checks if the map container size changed and updates the map if so —
\r
3659 // call it after you've changed the map size dynamically, also animating
\r
3660 // pan by default. If `options.pan` is `false`, panning will not occur.
\r
3661 // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
\r
3662 // that it doesn't happen often even if the method is called many
\r
3663 // times in a row.
\r
3666 // @method invalidateSize(animate: Boolean): this
\r
3667 // Checks if the map container size changed and updates the map if so —
\r
3668 // call it after you've changed the map size dynamically, also animating
\r
3669 // pan by default.
\r
3670 invalidateSize: function (options) {
\r
3671 if (!this._loaded) { return this; }
\r
3673 options = extend({
\r
3676 }, options === true ? {animate: true} : options);
\r
3678 var oldSize = this.getSize();
\r
3679 this._sizeChanged = true;
\r
3680 this._lastCenter = null;
\r
3682 var newSize = this.getSize(),
\r
3683 oldCenter = oldSize.divideBy(2).round(),
\r
3684 newCenter = newSize.divideBy(2).round(),
\r
3685 offset = oldCenter.subtract(newCenter);
\r
3687 if (!offset.x && !offset.y) { return this; }
\r
3689 if (options.animate && options.pan) {
\r
3690 this.panBy(offset);
\r
3693 if (options.pan) {
\r
3694 this._rawPanBy(offset);
\r
3697 this.fire('move');
\r
3699 if (options.debounceMoveend) {
\r
3700 clearTimeout(this._sizeTimer);
\r
3701 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
\r
3703 this.fire('moveend');
\r
3707 // @section Map state change events
\r
3708 // @event resize: ResizeEvent
\r
3709 // Fired when the map is resized.
\r
3710 return this.fire('resize', {
\r
3716 // @section Methods for modifying map state
\r
3717 // @method stop(): this
\r
3718 // Stops the currently running `panTo` or `flyTo` animation, if any.
\r
3719 stop: function () {
\r
3720 this.setZoom(this._limitZoom(this._zoom));
\r
3721 if (!this.options.zoomSnap) {
\r
3722 this.fire('viewreset');
\r
3724 return this._stop();
\r
3727 // @section Geolocation methods
\r
3728 // @method locate(options?: Locate options): this
\r
3729 // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
\r
3730 // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
\r
3731 // and optionally sets the map view to the user's location with respect to
\r
3732 // detection accuracy (or to the world view if geolocation failed).
\r
3733 // Note that, if your page doesn't use HTTPS, this method will fail in
\r
3734 // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
\r
3735 // See `Locate options` for more details.
\r
3736 locate: function (options) {
\r
3738 options = this._locateOptions = extend({
\r
3742 // maxZoom: <Number>
\r
3744 // enableHighAccuracy: false
\r
3747 if (!('geolocation' in navigator)) {
\r
3748 this._handleGeolocationError({
\r
3750 message: 'Geolocation not supported.'
\r
3755 var onResponse = bind(this._handleGeolocationResponse, this),
\r
3756 onError = bind(this._handleGeolocationError, this);
\r
3758 if (options.watch) {
\r
3759 this._locationWatchId =
\r
3760 navigator.geolocation.watchPosition(onResponse, onError, options);
\r
3762 navigator.geolocation.getCurrentPosition(onResponse, onError, options);
\r
3767 // @method stopLocate(): this
\r
3768 // Stops watching location previously initiated by `map.locate({watch: true})`
\r
3769 // and aborts resetting the map view if map.locate was called with
\r
3770 // `{setView: true}`.
\r
3771 stopLocate: function () {
\r
3772 if (navigator.geolocation && navigator.geolocation.clearWatch) {
\r
3773 navigator.geolocation.clearWatch(this._locationWatchId);
\r
3775 if (this._locateOptions) {
\r
3776 this._locateOptions.setView = false;
\r
3781 _handleGeolocationError: function (error) {
\r
3782 if (!this._container._leaflet_id) { return; }
\r
3784 var c = error.code,
\r
3785 message = error.message ||
\r
3786 (c === 1 ? 'permission denied' :
\r
3787 (c === 2 ? 'position unavailable' : 'timeout'));
\r
3789 if (this._locateOptions.setView && !this._loaded) {
\r
3793 // @section Location events
\r
3794 // @event locationerror: ErrorEvent
\r
3795 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
\r
3796 this.fire('locationerror', {
\r
3798 message: 'Geolocation error: ' + message + '.'
\r
3802 _handleGeolocationResponse: function (pos) {
\r
3803 if (!this._container._leaflet_id) { return; }
\r
3805 var lat = pos.coords.latitude,
\r
3806 lng = pos.coords.longitude,
\r
3807 latlng = new LatLng(lat, lng),
\r
3808 bounds = latlng.toBounds(pos.coords.accuracy * 2),
\r
3809 options = this._locateOptions;
\r
3811 if (options.setView) {
\r
3812 var zoom = this.getBoundsZoom(bounds);
\r
3813 this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
\r
3819 timestamp: pos.timestamp
\r
3822 for (var i in pos.coords) {
\r
3823 if (typeof pos.coords[i] === 'number') {
\r
3824 data[i] = pos.coords[i];
\r
3828 // @event locationfound: LocationEvent
\r
3829 // Fired when geolocation (using the [`locate`](#map-locate) method)
\r
3830 // went successfully.
\r
3831 this.fire('locationfound', data);
\r
3834 // TODO Appropriate docs section?
\r
3835 // @section Other Methods
\r
3836 // @method addHandler(name: String, HandlerClass: Function): this
\r
3837 // Adds a new `Handler` to the map, given its name and constructor function.
\r
3838 addHandler: function (name, HandlerClass) {
\r
3839 if (!HandlerClass) { return this; }
\r
3841 var handler = this[name] = new HandlerClass(this);
\r
3843 this._handlers.push(handler);
\r
3845 if (this.options[name]) {
\r
3852 // @method remove(): this
\r
3853 // Destroys the map and clears all related event listeners.
\r
3854 remove: function () {
\r
3856 this._initEvents(true);
\r
3857 if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }
\r
3859 if (this._containerId !== this._container._leaflet_id) {
\r
3860 throw new Error('Map container is being reused by another instance');
\r
3864 // throws error in IE6-8
\r
3865 delete this._container._leaflet_id;
\r
3866 delete this._containerId;
\r
3868 /*eslint-disable */
\r
3869 this._container._leaflet_id = undefined;
\r
3870 /* eslint-enable */
\r
3871 this._containerId = undefined;
\r
3874 if (this._locationWatchId !== undefined) {
\r
3875 this.stopLocate();
\r
3880 remove(this._mapPane);
\r
3882 if (this._clearControlPos) {
\r
3883 this._clearControlPos();
\r
3885 if (this._resizeRequest) {
\r
3886 cancelAnimFrame(this._resizeRequest);
\r
3887 this._resizeRequest = null;
\r
3890 this._clearHandlers();
\r
3892 if (this._loaded) {
\r
3893 // @section Map state change events
\r
3894 // @event unload: Event
\r
3895 // Fired when the map is destroyed with [remove](#map-remove) method.
\r
3896 this.fire('unload');
\r
3900 for (i in this._layers) {
\r
3901 this._layers[i].remove();
\r
3903 for (i in this._panes) {
\r
3904 remove(this._panes[i]);
\r
3907 this._layers = [];
\r
3909 delete this._mapPane;
\r
3910 delete this._renderer;
\r
3915 // @section Other Methods
\r
3916 // @method createPane(name: String, container?: HTMLElement): HTMLElement
\r
3917 // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
\r
3918 // then returns it. The pane is created as a child of `container`, or
\r
3919 // as a child of the main map pane if not set.
\r
3920 createPane: function (name, container) {
\r
3921 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
\r
3922 pane = create$1('div', className, container || this._mapPane);
\r
3925 this._panes[name] = pane;
\r
3930 // @section Methods for Getting Map State
\r
3932 // @method getCenter(): LatLng
\r
3933 // Returns the geographical center of the map view
\r
3934 getCenter: function () {
\r
3935 this._checkIfLoaded();
\r
3937 if (this._lastCenter && !this._moved()) {
\r
3938 return this._lastCenter.clone();
\r
3940 return this.layerPointToLatLng(this._getCenterLayerPoint());
\r
3943 // @method getZoom(): Number
\r
3944 // Returns the current zoom level of the map view
\r
3945 getZoom: function () {
\r
3946 return this._zoom;
\r
3949 // @method getBounds(): LatLngBounds
\r
3950 // Returns the geographical bounds visible in the current map view
\r
3951 getBounds: function () {
\r
3952 var bounds = this.getPixelBounds(),
\r
3953 sw = this.unproject(bounds.getBottomLeft()),
\r
3954 ne = this.unproject(bounds.getTopRight());
\r
3956 return new LatLngBounds(sw, ne);
\r
3959 // @method getMinZoom(): Number
\r
3960 // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
\r
3961 getMinZoom: function () {
\r
3962 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
\r
3965 // @method getMaxZoom(): Number
\r
3966 // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
\r
3967 getMaxZoom: function () {
\r
3968 return this.options.maxZoom === undefined ?
\r
3969 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
\r
3970 this.options.maxZoom;
\r
3973 // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
\r
3974 // Returns the maximum zoom level on which the given bounds fit to the map
\r
3975 // view in its entirety. If `inside` (optional) is set to `true`, the method
\r
3976 // instead returns the minimum zoom level on which the map view fits into
\r
3977 // the given bounds in its entirety.
\r
3978 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
\r
3979 bounds = toLatLngBounds(bounds);
\r
3980 padding = toPoint(padding || [0, 0]);
\r
3982 var zoom = this.getZoom() || 0,
\r
3983 min = this.getMinZoom(),
\r
3984 max = this.getMaxZoom(),
\r
3985 nw = bounds.getNorthWest(),
\r
3986 se = bounds.getSouthEast(),
\r
3987 size = this.getSize().subtract(padding),
\r
3988 boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
\r
3989 snap = Browser.any3d ? this.options.zoomSnap : 1,
\r
3990 scalex = size.x / boundsSize.x,
\r
3991 scaley = size.y / boundsSize.y,
\r
3992 scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
\r
3994 zoom = this.getScaleZoom(scale, zoom);
\r
3997 zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
\r
3998 zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
\r
4001 return Math.max(min, Math.min(max, zoom));
\r
4004 // @method getSize(): Point
\r
4005 // Returns the current size of the map container (in pixels).
\r
4006 getSize: function () {
\r
4007 if (!this._size || this._sizeChanged) {
\r
4008 this._size = new Point(
\r
4009 this._container.clientWidth || 0,
\r
4010 this._container.clientHeight || 0);
\r
4012 this._sizeChanged = false;
\r
4014 return this._size.clone();
\r
4017 // @method getPixelBounds(): Bounds
\r
4018 // Returns the bounds of the current map view in projected pixel
\r
4019 // coordinates (sometimes useful in layer and overlay implementations).
\r
4020 getPixelBounds: function (center, zoom) {
\r
4021 var topLeftPoint = this._getTopLeftPoint(center, zoom);
\r
4022 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
\r
4025 // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
\r
4026 // the map pane? "left point of the map layer" can be confusing, specially
\r
4027 // since there can be negative offsets.
\r
4028 // @method getPixelOrigin(): Point
\r
4029 // Returns the projected pixel coordinates of the top left point of
\r
4030 // the map layer (useful in custom layer and overlay implementations).
\r
4031 getPixelOrigin: function () {
\r
4032 this._checkIfLoaded();
\r
4033 return this._pixelOrigin;
\r
4036 // @method getPixelWorldBounds(zoom?: Number): Bounds
\r
4037 // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
\r
4038 // If `zoom` is omitted, the map's current zoom level is used.
\r
4039 getPixelWorldBounds: function (zoom) {
\r
4040 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
\r
4043 // @section Other Methods
\r
4045 // @method getPane(pane: String|HTMLElement): HTMLElement
\r
4046 // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
\r
4047 getPane: function (pane) {
\r
4048 return typeof pane === 'string' ? this._panes[pane] : pane;
\r
4051 // @method getPanes(): Object
\r
4052 // Returns a plain object containing the names of all [panes](#map-pane) as keys and
\r
4053 // the panes as values.
\r
4054 getPanes: function () {
\r
4055 return this._panes;
\r
4058 // @method getContainer: HTMLElement
\r
4059 // Returns the HTML element that contains the map.
\r
4060 getContainer: function () {
\r
4061 return this._container;
\r
4065 // @section Conversion Methods
\r
4067 // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
\r
4068 // Returns the scale factor to be applied to a map transition from zoom level
\r
4069 // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
\r
4070 getZoomScale: function (toZoom, fromZoom) {
\r
4071 // TODO replace with universal implementation after refactoring projections
\r
4072 var crs = this.options.crs;
\r
4073 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
\r
4074 return crs.scale(toZoom) / crs.scale(fromZoom);
\r
4077 // @method getScaleZoom(scale: Number, fromZoom: Number): Number
\r
4078 // Returns the zoom level that the map would end up at, if it is at `fromZoom`
\r
4079 // level and everything is scaled by a factor of `scale`. Inverse of
\r
4080 // [`getZoomScale`](#map-getZoomScale).
\r
4081 getScaleZoom: function (scale, fromZoom) {
\r
4082 var crs = this.options.crs;
\r
4083 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
\r
4084 var zoom = crs.zoom(scale * crs.scale(fromZoom));
\r
4085 return isNaN(zoom) ? Infinity : zoom;
\r
4088 // @method project(latlng: LatLng, zoom: Number): Point
\r
4089 // Projects a geographical coordinate `LatLng` according to the projection
\r
4090 // of the map's CRS, then scales it according to `zoom` and the CRS's
\r
4091 // `Transformation`. The result is pixel coordinate relative to
\r
4092 // the CRS origin.
\r
4093 project: function (latlng, zoom) {
\r
4094 zoom = zoom === undefined ? this._zoom : zoom;
\r
4095 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
\r
4098 // @method unproject(point: Point, zoom: Number): LatLng
\r
4099 // Inverse of [`project`](#map-project).
\r
4100 unproject: function (point, zoom) {
\r
4101 zoom = zoom === undefined ? this._zoom : zoom;
\r
4102 return this.options.crs.pointToLatLng(toPoint(point), zoom);
\r
4105 // @method layerPointToLatLng(point: Point): LatLng
\r
4106 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
\r
4107 // returns the corresponding geographical coordinate (for the current zoom level).
\r
4108 layerPointToLatLng: function (point) {
\r
4109 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
\r
4110 return this.unproject(projectedPoint);
\r
4113 // @method latLngToLayerPoint(latlng: LatLng): Point
\r
4114 // Given a geographical coordinate, returns the corresponding pixel coordinate
\r
4115 // relative to the [origin pixel](#map-getpixelorigin).
\r
4116 latLngToLayerPoint: function (latlng) {
\r
4117 var projectedPoint = this.project(toLatLng(latlng))._round();
\r
4118 return projectedPoint._subtract(this.getPixelOrigin());
\r
4121 // @method wrapLatLng(latlng: LatLng): LatLng
\r
4122 // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
\r
4123 // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
\r
4125 // By default this means longitude is wrapped around the dateline so its
\r
4126 // value is between -180 and +180 degrees.
\r
4127 wrapLatLng: function (latlng) {
\r
4128 return this.options.crs.wrapLatLng(toLatLng(latlng));
\r
4131 // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
\r
4132 // Returns a `LatLngBounds` with the same size as the given one, ensuring that
\r
4133 // its center is within the CRS's bounds.
\r
4134 // By default this means the center longitude is wrapped around the dateline so its
\r
4135 // value is between -180 and +180 degrees, and the majority of the bounds
\r
4136 // overlaps the CRS's bounds.
\r
4137 wrapLatLngBounds: function (latlng) {
\r
4138 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
\r
4141 // @method distance(latlng1: LatLng, latlng2: LatLng): Number
\r
4142 // Returns the distance between two geographical coordinates according to
\r
4143 // the map's CRS. By default this measures distance in meters.
\r
4144 distance: function (latlng1, latlng2) {
\r
4145 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
\r
4148 // @method containerPointToLayerPoint(point: Point): Point
\r
4149 // Given a pixel coordinate relative to the map container, returns the corresponding
\r
4150 // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
\r
4151 containerPointToLayerPoint: function (point) { // (Point)
\r
4152 return toPoint(point).subtract(this._getMapPanePos());
\r
4155 // @method layerPointToContainerPoint(point: Point): Point
\r
4156 // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
\r
4157 // returns the corresponding pixel coordinate relative to the map container.
\r
4158 layerPointToContainerPoint: function (point) { // (Point)
\r
4159 return toPoint(point).add(this._getMapPanePos());
\r
4162 // @method containerPointToLatLng(point: Point): LatLng
\r
4163 // Given a pixel coordinate relative to the map container, returns
\r
4164 // the corresponding geographical coordinate (for the current zoom level).
\r
4165 containerPointToLatLng: function (point) {
\r
4166 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
\r
4167 return this.layerPointToLatLng(layerPoint);
\r
4170 // @method latLngToContainerPoint(latlng: LatLng): Point
\r
4171 // Given a geographical coordinate, returns the corresponding pixel coordinate
\r
4172 // relative to the map container.
\r
4173 latLngToContainerPoint: function (latlng) {
\r
4174 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
\r
4177 // @method mouseEventToContainerPoint(ev: MouseEvent): Point
\r
4178 // Given a MouseEvent object, returns the pixel coordinate relative to the
\r
4179 // map container where the event took place.
\r
4180 mouseEventToContainerPoint: function (e) {
\r
4181 return getMousePosition(e, this._container);
\r
4184 // @method mouseEventToLayerPoint(ev: MouseEvent): Point
\r
4185 // Given a MouseEvent object, returns the pixel coordinate relative to
\r
4186 // the [origin pixel](#map-getpixelorigin) where the event took place.
\r
4187 mouseEventToLayerPoint: function (e) {
\r
4188 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
\r
4191 // @method mouseEventToLatLng(ev: MouseEvent): LatLng
\r
4192 // Given a MouseEvent object, returns geographical coordinate where the
\r
4193 // event took place.
\r
4194 mouseEventToLatLng: function (e) { // (MouseEvent)
\r
4195 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
\r
4199 // map initialization methods
\r
4201 _initContainer: function (id) {
\r
4202 var container = this._container = get(id);
\r
4205 throw new Error('Map container not found.');
\r
4206 } else if (container._leaflet_id) {
\r
4207 throw new Error('Map container is already initialized.');
\r
4210 on(container, 'scroll', this._onScroll, this);
\r
4211 this._containerId = stamp(container);
\r
4214 _initLayout: function () {
\r
4215 var container = this._container;
\r
4217 this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;
\r
4219 addClass(container, 'leaflet-container' +
\r
4220 (Browser.touch ? ' leaflet-touch' : '') +
\r
4221 (Browser.retina ? ' leaflet-retina' : '') +
\r
4222 (Browser.ielt9 ? ' leaflet-oldie' : '') +
\r
4223 (Browser.safari ? ' leaflet-safari' : '') +
\r
4224 (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
\r
4226 var position = getStyle(container, 'position');
\r
4228 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {
\r
4229 container.style.position = 'relative';
\r
4232 this._initPanes();
\r
4234 if (this._initControlPos) {
\r
4235 this._initControlPos();
\r
4239 _initPanes: function () {
\r
4240 var panes = this._panes = {};
\r
4241 this._paneRenderers = {};
\r
4245 // Panes are DOM elements used to control the ordering of layers on the map. You
\r
4246 // can access panes with [`map.getPane`](#map-getpane) or
\r
4247 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
\r
4248 // [`map.createPane`](#map-createpane) method.
\r
4250 // Every map has the following default panes that differ only in zIndex.
\r
4252 // @pane mapPane: HTMLElement = 'auto'
\r
4253 // Pane that contains all other map panes
\r
4255 this._mapPane = this.createPane('mapPane', this._container);
\r
4256 setPosition(this._mapPane, new Point(0, 0));
\r
4258 // @pane tilePane: HTMLElement = 200
\r
4259 // Pane for `GridLayer`s and `TileLayer`s
\r
4260 this.createPane('tilePane');
\r
4261 // @pane overlayPane: HTMLElement = 400
\r
4262 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
\r
4263 this.createPane('overlayPane');
\r
4264 // @pane shadowPane: HTMLElement = 500
\r
4265 // Pane for overlay shadows (e.g. `Marker` shadows)
\r
4266 this.createPane('shadowPane');
\r
4267 // @pane markerPane: HTMLElement = 600
\r
4268 // Pane for `Icon`s of `Marker`s
\r
4269 this.createPane('markerPane');
\r
4270 // @pane tooltipPane: HTMLElement = 650
\r
4271 // Pane for `Tooltip`s.
\r
4272 this.createPane('tooltipPane');
\r
4273 // @pane popupPane: HTMLElement = 700
\r
4274 // Pane for `Popup`s.
\r
4275 this.createPane('popupPane');
\r
4277 if (!this.options.markerZoomAnimation) {
\r
4278 addClass(panes.markerPane, 'leaflet-zoom-hide');
\r
4279 addClass(panes.shadowPane, 'leaflet-zoom-hide');
\r
4284 // private methods that modify map state
\r
4286 // @section Map state change events
\r
4287 _resetView: function (center, zoom, noMoveStart) {
\r
4288 setPosition(this._mapPane, new Point(0, 0));
\r
4290 var loading = !this._loaded;
\r
4291 this._loaded = true;
\r
4292 zoom = this._limitZoom(zoom);
\r
4294 this.fire('viewprereset');
\r
4296 var zoomChanged = this._zoom !== zoom;
\r
4298 ._moveStart(zoomChanged, noMoveStart)
\r
4299 ._move(center, zoom)
\r
4300 ._moveEnd(zoomChanged);
\r
4302 // @event viewreset: Event
\r
4303 // Fired when the map needs to redraw its content (this usually happens
\r
4304 // on map zoom or load). Very useful for creating custom overlays.
\r
4305 this.fire('viewreset');
\r
4307 // @event load: Event
\r
4308 // Fired when the map is initialized (when its center and zoom are set
\r
4309 // for the first time).
\r
4311 this.fire('load');
\r
4315 _moveStart: function (zoomChanged, noMoveStart) {
\r
4316 // @event zoomstart: Event
\r
4317 // Fired when the map zoom is about to change (e.g. before zoom animation).
\r
4318 // @event movestart: Event
\r
4319 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
\r
4320 if (zoomChanged) {
\r
4321 this.fire('zoomstart');
\r
4323 if (!noMoveStart) {
\r
4324 this.fire('movestart');
\r
4329 _move: function (center, zoom, data, supressEvent) {
\r
4330 if (zoom === undefined) {
\r
4331 zoom = this._zoom;
\r
4333 var zoomChanged = this._zoom !== zoom;
\r
4335 this._zoom = zoom;
\r
4336 this._lastCenter = center;
\r
4337 this._pixelOrigin = this._getNewPixelOrigin(center);
\r
4339 if (!supressEvent) {
\r
4340 // @event zoom: Event
\r
4341 // Fired repeatedly during any change in zoom level,
\r
4342 // including zoom and fly animations.
\r
4343 if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
\r
4344 this.fire('zoom', data);
\r
4347 // @event move: Event
\r
4348 // Fired repeatedly during any movement of the map,
\r
4349 // including pan and fly animations.
\r
4350 this.fire('move', data);
\r
4351 } else if (data && data.pinch) { // Always fire 'zoom' if pinching because #3530
\r
4352 this.fire('zoom', data);
\r
4357 _moveEnd: function (zoomChanged) {
\r
4358 // @event zoomend: Event
\r
4359 // Fired when the map zoom changed, after any animations.
\r
4360 if (zoomChanged) {
\r
4361 this.fire('zoomend');
\r
4364 // @event moveend: Event
\r
4365 // Fired when the center of the map stops changing
\r
4366 // (e.g. user stopped dragging the map or after non-centered zoom).
\r
4367 return this.fire('moveend');
\r
4370 _stop: function () {
\r
4371 cancelAnimFrame(this._flyToFrame);
\r
4372 if (this._panAnim) {
\r
4373 this._panAnim.stop();
\r
4378 _rawPanBy: function (offset) {
\r
4379 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
\r
4382 _getZoomSpan: function () {
\r
4383 return this.getMaxZoom() - this.getMinZoom();
\r
4386 _panInsideMaxBounds: function () {
\r
4387 if (!this._enforcingBounds) {
\r
4388 this.panInsideBounds(this.options.maxBounds);
\r
4392 _checkIfLoaded: function () {
\r
4393 if (!this._loaded) {
\r
4394 throw new Error('Set map center and zoom first.');
\r
4398 // DOM event handling
\r
4400 // @section Interaction events
\r
4401 _initEvents: function (remove) {
\r
4402 this._targets = {};
\r
4403 this._targets[stamp(this._container)] = this;
\r
4405 var onOff = remove ? off : on;
\r
4407 // @event click: MouseEvent
\r
4408 // Fired when the user clicks (or taps) the map.
\r
4409 // @event dblclick: MouseEvent
\r
4410 // Fired when the user double-clicks (or double-taps) the map.
\r
4411 // @event mousedown: MouseEvent
\r
4412 // Fired when the user pushes the mouse button on the map.
\r
4413 // @event mouseup: MouseEvent
\r
4414 // Fired when the user releases the mouse button on the map.
\r
4415 // @event mouseover: MouseEvent
\r
4416 // Fired when the mouse enters the map.
\r
4417 // @event mouseout: MouseEvent
\r
4418 // Fired when the mouse leaves the map.
\r
4419 // @event mousemove: MouseEvent
\r
4420 // Fired while the mouse moves over the map.
\r
4421 // @event contextmenu: MouseEvent
\r
4422 // Fired when the user pushes the right mouse button on the map, prevents
\r
4423 // default browser context menu from showing if there are listeners on
\r
4424 // this event. Also fired on mobile when the user holds a single touch
\r
4425 // for a second (also called long press).
\r
4426 // @event keypress: KeyboardEvent
\r
4427 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
\r
4428 // @event keydown: KeyboardEvent
\r
4429 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
\r
4430 // the `keydown` event is fired for keys that produce a character value and for keys
\r
4431 // that do not produce a character value.
\r
4432 // @event keyup: KeyboardEvent
\r
4433 // Fired when the user releases a key from the keyboard while the map is focused.
\r
4434 onOff(this._container, 'click dblclick mousedown mouseup ' +
\r
4435 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
\r
4437 if (this.options.trackResize) {
\r
4438 onOff(window, 'resize', this._onResize, this);
\r
4441 if (Browser.any3d && this.options.transform3DLimit) {
\r
4442 (remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
\r
4446 _onResize: function () {
\r
4447 cancelAnimFrame(this._resizeRequest);
\r
4448 this._resizeRequest = requestAnimFrame(
\r
4449 function () { this.invalidateSize({debounceMoveend: true}); }, this);
\r
4452 _onScroll: function () {
\r
4453 this._container.scrollTop = 0;
\r
4454 this._container.scrollLeft = 0;
\r
4457 _onMoveEnd: function () {
\r
4458 var pos = this._getMapPanePos();
\r
4459 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
\r
4460 // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
\r
4461 // a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/
\r
4462 this._resetView(this.getCenter(), this.getZoom());
\r
4466 _findEventTargets: function (e, type) {
\r
4469 isHover = type === 'mouseout' || type === 'mouseover',
\r
4470 src = e.target || e.srcElement,
\r
4474 target = this._targets[stamp(src)];
\r
4475 if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
\r
4476 // Prevent firing click after you just dragged an object.
\r
4480 if (target && target.listens(type, true)) {
\r
4481 if (isHover && !isExternalTarget(src, e)) { break; }
\r
4482 targets.push(target);
\r
4483 if (isHover) { break; }
\r
4485 if (src === this._container) { break; }
\r
4486 src = src.parentNode;
\r
4488 if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
\r
4494 _isClickDisabled: function (el) {
\r
4495 while (el && el !== this._container) {
\r
4496 if (el['_leaflet_disable_click']) { return true; }
\r
4497 el = el.parentNode;
\r
4501 _handleDOMEvent: function (e) {
\r
4502 var el = (e.target || e.srcElement);
\r
4503 if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
\r
4507 var type = e.type;
\r
4509 if (type === 'mousedown') {
\r
4510 // prevents outline when clicking on keyboard-focusable element
\r
4511 preventOutline(el);
\r
4514 this._fireDOMEvent(e, type);
\r
4517 _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
\r
4519 _fireDOMEvent: function (e, type, canvasTargets) {
\r
4521 if (e.type === 'click') {
\r
4522 // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
\r
4523 // @event preclick: MouseEvent
\r
4524 // Fired before mouse click on the map (sometimes useful when you
\r
4525 // want something to happen on click before any existing click
\r
4526 // handlers start running).
\r
4527 var synth = extend({}, e);
\r
4528 synth.type = 'preclick';
\r
4529 this._fireDOMEvent(synth, synth.type, canvasTargets);
\r
4532 // Find the layer the event is propagating from and its parents.
\r
4533 var targets = this._findEventTargets(e, type);
\r
4535 if (canvasTargets) {
\r
4536 var filtered = []; // pick only targets with listeners
\r
4537 for (var i = 0; i < canvasTargets.length; i++) {
\r
4538 if (canvasTargets[i].listens(type, true)) {
\r
4539 filtered.push(canvasTargets[i]);
\r
4542 targets = filtered.concat(targets);
\r
4545 if (!targets.length) { return; }
\r
4547 if (type === 'contextmenu') {
\r
4548 preventDefault(e);
\r
4551 var target = targets[0];
\r
4556 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
\r
4557 var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
\r
4558 data.containerPoint = isMarker ?
\r
4559 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
\r
4560 data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
\r
4561 data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
\r
4564 for (i = 0; i < targets.length; i++) {
\r
4565 targets[i].fire(type, data, true);
\r
4566 if (data.originalEvent._stopped ||
\r
4567 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
\r
4571 _draggableMoved: function (obj) {
\r
4572 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
\r
4573 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
\r
4576 _clearHandlers: function () {
\r
4577 for (var i = 0, len = this._handlers.length; i < len; i++) {
\r
4578 this._handlers[i].disable();
\r
4582 // @section Other Methods
\r
4584 // @method whenReady(fn: Function, context?: Object): this
\r
4585 // Runs the given function `fn` when the map gets initialized with
\r
4586 // a view (center and zoom) and at least one layer, or immediately
\r
4587 // if it's already initialized, optionally passing a function context.
\r
4588 whenReady: function (callback, context) {
\r
4589 if (this._loaded) {
\r
4590 callback.call(context || this, {target: this});
\r
4592 this.on('load', callback, context);
\r
4598 // private methods for getting map state
\r
4600 _getMapPanePos: function () {
\r
4601 return getPosition(this._mapPane) || new Point(0, 0);
\r
4604 _moved: function () {
\r
4605 var pos = this._getMapPanePos();
\r
4606 return pos && !pos.equals([0, 0]);
\r
4609 _getTopLeftPoint: function (center, zoom) {
\r
4610 var pixelOrigin = center && zoom !== undefined ?
\r
4611 this._getNewPixelOrigin(center, zoom) :
\r
4612 this.getPixelOrigin();
\r
4613 return pixelOrigin.subtract(this._getMapPanePos());
\r
4616 _getNewPixelOrigin: function (center, zoom) {
\r
4617 var viewHalf = this.getSize()._divideBy(2);
\r
4618 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
\r
4621 _latLngToNewLayerPoint: function (latlng, zoom, center) {
\r
4622 var topLeft = this._getNewPixelOrigin(center, zoom);
\r
4623 return this.project(latlng, zoom)._subtract(topLeft);
\r
4626 _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
\r
4627 var topLeft = this._getNewPixelOrigin(center, zoom);
\r
4629 this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
\r
4630 this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
\r
4631 this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
\r
4632 this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
\r
4636 // layer point of the current center
\r
4637 _getCenterLayerPoint: function () {
\r
4638 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
\r
4641 // offset of the specified place to the current center in pixels
\r
4642 _getCenterOffset: function (latlng) {
\r
4643 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
\r
4646 // adjust center for view to get inside bounds
\r
4647 _limitCenter: function (center, zoom, bounds) {
\r
4649 if (!bounds) { return center; }
\r
4651 var centerPoint = this.project(center, zoom),
\r
4652 viewHalf = this.getSize().divideBy(2),
\r
4653 viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
\r
4654 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
\r
4656 // If offset is less than a pixel, ignore.
\r
4657 // This prevents unstable projections from getting into
\r
4658 // an infinite loop of tiny offsets.
\r
4659 if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
\r
4663 return this.unproject(centerPoint.add(offset), zoom);
\r
4666 // adjust offset for view to get inside bounds
\r
4667 _limitOffset: function (offset, bounds) {
\r
4668 if (!bounds) { return offset; }
\r
4670 var viewBounds = this.getPixelBounds(),
\r
4671 newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
\r
4673 return offset.add(this._getBoundsOffset(newBounds, bounds));
\r
4676 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
\r
4677 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
\r
4678 var projectedMaxBounds = toBounds(
\r
4679 this.project(maxBounds.getNorthEast(), zoom),
\r
4680 this.project(maxBounds.getSouthWest(), zoom)
\r
4682 minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
\r
4683 maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
\r
4685 dx = this._rebound(minOffset.x, -maxOffset.x),
\r
4686 dy = this._rebound(minOffset.y, -maxOffset.y);
\r
4688 return new Point(dx, dy);
\r
4691 _rebound: function (left, right) {
\r
4692 return left + right > 0 ?
\r
4693 Math.round(left - right) / 2 :
\r
4694 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
\r
4697 _limitZoom: function (zoom) {
\r
4698 var min = this.getMinZoom(),
\r
4699 max = this.getMaxZoom(),
\r
4700 snap = Browser.any3d ? this.options.zoomSnap : 1;
\r
4702 zoom = Math.round(zoom / snap) * snap;
\r
4704 return Math.max(min, Math.min(max, zoom));
\r
4707 _onPanTransitionStep: function () {
\r
4708 this.fire('move');
\r
4711 _onPanTransitionEnd: function () {
\r
4712 removeClass(this._mapPane, 'leaflet-pan-anim');
\r
4713 this.fire('moveend');
\r
4716 _tryAnimatedPan: function (center, options) {
\r
4717 // difference between the new and current centers in pixels
\r
4718 var offset = this._getCenterOffset(center)._trunc();
\r
4720 // don't animate too far unless animate: true specified in options
\r
4721 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
\r
4723 this.panBy(offset, options);
\r
4728 _createAnimProxy: function () {
\r
4730 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
\r
4731 this._panes.mapPane.appendChild(proxy);
\r
4733 this.on('zoomanim', function (e) {
\r
4734 var prop = TRANSFORM,
\r
4735 transform = this._proxy.style[prop];
\r
4737 setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
\r
4739 // workaround for case when transform is the same and so transitionend event is not fired
\r
4740 if (transform === this._proxy.style[prop] && this._animatingZoom) {
\r
4741 this._onZoomTransitionEnd();
\r
4745 this.on('load moveend', this._animMoveEnd, this);
\r
4747 this._on('unload', this._destroyAnimProxy, this);
\r
4750 _destroyAnimProxy: function () {
\r
4751 remove(this._proxy);
\r
4752 this.off('load moveend', this._animMoveEnd, this);
\r
4753 delete this._proxy;
\r
4756 _animMoveEnd: function () {
\r
4757 var c = this.getCenter(),
\r
4758 z = this.getZoom();
\r
4759 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
\r
4762 _catchTransitionEnd: function (e) {
\r
4763 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
\r
4764 this._onZoomTransitionEnd();
\r
4768 _nothingToAnimate: function () {
\r
4769 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
\r
4772 _tryAnimatedZoom: function (center, zoom, options) {
\r
4774 if (this._animatingZoom) { return true; }
\r
4776 options = options || {};
\r
4778 // don't animate if disabled, not supported or zoom difference is too large
\r
4779 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
\r
4780 Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
\r
4782 // offset is the pixel coords of the zoom origin relative to the current center
\r
4783 var scale = this.getZoomScale(zoom),
\r
4784 offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
\r
4786 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
\r
4787 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
\r
4789 requestAnimFrame(function () {
\r
4791 ._moveStart(true, options.noMoveStart || false)
\r
4792 ._animateZoom(center, zoom, true);
\r
4798 _animateZoom: function (center, zoom, startAnim, noUpdate) {
\r
4799 if (!this._mapPane) { return; }
\r
4802 this._animatingZoom = true;
\r
4804 // remember what center/zoom to set after animation
\r
4805 this._animateToCenter = center;
\r
4806 this._animateToZoom = zoom;
\r
4808 addClass(this._mapPane, 'leaflet-zoom-anim');
\r
4811 // @section Other Events
\r
4812 // @event zoomanim: ZoomAnimEvent
\r
4813 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
\r
4814 this.fire('zoomanim', {
\r
4817 noUpdate: noUpdate
\r
4820 if (!this._tempFireZoomEvent) {
\r
4821 this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
\r
4824 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
\r
4826 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
\r
4827 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
\r
4830 _onZoomTransitionEnd: function () {
\r
4831 if (!this._animatingZoom) { return; }
\r
4833 if (this._mapPane) {
\r
4834 removeClass(this._mapPane, 'leaflet-zoom-anim');
\r
4837 this._animatingZoom = false;
\r
4839 this._move(this._animateToCenter, this._animateToZoom, undefined, true);
\r
4841 if (this._tempFireZoomEvent) {
\r
4842 this.fire('zoom');
\r
4844 delete this._tempFireZoomEvent;
\r
4846 this.fire('move');
\r
4848 this._moveEnd(true);
\r
4854 // @factory L.map(id: String, options?: Map options)
\r
4855 // Instantiates a map object given the DOM ID of a `<div>` element
\r
4856 // and optionally an object literal with `Map options`.
\r
4859 // @factory L.map(el: HTMLElement, options?: Map options)
\r
4860 // Instantiates a map object given an instance of a `<div>` HTML element
\r
4861 // and optionally an object literal with `Map options`.
\r
4862 function createMap(id, options) {
\r
4863 return new Map(id, options);
\r
4871 * L.Control is a base class for implementing map controls. Handles positioning.
\r
4872 * All other controls extend from this class.
\r
4875 var Control = Class.extend({
\r
4877 // @aka Control Options
\r
4879 // @option position: String = 'topright'
\r
4880 // The position of the control (one of the map corners). Possible values are `'topleft'`,
\r
4881 // `'topright'`, `'bottomleft'` or `'bottomright'`
\r
4882 position: 'topright'
\r
4885 initialize: function (options) {
\r
4886 setOptions(this, options);
\r
4890 * Classes extending L.Control will inherit the following methods:
\r
4892 * @method getPosition: string
\r
4893 * Returns the position of the control.
\r
4895 getPosition: function () {
\r
4896 return this.options.position;
\r
4899 // @method setPosition(position: string): this
\r
4900 // Sets the position of the control.
\r
4901 setPosition: function (position) {
\r
4902 var map = this._map;
\r
4905 map.removeControl(this);
\r
4908 this.options.position = position;
\r
4911 map.addControl(this);
\r
4917 // @method getContainer: HTMLElement
\r
4918 // Returns the HTMLElement that contains the control.
\r
4919 getContainer: function () {
\r
4920 return this._container;
\r
4923 // @method addTo(map: Map): this
\r
4924 // Adds the control to the given map.
\r
4925 addTo: function (map) {
\r
4929 var container = this._container = this.onAdd(map),
\r
4930 pos = this.getPosition(),
\r
4931 corner = map._controlCorners[pos];
\r
4933 addClass(container, 'leaflet-control');
\r
4935 if (pos.indexOf('bottom') !== -1) {
\r
4936 corner.insertBefore(container, corner.firstChild);
\r
4938 corner.appendChild(container);
\r
4941 this._map.on('unload', this.remove, this);
\r
4946 // @method remove: this
\r
4947 // Removes the control from the map it is currently active on.
\r
4948 remove: function () {
\r
4953 remove(this._container);
\r
4955 if (this.onRemove) {
\r
4956 this.onRemove(this._map);
\r
4959 this._map.off('unload', this.remove, this);
\r
4965 _refocusOnMap: function (e) {
\r
4966 // if map exists and event is not a keyboard event
\r
4967 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
\r
4968 this._map.getContainer().focus();
\r
4973 var control = function (options) {
\r
4974 return new Control(options);
\r
4977 /* @section Extension methods
\r
4980 * Every control should extend from `L.Control` and (re-)implement the following methods.
\r
4982 * @method onAdd(map: Map): HTMLElement
\r
4983 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
\r
4985 * @method onRemove(map: Map)
\r
4986 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
\r
4990 * @section Methods for Layers and Controls
\r
4993 // @method addControl(control: Control): this
\r
4994 // Adds the given control to the map
\r
4995 addControl: function (control) {
\r
4996 control.addTo(this);
\r
5000 // @method removeControl(control: Control): this
\r
5001 // Removes the given control from the map
\r
5002 removeControl: function (control) {
\r
5007 _initControlPos: function () {
\r
5008 var corners = this._controlCorners = {},
\r
5010 container = this._controlContainer =
\r
5011 create$1('div', l + 'control-container', this._container);
\r
5013 function createCorner(vSide, hSide) {
\r
5014 var className = l + vSide + ' ' + l + hSide;
\r
5016 corners[vSide + hSide] = create$1('div', className, container);
\r
5019 createCorner('top', 'left');
\r
5020 createCorner('top', 'right');
\r
5021 createCorner('bottom', 'left');
\r
5022 createCorner('bottom', 'right');
\r
5025 _clearControlPos: function () {
\r
5026 for (var i in this._controlCorners) {
\r
5027 remove(this._controlCorners[i]);
\r
5029 remove(this._controlContainer);
\r
5030 delete this._controlCorners;
\r
5031 delete this._controlContainer;
\r
5036 * @class Control.Layers
\r
5037 * @aka L.Control.Layers
\r
5038 * @inherits Control
\r
5040 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`.
\r
5045 * var baseLayers = {
\r
5046 * "Mapbox": mapbox,
\r
5047 * "OpenStreetMap": osm
\r
5050 * var overlays = {
\r
5051 * "Marker": marker,
\r
5052 * "Roads": roadsLayer
\r
5055 * L.control.layers(baseLayers, overlays).addTo(map);
\r
5058 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
\r
5062 * "<someName1>": layer1,
\r
5063 * "<someName2>": layer2
\r
5067 * The layer names can contain HTML, which allows you to add additional styling to the items:
\r
5070 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
\r
5074 var Layers = Control.extend({
\r
5076 // @aka Control.Layers options
\r
5078 // @option collapsed: Boolean = true
\r
5079 // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.
\r
5081 position: 'topright',
\r
5083 // @option autoZIndex: Boolean = true
\r
5084 // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
\r
5087 // @option hideSingleBase: Boolean = false
\r
5088 // If `true`, the base layers in the control will be hidden when there is only one.
\r
5089 hideSingleBase: false,
\r
5091 // @option sortLayers: Boolean = false
\r
5092 // Whether to sort the layers. When `false`, layers will keep the order
\r
5093 // in which they were added to the control.
\r
5094 sortLayers: false,
\r
5096 // @option sortFunction: Function = *
\r
5097 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
\r
5098 // that will be used for sorting the layers, when `sortLayers` is `true`.
\r
5099 // The function receives both the `L.Layer` instances and their names, as in
\r
5100 // `sortFunction(layerA, layerB, nameA, nameB)`.
\r
5101 // By default, it sorts layers alphabetically by their name.
\r
5102 sortFunction: function (layerA, layerB, nameA, nameB) {
\r
5103 return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
\r
5107 initialize: function (baseLayers, overlays, options) {
\r
5108 setOptions(this, options);
\r
5110 this._layerControlInputs = [];
\r
5111 this._layers = [];
\r
5112 this._lastZIndex = 0;
\r
5113 this._handlingClick = false;
\r
5114 this._preventClick = false;
\r
5116 for (var i in baseLayers) {
\r
5117 this._addLayer(baseLayers[i], i);
\r
5120 for (i in overlays) {
\r
5121 this._addLayer(overlays[i], i, true);
\r
5125 onAdd: function (map) {
\r
5126 this._initLayout();
\r
5130 map.on('zoomend', this._checkDisabledLayers, this);
\r
5132 for (var i = 0; i < this._layers.length; i++) {
\r
5133 this._layers[i].layer.on('add remove', this._onLayerChange, this);
\r
5136 return this._container;
\r
5139 addTo: function (map) {
\r
5140 Control.prototype.addTo.call(this, map);
\r
5141 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
\r
5142 return this._expandIfNotCollapsed();
\r
5145 onRemove: function () {
\r
5146 this._map.off('zoomend', this._checkDisabledLayers, this);
\r
5148 for (var i = 0; i < this._layers.length; i++) {
\r
5149 this._layers[i].layer.off('add remove', this._onLayerChange, this);
\r
5153 // @method addBaseLayer(layer: Layer, name: String): this
\r
5154 // Adds a base layer (radio button entry) with the given name to the control.
\r
5155 addBaseLayer: function (layer, name) {
\r
5156 this._addLayer(layer, name);
\r
5157 return (this._map) ? this._update() : this;
\r
5160 // @method addOverlay(layer: Layer, name: String): this
\r
5161 // Adds an overlay (checkbox entry) with the given name to the control.
\r
5162 addOverlay: function (layer, name) {
\r
5163 this._addLayer(layer, name, true);
\r
5164 return (this._map) ? this._update() : this;
\r
5167 // @method removeLayer(layer: Layer): this
\r
5168 // Remove the given layer from the control.
\r
5169 removeLayer: function (layer) {
\r
5170 layer.off('add remove', this._onLayerChange, this);
\r
5172 var obj = this._getLayer(stamp(layer));
\r
5174 this._layers.splice(this._layers.indexOf(obj), 1);
\r
5176 return (this._map) ? this._update() : this;
\r
5179 // @method expand(): this
\r
5180 // Expand the control container if collapsed.
\r
5181 expand: function () {
\r
5182 addClass(this._container, 'leaflet-control-layers-expanded');
\r
5183 this._section.style.height = null;
\r
5184 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
\r
5185 if (acceptableHeight < this._section.clientHeight) {
\r
5186 addClass(this._section, 'leaflet-control-layers-scrollbar');
\r
5187 this._section.style.height = acceptableHeight + 'px';
\r
5189 removeClass(this._section, 'leaflet-control-layers-scrollbar');
\r
5191 this._checkDisabledLayers();
\r
5195 // @method collapse(): this
\r
5196 // Collapse the control container if expanded.
\r
5197 collapse: function () {
\r
5198 removeClass(this._container, 'leaflet-control-layers-expanded');
\r
5202 _initLayout: function () {
\r
5203 var className = 'leaflet-control-layers',
\r
5204 container = this._container = create$1('div', className),
\r
5205 collapsed = this.options.collapsed;
\r
5207 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
\r
5208 container.setAttribute('aria-haspopup', true);
\r
5210 disableClickPropagation(container);
\r
5211 disableScrollPropagation(container);
\r
5213 var section = this._section = create$1('section', className + '-list');
\r
5216 this._map.on('click', this.collapse, this);
\r
5219 mouseenter: this._expandSafely,
\r
5220 mouseleave: this.collapse
\r
5224 var link = this._layersLink = create$1('a', className + '-toggle', container);
\r
5226 link.title = 'Layers';
\r
5227 link.setAttribute('role', 'button');
\r
5230 keydown: function (e) {
\r
5231 if (e.keyCode === 13) {
\r
5232 this._expandSafely();
\r
5235 // Certain screen readers intercept the key event and instead send a click event
\r
5236 click: function (e) {
\r
5237 preventDefault(e);
\r
5238 this._expandSafely();
\r
5246 this._baseLayersList = create$1('div', className + '-base', section);
\r
5247 this._separator = create$1('div', className + '-separator', section);
\r
5248 this._overlaysList = create$1('div', className + '-overlays', section);
\r
5250 container.appendChild(section);
\r
5253 _getLayer: function (id) {
\r
5254 for (var i = 0; i < this._layers.length; i++) {
\r
5256 if (this._layers[i] && stamp(this._layers[i].layer) === id) {
\r
5257 return this._layers[i];
\r
5262 _addLayer: function (layer, name, overlay) {
\r
5264 layer.on('add remove', this._onLayerChange, this);
\r
5267 this._layers.push({
\r
5273 if (this.options.sortLayers) {
\r
5274 this._layers.sort(bind(function (a, b) {
\r
5275 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
\r
5279 if (this.options.autoZIndex && layer.setZIndex) {
\r
5280 this._lastZIndex++;
\r
5281 layer.setZIndex(this._lastZIndex);
\r
5284 this._expandIfNotCollapsed();
\r
5287 _update: function () {
\r
5288 if (!this._container) { return this; }
\r
5290 empty(this._baseLayersList);
\r
5291 empty(this._overlaysList);
\r
5293 this._layerControlInputs = [];
\r
5294 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
\r
5296 for (i = 0; i < this._layers.length; i++) {
\r
5297 obj = this._layers[i];
\r
5298 this._addItem(obj);
\r
5299 overlaysPresent = overlaysPresent || obj.overlay;
\r
5300 baseLayersPresent = baseLayersPresent || !obj.overlay;
\r
5301 baseLayersCount += !obj.overlay ? 1 : 0;
\r
5304 // Hide base layers section if there's only one layer.
\r
5305 if (this.options.hideSingleBase) {
\r
5306 baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
\r
5307 this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
\r
5310 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
\r
5315 _onLayerChange: function (e) {
\r
5316 if (!this._handlingClick) {
\r
5320 var obj = this._getLayer(stamp(e.target));
\r
5323 // @section Layer events
\r
5324 // @event baselayerchange: LayersControlEvent
\r
5325 // Fired when the base layer is changed through the [layers control](#control-layers).
\r
5326 // @event overlayadd: LayersControlEvent
\r
5327 // Fired when an overlay is selected through the [layers control](#control-layers).
\r
5328 // @event overlayremove: LayersControlEvent
\r
5329 // Fired when an overlay is deselected through the [layers control](#control-layers).
\r
5330 // @namespace Control.Layers
\r
5331 var type = obj.overlay ?
\r
5332 (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
\r
5333 (e.type === 'add' ? 'baselayerchange' : null);
\r
5336 this._map.fire(type, obj);
\r
5340 // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)
\r
5341 _createRadioElement: function (name, checked) {
\r
5343 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
\r
5344 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
\r
5346 var radioFragment = document.createElement('div');
\r
5347 radioFragment.innerHTML = radioHtml;
\r
5349 return radioFragment.firstChild;
\r
5352 _addItem: function (obj) {
\r
5353 var label = document.createElement('label'),
\r
5354 checked = this._map.hasLayer(obj.layer),
\r
5357 if (obj.overlay) {
\r
5358 input = document.createElement('input');
\r
5359 input.type = 'checkbox';
\r
5360 input.className = 'leaflet-control-layers-selector';
\r
5361 input.defaultChecked = checked;
\r
5363 input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
\r
5366 this._layerControlInputs.push(input);
\r
5367 input.layerId = stamp(obj.layer);
\r
5369 on(input, 'click', this._onInputClick, this);
\r
5371 var name = document.createElement('span');
\r
5372 name.innerHTML = ' ' + obj.name;
\r
5374 // Helps from preventing layer control flicker when checkboxes are disabled
\r
5375 // https://github.com/Leaflet/Leaflet/issues/2771
\r
5376 var holder = document.createElement('span');
\r
5378 label.appendChild(holder);
\r
5379 holder.appendChild(input);
\r
5380 holder.appendChild(name);
\r
5382 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
\r
5383 container.appendChild(label);
\r
5385 this._checkDisabledLayers();
\r
5389 _onInputClick: function () {
\r
5390 // expanding the control on mobile with a click can cause adding a layer - we don't want this
\r
5391 if (this._preventClick) {
\r
5395 var inputs = this._layerControlInputs,
\r
5397 var addedLayers = [],
\r
5398 removedLayers = [];
\r
5400 this._handlingClick = true;
\r
5402 for (var i = inputs.length - 1; i >= 0; i--) {
\r
5403 input = inputs[i];
\r
5404 layer = this._getLayer(input.layerId).layer;
\r
5406 if (input.checked) {
\r
5407 addedLayers.push(layer);
\r
5408 } else if (!input.checked) {
\r
5409 removedLayers.push(layer);
\r
5413 // Bugfix issue 2318: Should remove all old layers before readding new ones
\r
5414 for (i = 0; i < removedLayers.length; i++) {
\r
5415 if (this._map.hasLayer(removedLayers[i])) {
\r
5416 this._map.removeLayer(removedLayers[i]);
\r
5419 for (i = 0; i < addedLayers.length; i++) {
\r
5420 if (!this._map.hasLayer(addedLayers[i])) {
\r
5421 this._map.addLayer(addedLayers[i]);
\r
5425 this._handlingClick = false;
\r
5427 this._refocusOnMap();
\r
5430 _checkDisabledLayers: function () {
\r
5431 var inputs = this._layerControlInputs,
\r
5434 zoom = this._map.getZoom();
\r
5436 for (var i = inputs.length - 1; i >= 0; i--) {
\r
5437 input = inputs[i];
\r
5438 layer = this._getLayer(input.layerId).layer;
\r
5439 input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
\r
5440 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
\r
5445 _expandIfNotCollapsed: function () {
\r
5446 if (this._map && !this.options.collapsed) {
\r
5452 _expandSafely: function () {
\r
5453 var section = this._section;
\r
5454 this._preventClick = true;
\r
5455 on(section, 'click', preventDefault);
\r
5458 setTimeout(function () {
\r
5459 off(section, 'click', preventDefault);
\r
5460 that._preventClick = false;
\r
5467 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
\r
5468 // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
\r
5469 var layers = function (baseLayers, overlays, options) {
\r
5470 return new Layers(baseLayers, overlays, options);
\r
5474 * @class Control.Zoom
\r
5475 * @aka L.Control.Zoom
\r
5476 * @inherits Control
\r
5478 * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
\r
5481 var Zoom = Control.extend({
\r
5483 // @aka Control.Zoom options
\r
5485 position: 'topleft',
\r
5487 // @option zoomInText: String = '<span aria-hidden="true">+</span>'
\r
5488 // The text set on the 'zoom in' button.
\r
5489 zoomInText: '<span aria-hidden="true">+</span>',
\r
5491 // @option zoomInTitle: String = 'Zoom in'
\r
5492 // The title set on the 'zoom in' button.
\r
5493 zoomInTitle: 'Zoom in',
\r
5495 // @option zoomOutText: String = '<span aria-hidden="true">−</span>'
\r
5496 // The text set on the 'zoom out' button.
\r
5497 zoomOutText: '<span aria-hidden="true">−</span>',
\r
5499 // @option zoomOutTitle: String = 'Zoom out'
\r
5500 // The title set on the 'zoom out' button.
\r
5501 zoomOutTitle: 'Zoom out'
\r
5504 onAdd: function (map) {
\r
5505 var zoomName = 'leaflet-control-zoom',
\r
5506 container = create$1('div', zoomName + ' leaflet-bar'),
\r
5507 options = this.options;
\r
5509 this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
\r
5510 zoomName + '-in', container, this._zoomIn);
\r
5511 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
\r
5512 zoomName + '-out', container, this._zoomOut);
\r
5514 this._updateDisabled();
\r
5515 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
\r
5520 onRemove: function (map) {
\r
5521 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
\r
5524 disable: function () {
\r
5525 this._disabled = true;
\r
5526 this._updateDisabled();
\r
5530 enable: function () {
\r
5531 this._disabled = false;
\r
5532 this._updateDisabled();
\r
5536 _zoomIn: function (e) {
\r
5537 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
\r
5538 this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
\r
5542 _zoomOut: function (e) {
\r
5543 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
\r
5544 this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
\r
5548 _createButton: function (html, title, className, container, fn) {
\r
5549 var link = create$1('a', className, container);
\r
5550 link.innerHTML = html;
\r
5552 link.title = title;
\r
5555 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
\r
5557 link.setAttribute('role', 'button');
\r
5558 link.setAttribute('aria-label', title);
\r
5560 disableClickPropagation(link);
\r
5561 on(link, 'click', stop);
\r
5562 on(link, 'click', fn, this);
\r
5563 on(link, 'click', this._refocusOnMap, this);
\r
5568 _updateDisabled: function () {
\r
5569 var map = this._map,
\r
5570 className = 'leaflet-disabled';
\r
5572 removeClass(this._zoomInButton, className);
\r
5573 removeClass(this._zoomOutButton, className);
\r
5574 this._zoomInButton.setAttribute('aria-disabled', 'false');
\r
5575 this._zoomOutButton.setAttribute('aria-disabled', 'false');
\r
5577 if (this._disabled || map._zoom === map.getMinZoom()) {
\r
5578 addClass(this._zoomOutButton, className);
\r
5579 this._zoomOutButton.setAttribute('aria-disabled', 'true');
\r
5581 if (this._disabled || map._zoom === map.getMaxZoom()) {
\r
5582 addClass(this._zoomInButton, className);
\r
5583 this._zoomInButton.setAttribute('aria-disabled', 'true');
\r
5589 // @section Control options
\r
5590 // @option zoomControl: Boolean = true
\r
5591 // Whether a [zoom control](#control-zoom) is added to the map by default.
\r
5592 Map.mergeOptions({
\r
5596 Map.addInitHook(function () {
\r
5597 if (this.options.zoomControl) {
\r
5598 // @section Controls
\r
5599 // @property zoomControl: Control.Zoom
\r
5600 // The default zoom control (only available if the
\r
5601 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
\r
5602 this.zoomControl = new Zoom();
\r
5603 this.addControl(this.zoomControl);
\r
5607 // @namespace Control.Zoom
\r
5608 // @factory L.control.zoom(options: Control.Zoom options)
\r
5609 // Creates a zoom control
\r
5610 var zoom = function (options) {
\r
5611 return new Zoom(options);
\r
5615 * @class Control.Scale
5616 * @aka L.Control.Scale
5619 * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
5624 * L.control.scale().addTo(map);
5628 var Scale = Control.extend({
5630 // @aka Control.Scale options
5632 position: 'bottomleft',
5634 // @option maxWidth: Number = 100
5635 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5638 // @option metric: Boolean = True
5639 // Whether to show the metric scale line (m/km).
5642 // @option imperial: Boolean = True
5643 // Whether to show the imperial scale line (mi/ft).
5646 // @option updateWhenIdle: Boolean = false
5647 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5650 onAdd: function (map) {
5651 var className = 'leaflet-control-scale',
5652 container = create$1('div', className),
5653 options = this.options;
5655 this._addScales(options, className + '-line', container);
5657 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5658 map.whenReady(this._update, this);
5663 onRemove: function (map) {
5664 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5667 _addScales: function (options, className, container) {
5668 if (options.metric) {
5669 this._mScale = create$1('div', className, container);
5671 if (options.imperial) {
5672 this._iScale = create$1('div', className, container);
5676 _update: function () {
5677 var map = this._map,
5678 y = map.getSize().y / 2;
5680 var maxMeters = map.distance(
5681 map.containerPointToLatLng([0, y]),
5682 map.containerPointToLatLng([this.options.maxWidth, y]));
5684 this._updateScales(maxMeters);
5687 _updateScales: function (maxMeters) {
5688 if (this.options.metric && maxMeters) {
5689 this._updateMetric(maxMeters);
5691 if (this.options.imperial && maxMeters) {
5692 this._updateImperial(maxMeters);
5696 _updateMetric: function (maxMeters) {
5697 var meters = this._getRoundNum(maxMeters),
5698 label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5700 this._updateScale(this._mScale, label, meters / maxMeters);
5703 _updateImperial: function (maxMeters) {
5704 var maxFeet = maxMeters * 3.2808399,
5705 maxMiles, miles, feet;
5707 if (maxFeet > 5280) {
5708 maxMiles = maxFeet / 5280;
5709 miles = this._getRoundNum(maxMiles);
5710 this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5713 feet = this._getRoundNum(maxFeet);
5714 this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5718 _updateScale: function (scale, text, ratio) {
5719 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5720 scale.innerHTML = text;
5723 _getRoundNum: function (num) {
5724 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5737 // @factory L.control.scale(options?: Control.Scale options)
5738 // Creates an scale control with the given options.
5739 var scale = function (options) {
5740 return new Scale(options);
5743 var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>';
\r
5747 * @class Control.Attribution
\r
5748 * @aka L.Control.Attribution
\r
5749 * @inherits Control
\r
5751 * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
\r
5754 var Attribution = Control.extend({
\r
5756 // @aka Control.Attribution options
\r
5758 position: 'bottomright',
\r
5760 // @option prefix: String|false = 'Leaflet'
\r
5761 // The HTML text shown before the attributions. Pass `false` to disable.
\r
5762 prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
\r
5765 initialize: function (options) {
\r
5766 setOptions(this, options);
\r
5768 this._attributions = {};
\r
5771 onAdd: function (map) {
\r
5772 map.attributionControl = this;
\r
5773 this._container = create$1('div', 'leaflet-control-attribution');
\r
5774 disableClickPropagation(this._container);
\r
5776 // TODO ugly, refactor
\r
5777 for (var i in map._layers) {
\r
5778 if (map._layers[i].getAttribution) {
\r
5779 this.addAttribution(map._layers[i].getAttribution());
\r
5785 map.on('layeradd', this._addAttribution, this);
\r
5787 return this._container;
\r
5790 onRemove: function (map) {
\r
5791 map.off('layeradd', this._addAttribution, this);
\r
5794 _addAttribution: function (ev) {
\r
5795 if (ev.layer.getAttribution) {
\r
5796 this.addAttribution(ev.layer.getAttribution());
\r
5797 ev.layer.once('remove', function () {
\r
5798 this.removeAttribution(ev.layer.getAttribution());
\r
5803 // @method setPrefix(prefix: String|false): this
\r
5804 // The HTML text shown before the attributions. Pass `false` to disable.
\r
5805 setPrefix: function (prefix) {
\r
5806 this.options.prefix = prefix;
\r
5811 // @method addAttribution(text: String): this
\r
5812 // Adds an attribution text (e.g. `'© OpenStreetMap contributors'`).
\r
5813 addAttribution: function (text) {
\r
5814 if (!text) { return this; }
\r
5816 if (!this._attributions[text]) {
\r
5817 this._attributions[text] = 0;
\r
5819 this._attributions[text]++;
\r
5826 // @method removeAttribution(text: String): this
\r
5827 // Removes an attribution text.
\r
5828 removeAttribution: function (text) {
\r
5829 if (!text) { return this; }
\r
5831 if (this._attributions[text]) {
\r
5832 this._attributions[text]--;
\r
5839 _update: function () {
\r
5840 if (!this._map) { return; }
\r
5844 for (var i in this._attributions) {
\r
5845 if (this._attributions[i]) {
\r
5850 var prefixAndAttribs = [];
\r
5852 if (this.options.prefix) {
\r
5853 prefixAndAttribs.push(this.options.prefix);
\r
5855 if (attribs.length) {
\r
5856 prefixAndAttribs.push(attribs.join(', '));
\r
5859 this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
\r
5864 // @section Control options
\r
5865 // @option attributionControl: Boolean = true
\r
5866 // Whether a [attribution control](#control-attribution) is added to the map by default.
\r
5867 Map.mergeOptions({
\r
5868 attributionControl: true
\r
5871 Map.addInitHook(function () {
\r
5872 if (this.options.attributionControl) {
\r
5873 new Attribution().addTo(this);
\r
5877 // @namespace Control.Attribution
\r
5878 // @factory L.control.attribution(options: Control.Attribution options)
\r
5879 // Creates an attribution control.
\r
5880 var attribution = function (options) {
\r
5881 return new Attribution(options);
\r
5884 Control.Layers = Layers;
5885 Control.Zoom = Zoom;
5886 Control.Scale = Scale;
5887 Control.Attribution = Attribution;
5889 control.layers = layers;
5890 control.zoom = zoom;
5891 control.scale = scale;
5892 control.attribution = attribution;
5895 L.Handler is a base class for handler classes that are used internally to inject
5896 interaction features like dragging to classes like Map and Marker.
5901 // Abstract class for map interaction handlers
5903 var Handler = Class.extend({
5904 initialize: function (map) {
5908 // @method enable(): this
5909 // Enables the handler
5910 enable: function () {
5911 if (this._enabled) { return this; }
5913 this._enabled = true;
5918 // @method disable(): this
5919 // Disables the handler
5920 disable: function () {
5921 if (!this._enabled) { return this; }
5923 this._enabled = false;
5928 // @method enabled(): Boolean
5929 // Returns `true` if the handler is enabled
5930 enabled: function () {
5931 return !!this._enabled;
5934 // @section Extension methods
5935 // Classes inheriting from `Handler` must implement the two following methods:
5936 // @method addHooks()
5937 // Called when the handler is enabled, should add event hooks.
5938 // @method removeHooks()
5939 // Called when the handler is disabled, should remove the event hooks added previously.
5942 // @section There is static function which can be called without instantiating L.Handler:
5943 // @function addTo(map: Map, name: String): this
5944 // Adds a new Handler to the given map with the given name.
5945 Handler.addTo = function (map, name) {
5946 map.addHandler(name, this);
5950 var Mixin = {Events: Events};
5953 * @class Draggable
\r
5954 * @aka L.Draggable
\r
5955 * @inherits Evented
\r
5957 * A class for making DOM elements draggable (including touch support).
\r
5958 * Used internally for map and marker dragging. Only works for elements
\r
5959 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
\r
5963 * var draggable = new L.Draggable(elementToDrag);
\r
5964 * draggable.enable();
\r
5968 var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';
\r
5970 var Draggable = Evented.extend({
\r
5974 // @aka Draggable options
\r
5975 // @option clickTolerance: Number = 3
\r
5976 // The max number of pixels a user can shift the mouse pointer during a click
\r
5977 // for it to be considered a valid click (as opposed to a mouse drag).
\r
5981 // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
\r
5982 // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
\r
5983 initialize: function (element, dragStartTarget, preventOutline, options) {
\r
5984 setOptions(this, options);
\r
5986 this._element = element;
\r
5987 this._dragStartTarget = dragStartTarget || element;
\r
5988 this._preventOutline = preventOutline;
\r
5991 // @method enable()
\r
5992 // Enables the dragging ability
\r
5993 enable: function () {
\r
5994 if (this._enabled) { return; }
\r
5996 on(this._dragStartTarget, START, this._onDown, this);
\r
5998 this._enabled = true;
\r
6001 // @method disable()
\r
6002 // Disables the dragging ability
\r
6003 disable: function () {
\r
6004 if (!this._enabled) { return; }
\r
6006 // If we're currently dragging this draggable,
\r
6007 // disabling it counts as first ending the drag.
\r
6008 if (Draggable._dragging === this) {
\r
6009 this.finishDrag(true);
\r
6012 off(this._dragStartTarget, START, this._onDown, this);
\r
6014 this._enabled = false;
\r
6015 this._moved = false;
\r
6018 _onDown: function (e) {
\r
6019 // Ignore the event if disabled; this happens in IE11
\r
6020 // under some circumstances, see #3666.
\r
6021 if (!this._enabled) { return; }
\r
6023 this._moved = false;
\r
6025 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
\r
6027 if (e.touches && e.touches.length !== 1) {
\r
6028 // Finish dragging to avoid conflict with touchZoom
\r
6029 if (Draggable._dragging === this) {
\r
6030 this.finishDrag();
\r
6035 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
\r
6036 Draggable._dragging = this; // Prevent dragging multiple objects at once.
\r
6038 if (this._preventOutline) {
\r
6039 preventOutline(this._element);
\r
6042 disableImageDrag();
\r
6043 disableTextSelection();
\r
6045 if (this._moving) { return; }
\r
6047 // @event down: Event
\r
6048 // Fired when a drag is about to start.
\r
6049 this.fire('down');
\r
6051 var first = e.touches ? e.touches[0] : e,
\r
6052 sizedParent = getSizedParentNode(this._element);
\r
6054 this._startPoint = new Point(first.clientX, first.clientY);
\r
6055 this._startPos = getPosition(this._element);
\r
6057 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
\r
6058 this._parentScale = getScale(sizedParent);
\r
6060 var mouseevent = e.type === 'mousedown';
\r
6061 on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
\r
6062 on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
\r
6065 _onMove: function (e) {
\r
6066 // Ignore the event if disabled; this happens in IE11
\r
6067 // under some circumstances, see #3666.
\r
6068 if (!this._enabled) { return; }
\r
6070 if (e.touches && e.touches.length > 1) {
\r
6071 this._moved = true;
\r
6075 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
\r
6076 offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
\r
6078 if (!offset.x && !offset.y) { return; }
\r
6079 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
\r
6081 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
\r
6082 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
\r
6083 // and we can use the cached value for the scale.
\r
6084 offset.x /= this._parentScale.x;
\r
6085 offset.y /= this._parentScale.y;
\r
6087 preventDefault(e);
\r
6089 if (!this._moved) {
\r
6090 // @event dragstart: Event
\r
6091 // Fired when a drag starts
\r
6092 this.fire('dragstart');
\r
6094 this._moved = true;
\r
6096 addClass(document.body, 'leaflet-dragging');
\r
6098 this._lastTarget = e.target || e.srcElement;
\r
6099 // IE and Edge do not give the <use> element, so fetch it
\r
6101 if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
\r
6102 this._lastTarget = this._lastTarget.correspondingUseElement;
\r
6104 addClass(this._lastTarget, 'leaflet-drag-target');
\r
6107 this._newPos = this._startPos.add(offset);
\r
6108 this._moving = true;
\r
6110 this._lastEvent = e;
\r
6111 this._updatePosition();
\r
6114 _updatePosition: function () {
\r
6115 var e = {originalEvent: this._lastEvent};
\r
6117 // @event predrag: Event
\r
6118 // Fired continuously during dragging *before* each corresponding
\r
6119 // update of the element's position.
\r
6120 this.fire('predrag', e);
\r
6121 setPosition(this._element, this._newPos);
\r
6123 // @event drag: Event
\r
6124 // Fired continuously during dragging.
\r
6125 this.fire('drag', e);
\r
6128 _onUp: function () {
\r
6129 // Ignore the event if disabled; this happens in IE11
\r
6130 // under some circumstances, see #3666.
\r
6131 if (!this._enabled) { return; }
\r
6132 this.finishDrag();
\r
6135 finishDrag: function (noInertia) {
\r
6136 removeClass(document.body, 'leaflet-dragging');
\r
6138 if (this._lastTarget) {
\r
6139 removeClass(this._lastTarget, 'leaflet-drag-target');
\r
6140 this._lastTarget = null;
\r
6143 off(document, 'mousemove touchmove', this._onMove, this);
\r
6144 off(document, 'mouseup touchend touchcancel', this._onUp, this);
\r
6146 enableImageDrag();
\r
6147 enableTextSelection();
\r
6149 var fireDragend = this._moved && this._moving;
\r
6151 this._moving = false;
\r
6152 Draggable._dragging = false;
\r
6154 if (fireDragend) {
\r
6155 // @event dragend: DragEndEvent
\r
6156 // Fired when the drag ends.
\r
6157 this.fire('dragend', {
\r
6158 noInertia: noInertia,
\r
6159 distance: this._newPos.distanceTo(this._startPos)
\r
6167 * @namespace PolyUtil
\r
6168 * Various utility functions for polygon geometries.
\r
6171 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
\r
6172 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
\r
6173 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
\r
6174 * performance. Note that polygon points needs different algorithm for clipping
\r
6175 * than polyline, so there's a separate method for it.
\r
6177 function clipPolygon(points, bounds, round) {
\r
6178 var clippedPoints,
\r
6179 edges = [1, 4, 2, 8],
\r
6184 for (i = 0, len = points.length; i < len; i++) {
\r
6185 points[i]._code = _getBitCode(points[i], bounds);
\r
6188 // for each edge (left, bottom, right, top)
\r
6189 for (k = 0; k < 4; k++) {
\r
6191 clippedPoints = [];
\r
6193 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
\r
6197 // if a is inside the clip window
\r
6198 if (!(a._code & edge)) {
\r
6199 // if b is outside the clip window (a->b goes out of screen)
\r
6200 if (b._code & edge) {
\r
6201 p = _getEdgeIntersection(b, a, edge, bounds, round);
\r
6202 p._code = _getBitCode(p, bounds);
\r
6203 clippedPoints.push(p);
\r
6205 clippedPoints.push(a);
\r
6207 // else if b is inside the clip window (a->b enters the screen)
\r
6208 } else if (!(b._code & edge)) {
\r
6209 p = _getEdgeIntersection(b, a, edge, bounds, round);
\r
6210 p._code = _getBitCode(p, bounds);
\r
6211 clippedPoints.push(p);
\r
6214 points = clippedPoints;
\r
6220 /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng
\r
6221 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.
\r
6223 function polygonCenter(latlngs, crs) {
\r
6224 var i, j, p1, p2, f, area, x, y, center;
\r
6226 if (!latlngs || latlngs.length === 0) {
\r
6227 throw new Error('latlngs not passed');
\r
6230 if (!isFlat(latlngs)) {
\r
6231 console.warn('latlngs are not flat! Only the first ring will be used');
\r
6232 latlngs = latlngs[0];
\r
6235 var centroidLatLng = toLatLng([0, 0]);
\r
6237 var bounds = toLatLngBounds(latlngs);
\r
6238 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
\r
6239 // tests showed that below 1700 rounding errors are happening
\r
6240 if (areaBounds < 1700) {
\r
6241 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
\r
6242 centroidLatLng = centroid(latlngs);
\r
6245 var len = latlngs.length;
\r
6247 for (i = 0; i < len; i++) {
\r
6248 var latlng = toLatLng(latlngs[i]);
\r
6249 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
\r
6254 // polygon centroid algorithm;
\r
6255 for (i = 0, j = len - 1; i < len; j = i++) {
\r
6259 f = p1.y * p2.x - p2.y * p1.x;
\r
6260 x += (p1.x + p2.x) * f;
\r
6261 y += (p1.y + p2.y) * f;
\r
6266 // Polygon is so small that all points are on same pixel.
\r
6267 center = points[0];
\r
6269 center = [x / area, y / area];
\r
6272 var latlngCenter = crs.unproject(toPoint(center));
\r
6273 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
\r
6276 /* @function centroid(latlngs: LatLng[]): LatLng
\r
6277 * Returns the 'center of mass' of the passed LatLngs.
\r
6279 function centroid(coords) {
\r
6283 for (var i = 0; i < coords.length; i++) {
\r
6284 var latlng = toLatLng(coords[i]);
\r
6285 latSum += latlng.lat;
\r
6286 lngSum += latlng.lng;
\r
6289 return toLatLng([latSum / len, lngSum / len]);
\r
6294 clipPolygon: clipPolygon,
6295 polygonCenter: polygonCenter,
6300 * @namespace LineUtil
\r
6302 * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
\r
6305 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
\r
6306 // Improves rendering performance dramatically by lessening the number of points to draw.
\r
6308 // @function simplify(points: Point[], tolerance: Number): Point[]
\r
6309 // Dramatically reduces the number of points in a polyline while retaining
\r
6310 // its shape and returns a new array of simplified points, using the
\r
6311 // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
\r
6312 // Used for a huge performance boost when processing/displaying Leaflet polylines for
\r
6313 // each zoom level and also reducing visual noise. tolerance affects the amount of
\r
6314 // simplification (lesser value means higher quality but slower and with more points).
\r
6315 // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
\r
6316 function simplify(points, tolerance) {
\r
6317 if (!tolerance || !points.length) {
\r
6318 return points.slice();
\r
6321 var sqTolerance = tolerance * tolerance;
\r
6323 // stage 1: vertex reduction
\r
6324 points = _reducePoints(points, sqTolerance);
\r
6326 // stage 2: Douglas-Peucker simplification
\r
6327 points = _simplifyDP(points, sqTolerance);
\r
6332 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
\r
6333 // Returns the distance between point `p` and segment `p1` to `p2`.
\r
6334 function pointToSegmentDistance(p, p1, p2) {
\r
6335 return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
\r
6338 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
\r
6339 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
\r
6340 function closestPointOnSegment(p, p1, p2) {
\r
6341 return _sqClosestPointOnSegment(p, p1, p2);
\r
6344 // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
\r
6345 function _simplifyDP(points, sqTolerance) {
\r
6347 var len = points.length,
\r
6348 ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
\r
6349 markers = new ArrayConstructor(len);
\r
6351 markers[0] = markers[len - 1] = 1;
\r
6353 _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
\r
6358 for (i = 0; i < len; i++) {
\r
6360 newPoints.push(points[i]);
\r
6367 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
\r
6369 var maxSqDist = 0,
\r
6372 for (i = first + 1; i <= last - 1; i++) {
\r
6373 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
\r
6375 if (sqDist > maxSqDist) {
\r
6377 maxSqDist = sqDist;
\r
6381 if (maxSqDist > sqTolerance) {
\r
6382 markers[index] = 1;
\r
6384 _simplifyDPStep(points, markers, sqTolerance, first, index);
\r
6385 _simplifyDPStep(points, markers, sqTolerance, index, last);
\r
6389 // reduce points that are too close to each other to a single point
\r
6390 function _reducePoints(points, sqTolerance) {
\r
6391 var reducedPoints = [points[0]];
\r
6393 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
\r
6394 if (_sqDist(points[i], points[prev]) > sqTolerance) {
\r
6395 reducedPoints.push(points[i]);
\r
6399 if (prev < len - 1) {
\r
6400 reducedPoints.push(points[len - 1]);
\r
6402 return reducedPoints;
\r
6407 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
\r
6408 // Clips the segment a to b by rectangular bounds with the
\r
6409 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
\r
6410 // (modifying the segment points directly!). Used by Leaflet to only show polyline
\r
6411 // points that are on the screen or near, increasing performance.
\r
6412 function clipSegment(a, b, bounds, useLastCode, round) {
\r
6413 var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
\r
6414 codeB = _getBitCode(b, bounds),
\r
6416 codeOut, p, newCode;
\r
6418 // save 2nd code to avoid calculating it on the next segment
\r
6419 _lastCode = codeB;
\r
6422 // if a,b is inside the clip window (trivial accept)
\r
6423 if (!(codeA | codeB)) {
\r
6427 // if a,b is outside the clip window (trivial reject)
\r
6428 if (codeA & codeB) {
\r
6433 codeOut = codeA || codeB;
\r
6434 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
\r
6435 newCode = _getBitCode(p, bounds);
\r
6437 if (codeOut === codeA) {
\r
6447 function _getEdgeIntersection(a, b, code, bounds, round) {
\r
6448 var dx = b.x - a.x,
\r
6454 if (code & 8) { // top
\r
6455 x = a.x + dx * (max.y - a.y) / dy;
\r
6458 } else if (code & 4) { // bottom
\r
6459 x = a.x + dx * (min.y - a.y) / dy;
\r
6462 } else if (code & 2) { // right
\r
6464 y = a.y + dy * (max.x - a.x) / dx;
\r
6466 } else if (code & 1) { // left
\r
6468 y = a.y + dy * (min.x - a.x) / dx;
\r
6471 return new Point(x, y, round);
\r
6474 function _getBitCode(p, bounds) {
\r
6477 if (p.x < bounds.min.x) { // left
\r
6479 } else if (p.x > bounds.max.x) { // right
\r
6483 if (p.y < bounds.min.y) { // bottom
\r
6485 } else if (p.y > bounds.max.y) { // top
\r
6492 // square distance (to avoid unnecessary Math.sqrt calls)
\r
6493 function _sqDist(p1, p2) {
\r
6494 var dx = p2.x - p1.x,
\r
6496 return dx * dx + dy * dy;
\r
6499 // return closest point on segment or distance to that point
\r
6500 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
\r
6505 dot = dx * dx + dy * dy,
\r
6509 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
\r
6514 } else if (t > 0) {
\r
6523 return sqDist ? dx * dx + dy * dy : new Point(x, y);
\r
6527 // @function isFlat(latlngs: LatLng[]): Boolean
\r
6528 // Returns true if `latlngs` is a flat array, false is nested.
\r
6529 function isFlat(latlngs) {
\r
6530 return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
\r
6533 function _flat(latlngs) {
\r
6534 console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
\r
6535 return isFlat(latlngs);
\r
6538 /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng
\r
6539 * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.
\r
6541 function polylineCenter(latlngs, crs) {
\r
6542 var i, halfDist, segDist, dist, p1, p2, ratio, center;
\r
6544 if (!latlngs || latlngs.length === 0) {
\r
6545 throw new Error('latlngs not passed');
\r
6548 if (!isFlat(latlngs)) {
\r
6549 console.warn('latlngs are not flat! Only the first ring will be used');
\r
6550 latlngs = latlngs[0];
\r
6553 var centroidLatLng = toLatLng([0, 0]);
\r
6555 var bounds = toLatLngBounds(latlngs);
\r
6556 var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
\r
6557 // tests showed that below 1700 rounding errors are happening
\r
6558 if (areaBounds < 1700) {
\r
6559 // getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
\r
6560 centroidLatLng = centroid(latlngs);
\r
6563 var len = latlngs.length;
\r
6565 for (i = 0; i < len; i++) {
\r
6566 var latlng = toLatLng(latlngs[i]);
\r
6567 points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
\r
6570 for (i = 0, halfDist = 0; i < len - 1; i++) {
\r
6571 halfDist += points[i].distanceTo(points[i + 1]) / 2;
\r
6574 // The line is so small in the current view that all points are on the same pixel.
\r
6575 if (halfDist === 0) {
\r
6576 center = points[0];
\r
6578 for (i = 0, dist = 0; i < len - 1; i++) {
\r
6580 p2 = points[i + 1];
\r
6581 segDist = p1.distanceTo(p2);
\r
6584 if (dist > halfDist) {
\r
6585 ratio = (dist - halfDist) / segDist;
\r
6587 p2.x - ratio * (p2.x - p1.x),
\r
6588 p2.y - ratio * (p2.y - p1.y)
\r
6595 var latlngCenter = crs.unproject(toPoint(center));
\r
6596 return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
\r
6602 pointToSegmentDistance: pointToSegmentDistance,
6603 closestPointOnSegment: closestPointOnSegment,
6604 clipSegment: clipSegment,
6605 _getEdgeIntersection: _getEdgeIntersection,
6606 _getBitCode: _getBitCode,
6607 _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6610 polylineCenter: polylineCenter
6614 * @namespace Projection
\r
6616 * Leaflet comes with a set of already defined Projections out of the box:
\r
6618 * @projection L.Projection.LonLat
\r
6620 * Equirectangular, or Plate Carree projection — the most simple projection,
\r
6621 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
\r
6622 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
\r
6623 * `EPSG:4326` and `Simple` CRS.
\r
6627 project: function (latlng) {
\r
6628 return new Point(latlng.lng, latlng.lat);
\r
6631 unproject: function (point) {
\r
6632 return new LatLng(point.y, point.x);
\r
6635 bounds: new Bounds([-180, -90], [180, 90])
\r
6639 * @namespace Projection
\r
6640 * @projection L.Projection.Mercator
\r
6642 * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
\r
6647 R_MINOR: 6356752.314245179,
\r
6649 bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
\r
6651 project: function (latlng) {
\r
6652 var d = Math.PI / 180,
\r
6654 y = latlng.lat * d,
\r
6655 tmp = this.R_MINOR / r,
\r
6656 e = Math.sqrt(1 - tmp * tmp),
\r
6657 con = e * Math.sin(y);
\r
6659 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
\r
6660 y = -r * Math.log(Math.max(ts, 1E-10));
\r
6662 return new Point(latlng.lng * d * r, y);
\r
6665 unproject: function (point) {
\r
6666 var d = 180 / Math.PI,
\r
6668 tmp = this.R_MINOR / r,
\r
6669 e = Math.sqrt(1 - tmp * tmp),
\r
6670 ts = Math.exp(-point.y / r),
\r
6671 phi = Math.PI / 2 - 2 * Math.atan(ts);
\r
6673 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
\r
6674 con = e * Math.sin(phi);
\r
6675 con = Math.pow((1 - con) / (1 + con), e / 2);
\r
6676 dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
\r
6680 return new LatLng(phi * d, point.x * d / r);
\r
6687 * An object with methods for projecting geographical coordinates of the world onto
6688 * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).
6690 * @property bounds: Bounds
6691 * The bounds (specified in CRS units) where the projection is valid
6693 * @method project(latlng: LatLng): Point
6694 * Projects geographical coordinates into a 2D point.
6695 * Only accepts actual `L.LatLng` instances, not arrays.
6697 * @method unproject(point: Point): LatLng
6698 * The inverse of `project`. Projects a 2D point into a geographical location.
6699 * Only accepts actual `L.Point` instances, not arrays.
6701 * Note that the projection instances do not inherit from Leaflet's `Class` object,
6702 * and can't be instantiated. Also, new classes can't inherit from them,
6703 * and methods can't be added to them with the `include` function.
6711 SphericalMercator: SphericalMercator
6716 * @crs L.CRS.EPSG3395
\r
6718 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
\r
6720 var EPSG3395 = extend({}, Earth, {
\r
6721 code: 'EPSG:3395',
\r
6722 projection: Mercator,
\r
6724 transformation: (function () {
\r
6725 var scale = 0.5 / (Math.PI * Mercator.R);
\r
6726 return toTransformation(scale, 0.5, -scale, 0.5);
\r
6732 * @crs L.CRS.EPSG4326
\r
6734 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
\r
6736 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
\r
6737 * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
\r
6738 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
\r
6739 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
\r
6740 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
\r
6743 var EPSG4326 = extend({}, Earth, {
\r
6744 code: 'EPSG:4326',
\r
6745 projection: LonLat,
\r
6746 transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
\r
6753 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6754 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6755 * axis should still be inverted (going from bottom to top). `distance()` returns
6756 * simple euclidean distance.
6759 var Simple = extend({}, CRS, {
6761 transformation: toTransformation(1, 0, -1, 0),
6763 scale: function (zoom) {
6764 return Math.pow(2, zoom);
6767 zoom: function (scale) {
6768 return Math.log(scale) / Math.LN2;
6771 distance: function (latlng1, latlng2) {
6772 var dx = latlng2.lng - latlng1.lng,
6773 dy = latlng2.lat - latlng1.lat;
6775 return Math.sqrt(dx * dx + dy * dy);
6782 CRS.EPSG3395 = EPSG3395;
6783 CRS.EPSG3857 = EPSG3857;
6784 CRS.EPSG900913 = EPSG900913;
6785 CRS.EPSG4326 = EPSG4326;
6786 CRS.Simple = Simple;
6794 * A set of methods from the Layer base class that all Leaflet layers use.
6795 * Inherits all methods, options and events from `L.Evented`.
6800 * var layer = L.marker(latlng).addTo(map);
6806 * Fired after the layer is added to a map
6808 * @event remove: Event
6809 * Fired after the layer is removed from a map
6813 var Layer = Evented.extend({
6815 // Classes extending `L.Layer` will inherit the following options:
6817 // @option pane: String = 'overlayPane'
6818 // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
6819 pane: 'overlayPane',
6821 // @option attribution: String = null
6822 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
6825 bubblingMouseEvents: true
6829 * Classes extending `L.Layer` will inherit the following methods:
6831 * @method addTo(map: Map|LayerGroup): this
6832 * Adds the layer to the given map or layer group.
6834 addTo: function (map) {
6839 // @method remove: this
6840 // Removes the layer from the map it is currently active on.
6841 remove: function () {
6842 return this.removeFrom(this._map || this._mapToAdd);
6845 // @method removeFrom(map: Map): this
6846 // Removes the layer from the given map
6849 // @method removeFrom(group: LayerGroup): this
6850 // Removes the layer from the given `LayerGroup`
6851 removeFrom: function (obj) {
6853 obj.removeLayer(this);
6858 // @method getPane(name? : String): HTMLElement
6859 // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6860 getPane: function (name) {
6861 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6864 addInteractiveTarget: function (targetEl) {
6865 this._map._targets[stamp(targetEl)] = this;
6869 removeInteractiveTarget: function (targetEl) {
6870 delete this._map._targets[stamp(targetEl)];
6874 // @method getAttribution: String
6875 // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6876 getAttribution: function () {
6877 return this.options.attribution;
6880 _layerAdd: function (e) {
6883 // check in case layer gets added and then removed before the map is ready
6884 if (!map.hasLayer(this)) { return; }
6887 this._zoomAnimated = map._zoomAnimated;
6889 if (this.getEvents) {
6890 var events = this.getEvents();
6891 map.on(events, this);
6892 this.once('remove', function () {
6893 map.off(events, this);
6900 map.fire('layeradd', {layer: this});
6904 /* @section Extension methods
6907 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6909 * @method onAdd(map: Map): this
6910 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
6912 * @method onRemove(map: Map): this
6913 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
6915 * @method getEvents(): Object
6916 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
6918 * @method getAttribution(): String
6919 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6921 * @method beforeAdd(map: Map): this
6922 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
6927 * @section Layer events
6929 * @event layeradd: LayerEvent
6930 * Fired when a new layer is added to the map.
6932 * @event layerremove: LayerEvent
6933 * Fired when some layer is removed from the map
6935 * @section Methods for Layers and Controls
6938 // @method addLayer(layer: Layer): this
6939 // Adds the given layer to the map
6940 addLayer: function (layer) {
6941 if (!layer._layerAdd) {
6942 throw new Error('The provided object is not a Layer.');
6945 var id = stamp(layer);
6946 if (this._layers[id]) { return this; }
6947 this._layers[id] = layer;
6949 layer._mapToAdd = this;
6951 if (layer.beforeAdd) {
6952 layer.beforeAdd(this);
6955 this.whenReady(layer._layerAdd, layer);
6960 // @method removeLayer(layer: Layer): this
6961 // Removes the given layer from the map.
6962 removeLayer: function (layer) {
6963 var id = stamp(layer);
6965 if (!this._layers[id]) { return this; }
6968 layer.onRemove(this);
6971 delete this._layers[id];
6974 this.fire('layerremove', {layer: layer});
6975 layer.fire('remove');
6978 layer._map = layer._mapToAdd = null;
6983 // @method hasLayer(layer: Layer): Boolean
6984 // Returns `true` if the given layer is currently added to the map
6985 hasLayer: function (layer) {
6986 return stamp(layer) in this._layers;
6989 /* @method eachLayer(fn: Function, context?: Object): this
6990 * Iterates over the layers of the map, optionally specifying context of the iterator function.
6992 * map.eachLayer(function(layer){
6993 * layer.bindPopup('Hello');
6997 eachLayer: function (method, context) {
6998 for (var i in this._layers) {
6999 method.call(context, this._layers[i]);
7004 _addLayers: function (layers) {
7005 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
7007 for (var i = 0, len = layers.length; i < len; i++) {
7008 this.addLayer(layers[i]);
7012 _addZoomLimit: function (layer) {
7013 if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
7014 this._zoomBoundLayers[stamp(layer)] = layer;
7015 this._updateZoomLevels();
7019 _removeZoomLimit: function (layer) {
7020 var id = stamp(layer);
7022 if (this._zoomBoundLayers[id]) {
7023 delete this._zoomBoundLayers[id];
7024 this._updateZoomLevels();
7028 _updateZoomLevels: function () {
7029 var minZoom = Infinity,
7030 maxZoom = -Infinity,
7031 oldZoomSpan = this._getZoomSpan();
7033 for (var i in this._zoomBoundLayers) {
7034 var options = this._zoomBoundLayers[i].options;
7036 minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
7037 maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
7040 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
7041 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
7043 // @section Map state change events
7044 // @event zoomlevelschange: Event
7045 // Fired when the number of zoomlevels on the map is changed due
7046 // to adding or removing a layer.
7047 if (oldZoomSpan !== this._getZoomSpan()) {
7048 this.fire('zoomlevelschange');
7051 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
7052 this.setZoom(this._layersMaxZoom);
7054 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
7055 this.setZoom(this._layersMinZoom);
7061 * @class LayerGroup
\r
7062 * @aka L.LayerGroup
\r
7063 * @inherits Interactive layer
\r
7065 * Used to group several layers and handle them as one. If you add it to the map,
\r
7066 * any layers added or removed from the group will be added/removed on the map as
\r
7067 * well. Extends `Layer`.
\r
7072 * L.layerGroup([marker1, marker2])
\r
7073 * .addLayer(polyline)
\r
7078 var LayerGroup = Layer.extend({
\r
7080 initialize: function (layers, options) {
\r
7081 setOptions(this, options);
\r
7083 this._layers = {};
\r
7088 for (i = 0, len = layers.length; i < len; i++) {
\r
7089 this.addLayer(layers[i]);
\r
7094 // @method addLayer(layer: Layer): this
\r
7095 // Adds the given layer to the group.
\r
7096 addLayer: function (layer) {
\r
7097 var id = this.getLayerId(layer);
\r
7099 this._layers[id] = layer;
\r
7102 this._map.addLayer(layer);
\r
7108 // @method removeLayer(layer: Layer): this
\r
7109 // Removes the given layer from the group.
\r
7111 // @method removeLayer(id: Number): this
\r
7112 // Removes the layer with the given internal ID from the group.
\r
7113 removeLayer: function (layer) {
\r
7114 var id = layer in this._layers ? layer : this.getLayerId(layer);
\r
7116 if (this._map && this._layers[id]) {
\r
7117 this._map.removeLayer(this._layers[id]);
\r
7120 delete this._layers[id];
\r
7125 // @method hasLayer(layer: Layer): Boolean
\r
7126 // Returns `true` if the given layer is currently added to the group.
\r
7128 // @method hasLayer(id: Number): Boolean
\r
7129 // Returns `true` if the given internal ID is currently added to the group.
\r
7130 hasLayer: function (layer) {
\r
7131 var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
\r
7132 return layerId in this._layers;
\r
7135 // @method clearLayers(): this
\r
7136 // Removes all the layers from the group.
\r
7137 clearLayers: function () {
\r
7138 return this.eachLayer(this.removeLayer, this);
\r
7141 // @method invoke(methodName: String, …): this
\r
7142 // Calls `methodName` on every layer contained in this group, passing any
\r
7143 // additional parameters. Has no effect if the layers contained do not
\r
7144 // implement `methodName`.
\r
7145 invoke: function (methodName) {
\r
7146 var args = Array.prototype.slice.call(arguments, 1),
\r
7149 for (i in this._layers) {
\r
7150 layer = this._layers[i];
\r
7152 if (layer[methodName]) {
\r
7153 layer[methodName].apply(layer, args);
\r
7160 onAdd: function (map) {
\r
7161 this.eachLayer(map.addLayer, map);
\r
7164 onRemove: function (map) {
\r
7165 this.eachLayer(map.removeLayer, map);
\r
7168 // @method eachLayer(fn: Function, context?: Object): this
\r
7169 // Iterates over the layers of the group, optionally specifying context of the iterator function.
\r
7171 // group.eachLayer(function (layer) {
\r
7172 // layer.bindPopup('Hello');
\r
7175 eachLayer: function (method, context) {
\r
7176 for (var i in this._layers) {
\r
7177 method.call(context, this._layers[i]);
\r
7182 // @method getLayer(id: Number): Layer
\r
7183 // Returns the layer with the given internal ID.
\r
7184 getLayer: function (id) {
\r
7185 return this._layers[id];
\r
7188 // @method getLayers(): Layer[]
\r
7189 // Returns an array of all the layers added to the group.
\r
7190 getLayers: function () {
\r
7192 this.eachLayer(layers.push, layers);
\r
7196 // @method setZIndex(zIndex: Number): this
\r
7197 // Calls `setZIndex` on every layer contained in this group, passing the z-index.
\r
7198 setZIndex: function (zIndex) {
\r
7199 return this.invoke('setZIndex', zIndex);
\r
7202 // @method getLayerId(layer: Layer): Number
\r
7203 // Returns the internal ID for a layer
\r
7204 getLayerId: function (layer) {
\r
7205 return stamp(layer);
\r
7210 // @factory L.layerGroup(layers?: Layer[], options?: Object)
\r
7211 // Create a layer group, optionally given an initial set of layers and an `options` object.
\r
7212 var layerGroup = function (layers, options) {
\r
7213 return new LayerGroup(layers, options);
\r
7217 * @class FeatureGroup
\r
7218 * @aka L.FeatureGroup
\r
7219 * @inherits LayerGroup
\r
7221 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
\r
7222 * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
\r
7223 * * Events are propagated to the `FeatureGroup`, so if the group has an event
\r
7224 * handler, it will handle events from any of the layers. This includes mouse events
\r
7225 * and custom events.
\r
7226 * * Has `layeradd` and `layerremove` events
\r
7231 * L.featureGroup([marker1, marker2, polyline])
\r
7232 * .bindPopup('Hello world!')
\r
7233 * .on('click', function() { alert('Clicked on a member of the group!'); })
\r
7238 var FeatureGroup = LayerGroup.extend({
\r
7240 addLayer: function (layer) {
\r
7241 if (this.hasLayer(layer)) {
\r
7245 layer.addEventParent(this);
\r
7247 LayerGroup.prototype.addLayer.call(this, layer);
\r
7249 // @event layeradd: LayerEvent
\r
7250 // Fired when a layer is added to this `FeatureGroup`
\r
7251 return this.fire('layeradd', {layer: layer});
\r
7254 removeLayer: function (layer) {
\r
7255 if (!this.hasLayer(layer)) {
\r
7258 if (layer in this._layers) {
\r
7259 layer = this._layers[layer];
\r
7262 layer.removeEventParent(this);
\r
7264 LayerGroup.prototype.removeLayer.call(this, layer);
\r
7266 // @event layerremove: LayerEvent
\r
7267 // Fired when a layer is removed from this `FeatureGroup`
\r
7268 return this.fire('layerremove', {layer: layer});
\r
7271 // @method setStyle(style: Path options): this
\r
7272 // Sets the given path options to each layer of the group that has a `setStyle` method.
\r
7273 setStyle: function (style) {
\r
7274 return this.invoke('setStyle', style);
\r
7277 // @method bringToFront(): this
\r
7278 // Brings the layer group to the top of all other layers
\r
7279 bringToFront: function () {
\r
7280 return this.invoke('bringToFront');
\r
7283 // @method bringToBack(): this
\r
7284 // Brings the layer group to the back of all other layers
\r
7285 bringToBack: function () {
\r
7286 return this.invoke('bringToBack');
\r
7289 // @method getBounds(): LatLngBounds
\r
7290 // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
\r
7291 getBounds: function () {
\r
7292 var bounds = new LatLngBounds();
\r
7294 for (var id in this._layers) {
\r
7295 var layer = this._layers[id];
\r
7296 bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
\r
7302 // @factory L.featureGroup(layers?: Layer[], options?: Object)
\r
7303 // Create a feature group, optionally given an initial set of layers and an `options` object.
\r
7304 var featureGroup = function (layers, options) {
\r
7305 return new FeatureGroup(layers, options);
\r
7312 * Represents an icon to provide when creating a marker.
\r
7317 * var myIcon = L.icon({
\r
7318 * iconUrl: 'my-icon.png',
\r
7319 * iconRetinaUrl: 'my-icon@2x.png',
\r
7320 * iconSize: [38, 95],
\r
7321 * iconAnchor: [22, 94],
\r
7322 * popupAnchor: [-3, -76],
\r
7323 * shadowUrl: 'my-icon-shadow.png',
\r
7324 * shadowRetinaUrl: 'my-icon-shadow@2x.png',
\r
7325 * shadowSize: [68, 95],
\r
7326 * shadowAnchor: [22, 94]
\r
7329 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
\r
7332 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
\r
7336 var Icon = Class.extend({
\r
7339 * @aka Icon options
\r
7341 * @option iconUrl: String = null
\r
7342 * **(required)** The URL to the icon image (absolute or relative to your script path).
\r
7344 * @option iconRetinaUrl: String = null
\r
7345 * The URL to a retina sized version of the icon image (absolute or relative to your
\r
7346 * script path). Used for Retina screen devices.
\r
7348 * @option iconSize: Point = null
\r
7349 * Size of the icon image in pixels.
\r
7351 * @option iconAnchor: Point = null
\r
7352 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
\r
7353 * will be aligned so that this point is at the marker's geographical location. Centered
\r
7354 * by default if size is specified, also can be set in CSS with negative margins.
\r
7356 * @option popupAnchor: Point = [0, 0]
\r
7357 * The coordinates of the point from which popups will "open", relative to the icon anchor.
\r
7359 * @option tooltipAnchor: Point = [0, 0]
\r
7360 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
\r
7362 * @option shadowUrl: String = null
\r
7363 * The URL to the icon shadow image. If not specified, no shadow image will be created.
\r
7365 * @option shadowRetinaUrl: String = null
\r
7367 * @option shadowSize: Point = null
\r
7368 * Size of the shadow image in pixels.
\r
7370 * @option shadowAnchor: Point = null
\r
7371 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
\r
7372 * as iconAnchor if not specified).
\r
7374 * @option className: String = ''
\r
7375 * A custom class name to assign to both icon and shadow images. Empty by default.
\r
7379 popupAnchor: [0, 0],
\r
7380 tooltipAnchor: [0, 0],
\r
7382 // @option crossOrigin: Boolean|String = false
\r
7383 // Whether the crossOrigin attribute will be added to the tiles.
\r
7384 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
\r
7385 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
\r
7386 crossOrigin: false
\r
7389 initialize: function (options) {
\r
7390 setOptions(this, options);
\r
7393 // @method createIcon(oldIcon?: HTMLElement): HTMLElement
\r
7394 // Called internally when the icon has to be shown, returns a `<img>` HTML element
\r
7395 // styled according to the options.
\r
7396 createIcon: function (oldIcon) {
\r
7397 return this._createIcon('icon', oldIcon);
\r
7400 // @method createShadow(oldIcon?: HTMLElement): HTMLElement
\r
7401 // As `createIcon`, but for the shadow beneath it.
\r
7402 createShadow: function (oldIcon) {
\r
7403 return this._createIcon('shadow', oldIcon);
\r
7406 _createIcon: function (name, oldIcon) {
\r
7407 var src = this._getIconUrl(name);
\r
7410 if (name === 'icon') {
\r
7411 throw new Error('iconUrl not set in Icon options (see the docs).');
\r
7416 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
\r
7417 this._setIconStyles(img, name);
\r
7419 if (this.options.crossOrigin || this.options.crossOrigin === '') {
\r
7420 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
\r
7426 _setIconStyles: function (img, name) {
\r
7427 var options = this.options;
\r
7428 var sizeOption = options[name + 'Size'];
\r
7430 if (typeof sizeOption === 'number') {
\r
7431 sizeOption = [sizeOption, sizeOption];
\r
7434 var size = toPoint(sizeOption),
\r
7435 anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
\r
7436 size && size.divideBy(2, true));
\r
7438 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
\r
7441 img.style.marginLeft = (-anchor.x) + 'px';
\r
7442 img.style.marginTop = (-anchor.y) + 'px';
\r
7446 img.style.width = size.x + 'px';
\r
7447 img.style.height = size.y + 'px';
\r
7451 _createImg: function (src, el) {
\r
7452 el = el || document.createElement('img');
\r
7457 _getIconUrl: function (name) {
\r
7458 return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
\r
7463 // @factory L.icon(options: Icon options)
\r
7464 // Creates an icon instance with the given options.
\r
7465 function icon(options) {
\r
7466 return new Icon(options);
\r
7470 * @miniclass Icon.Default (Icon)
7471 * @aka L.Icon.Default
7474 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7475 * no icon is specified. Points to the blue marker image distributed with Leaflet
7478 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7479 * (which is a set of `Icon options`).
7481 * If you want to _completely_ replace the default icon, override the
7482 * `L.Marker.prototype.options.icon` with your own icon instead.
7485 var IconDefault = Icon.extend({
7488 iconUrl: 'marker-icon.png',
7489 iconRetinaUrl: 'marker-icon-2x.png',
7490 shadowUrl: 'marker-shadow.png',
7492 iconAnchor: [12, 41],
7493 popupAnchor: [1, -34],
7494 tooltipAnchor: [16, -28],
7495 shadowSize: [41, 41]
7498 _getIconUrl: function (name) {
7499 if (typeof IconDefault.imagePath !== 'string') { // Deprecated, backwards-compatibility only
7500 IconDefault.imagePath = this._detectIconPath();
7503 // @option imagePath: String
7504 // `Icon.Default` will try to auto-detect the location of the
7505 // blue icon images. If you are placing these images in a non-standard
7506 // way, set this option to point to the right path.
7507 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7510 _stripUrl: function (path) { // separate function to use in tests
7511 var strip = function (str, re, idx) {
7512 var match = re.exec(str);
7513 return match && match[idx];
7515 path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
7516 return path && strip(path, /^(.*)marker-icon\.png$/, 1);
7519 _detectIconPath: function () {
7520 var el = create$1('div', 'leaflet-default-icon-path', document.body);
7521 var path = getStyle(el, 'background-image') ||
7522 getStyle(el, 'backgroundImage'); // IE8
7524 document.body.removeChild(el);
7525 path = this._stripUrl(path);
7526 if (path) { return path; }
7527 var link = document.querySelector('link[href$="leaflet.css"]');
7528 if (!link) { return ''; }
7529 return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
7534 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7538 /* @namespace Marker
7539 * @section Interaction handlers
7541 * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
7544 * marker.dragging.disable();
7547 * @property dragging: Handler
7548 * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7551 var MarkerDrag = Handler.extend({
7552 initialize: function (marker) {
7553 this._marker = marker;
7556 addHooks: function () {
7557 var icon = this._marker._icon;
7559 if (!this._draggable) {
7560 this._draggable = new Draggable(icon, icon, true);
7563 this._draggable.on({
7564 dragstart: this._onDragStart,
7565 predrag: this._onPreDrag,
7567 dragend: this._onDragEnd
7570 addClass(icon, 'leaflet-marker-draggable');
7573 removeHooks: function () {
7574 this._draggable.off({
7575 dragstart: this._onDragStart,
7576 predrag: this._onPreDrag,
7578 dragend: this._onDragEnd
7581 if (this._marker._icon) {
7582 removeClass(this._marker._icon, 'leaflet-marker-draggable');
7586 moved: function () {
7587 return this._draggable && this._draggable._moved;
7590 _adjustPan: function (e) {
7591 var marker = this._marker,
7593 speed = this._marker.options.autoPanSpeed,
7594 padding = this._marker.options.autoPanPadding,
7595 iconPos = getPosition(marker._icon),
7596 bounds = map.getPixelBounds(),
7597 origin = map.getPixelOrigin();
7599 var panBounds = toBounds(
7600 bounds.min._subtract(origin).add(padding),
7601 bounds.max._subtract(origin).subtract(padding)
7604 if (!panBounds.contains(iconPos)) {
7605 // Compute incremental movement
7606 var movement = toPoint(
7607 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7608 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7610 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7611 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7612 ).multiplyBy(speed);
7614 map.panBy(movement, {animate: false});
7616 this._draggable._newPos._add(movement);
7617 this._draggable._startPos._add(movement);
7619 setPosition(marker._icon, this._draggable._newPos);
7622 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7626 _onDragStart: function () {
7627 // @section Dragging events
7628 // @event dragstart: Event
7629 // Fired when the user starts dragging the marker.
7631 // @event movestart: Event
7632 // Fired when the marker starts moving (because of dragging).
7634 this._oldLatLng = this._marker.getLatLng();
7636 // When using ES6 imports it could not be set when `Popup` was not imported as well
7637 this._marker.closePopup && this._marker.closePopup();
7644 _onPreDrag: function (e) {
7645 if (this._marker.options.autoPan) {
7646 cancelAnimFrame(this._panRequest);
7647 this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7651 _onDrag: function (e) {
7652 var marker = this._marker,
7653 shadow = marker._shadow,
7654 iconPos = getPosition(marker._icon),
7655 latlng = marker._map.layerPointToLatLng(iconPos);
7657 // update shadow position
7659 setPosition(shadow, iconPos);
7662 marker._latlng = latlng;
7664 e.oldLatLng = this._oldLatLng;
7666 // @event drag: Event
7667 // Fired repeatedly while the user drags the marker.
7673 _onDragEnd: function (e) {
7674 // @event dragend: DragEndEvent
7675 // Fired when the user stops dragging the marker.
7677 cancelAnimFrame(this._panRequest);
7679 // @event moveend: Event
7680 // Fired when the marker stops moving (because of dragging).
7681 delete this._oldLatLng;
7684 .fire('dragend', e);
7690 * @inherits Interactive layer
\r
7692 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
\r
7697 * L.marker([50.5, 30.5]).addTo(map);
\r
7701 var Marker = Layer.extend({
\r
7704 // @aka Marker options
\r
7706 // @option icon: Icon = *
\r
7707 // Icon instance to use for rendering the marker.
\r
7708 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
\r
7709 // If not specified, a common instance of `L.Icon.Default` is used.
\r
7710 icon: new IconDefault(),
\r
7712 // Option inherited from "Interactive layer" abstract class
\r
7713 interactive: true,
\r
7715 // @option keyboard: Boolean = true
\r
7716 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
\r
7719 // @option title: String = ''
\r
7720 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
\r
7721 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
\r
7724 // @option alt: String = 'Marker'
\r
7725 // Text for the `alt` attribute of the icon image.
\r
7726 // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
\r
7729 // @option zIndexOffset: Number = 0
\r
7730 // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
\r
7733 // @option opacity: Number = 1.0
\r
7734 // The opacity of the marker.
\r
7737 // @option riseOnHover: Boolean = false
\r
7738 // If `true`, the marker will get on top of others when you hover the mouse over it.
\r
7739 riseOnHover: false,
\r
7741 // @option riseOffset: Number = 250
\r
7742 // The z-index offset used for the `riseOnHover` feature.
\r
7745 // @option pane: String = 'markerPane'
\r
7746 // `Map pane` where the markers icon will be added.
\r
7747 pane: 'markerPane',
\r
7749 // @option shadowPane: String = 'shadowPane'
\r
7750 // `Map pane` where the markers shadow will be added.
\r
7751 shadowPane: 'shadowPane',
\r
7753 // @option bubblingMouseEvents: Boolean = false
\r
7754 // When `true`, a mouse event on this marker will trigger the same event on the map
\r
7755 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
\r
7756 bubblingMouseEvents: false,
\r
7758 // @option autoPanOnFocus: Boolean = true
\r
7759 // When `true`, the map will pan whenever the marker is focused (via
\r
7760 // e.g. pressing `tab` on the keyboard) to ensure the marker is
\r
7761 // visible within the map's bounds
\r
7762 autoPanOnFocus: true,
\r
7764 // @section Draggable marker options
\r
7765 // @option draggable: Boolean = false
\r
7766 // Whether the marker is draggable with mouse/touch or not.
\r
7769 // @option autoPan: Boolean = false
\r
7770 // Whether to pan the map when dragging this marker near its edge or not.
\r
7773 // @option autoPanPadding: Point = Point(50, 50)
\r
7774 // Distance (in pixels to the left/right and to the top/bottom) of the
\r
7775 // map edge to start panning the map.
\r
7776 autoPanPadding: [50, 50],
\r
7778 // @option autoPanSpeed: Number = 10
\r
7779 // Number of pixels the map should pan by.
\r
7785 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
\r
7788 initialize: function (latlng, options) {
\r
7789 setOptions(this, options);
\r
7790 this._latlng = toLatLng(latlng);
\r
7793 onAdd: function (map) {
\r
7794 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
\r
7796 if (this._zoomAnimated) {
\r
7797 map.on('zoomanim', this._animateZoom, this);
\r
7804 onRemove: function (map) {
\r
7805 if (this.dragging && this.dragging.enabled()) {
\r
7806 this.options.draggable = true;
\r
7807 this.dragging.removeHooks();
\r
7809 delete this.dragging;
\r
7811 if (this._zoomAnimated) {
\r
7812 map.off('zoomanim', this._animateZoom, this);
\r
7815 this._removeIcon();
\r
7816 this._removeShadow();
\r
7819 getEvents: function () {
\r
7821 zoom: this.update,
\r
7822 viewreset: this.update
\r
7826 // @method getLatLng: LatLng
\r
7827 // Returns the current geographical position of the marker.
\r
7828 getLatLng: function () {
\r
7829 return this._latlng;
\r
7832 // @method setLatLng(latlng: LatLng): this
\r
7833 // Changes the marker position to the given point.
\r
7834 setLatLng: function (latlng) {
\r
7835 var oldLatLng = this._latlng;
\r
7836 this._latlng = toLatLng(latlng);
\r
7839 // @event move: Event
\r
7840 // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
\r
7841 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
\r
7844 // @method setZIndexOffset(offset: Number): this
\r
7845 // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
\r
7846 setZIndexOffset: function (offset) {
\r
7847 this.options.zIndexOffset = offset;
\r
7848 return this.update();
\r
7851 // @method getIcon: Icon
\r
7852 // Returns the current icon used by the marker
\r
7853 getIcon: function () {
\r
7854 return this.options.icon;
\r
7857 // @method setIcon(icon: Icon): this
\r
7858 // Changes the marker icon.
\r
7859 setIcon: function (icon) {
\r
7861 this.options.icon = icon;
\r
7868 if (this._popup) {
\r
7869 this.bindPopup(this._popup, this._popup.options);
\r
7875 getElement: function () {
\r
7876 return this._icon;
\r
7879 update: function () {
\r
7881 if (this._icon && this._map) {
\r
7882 var pos = this._map.latLngToLayerPoint(this._latlng).round();
\r
7883 this._setPos(pos);
\r
7889 _initIcon: function () {
\r
7890 var options = this.options,
\r
7891 classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
\r
7893 var icon = options.icon.createIcon(this._icon),
\r
7896 // if we're not reusing the icon, remove the old one and init new one
\r
7897 if (icon !== this._icon) {
\r
7899 this._removeIcon();
\r
7903 if (options.title) {
\r
7904 icon.title = options.title;
\r
7907 if (icon.tagName === 'IMG') {
\r
7908 icon.alt = options.alt || '';
\r
7912 addClass(icon, classToAdd);
\r
7914 if (options.keyboard) {
\r
7915 icon.tabIndex = '0';
\r
7916 icon.setAttribute('role', 'button');
\r
7919 this._icon = icon;
\r
7921 if (options.riseOnHover) {
\r
7923 mouseover: this._bringToFront,
\r
7924 mouseout: this._resetZIndex
\r
7928 if (this.options.autoPanOnFocus) {
\r
7929 on(icon, 'focus', this._panOnFocus, this);
\r
7932 var newShadow = options.icon.createShadow(this._shadow),
\r
7933 addShadow = false;
\r
7935 if (newShadow !== this._shadow) {
\r
7936 this._removeShadow();
\r
7941 addClass(newShadow, classToAdd);
\r
7942 newShadow.alt = '';
\r
7944 this._shadow = newShadow;
\r
7947 if (options.opacity < 1) {
\r
7948 this._updateOpacity();
\r
7953 this.getPane().appendChild(this._icon);
\r
7955 this._initInteraction();
\r
7956 if (newShadow && addShadow) {
\r
7957 this.getPane(options.shadowPane).appendChild(this._shadow);
\r
7961 _removeIcon: function () {
\r
7962 if (this.options.riseOnHover) {
\r
7964 mouseover: this._bringToFront,
\r
7965 mouseout: this._resetZIndex
\r
7969 if (this.options.autoPanOnFocus) {
\r
7970 off(this._icon, 'focus', this._panOnFocus, this);
\r
7973 remove(this._icon);
\r
7974 this.removeInteractiveTarget(this._icon);
\r
7976 this._icon = null;
\r
7979 _removeShadow: function () {
\r
7980 if (this._shadow) {
\r
7981 remove(this._shadow);
\r
7983 this._shadow = null;
\r
7986 _setPos: function (pos) {
\r
7989 setPosition(this._icon, pos);
\r
7992 if (this._shadow) {
\r
7993 setPosition(this._shadow, pos);
\r
7996 this._zIndex = pos.y + this.options.zIndexOffset;
\r
7998 this._resetZIndex();
\r
8001 _updateZIndex: function (offset) {
\r
8003 this._icon.style.zIndex = this._zIndex + offset;
\r
8007 _animateZoom: function (opt) {
\r
8008 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
\r
8010 this._setPos(pos);
\r
8013 _initInteraction: function () {
\r
8015 if (!this.options.interactive) { return; }
\r
8017 addClass(this._icon, 'leaflet-interactive');
\r
8019 this.addInteractiveTarget(this._icon);
\r
8022 var draggable = this.options.draggable;
\r
8023 if (this.dragging) {
\r
8024 draggable = this.dragging.enabled();
\r
8025 this.dragging.disable();
\r
8028 this.dragging = new MarkerDrag(this);
\r
8031 this.dragging.enable();
\r
8036 // @method setOpacity(opacity: Number): this
\r
8037 // Changes the opacity of the marker.
\r
8038 setOpacity: function (opacity) {
\r
8039 this.options.opacity = opacity;
\r
8041 this._updateOpacity();
\r
8047 _updateOpacity: function () {
\r
8048 var opacity = this.options.opacity;
\r
8051 setOpacity(this._icon, opacity);
\r
8054 if (this._shadow) {
\r
8055 setOpacity(this._shadow, opacity);
\r
8059 _bringToFront: function () {
\r
8060 this._updateZIndex(this.options.riseOffset);
\r
8063 _resetZIndex: function () {
\r
8064 this._updateZIndex(0);
\r
8067 _panOnFocus: function () {
\r
8068 var map = this._map;
\r
8069 if (!map) { return; }
\r
8071 var iconOpts = this.options.icon.options;
\r
8072 var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);
\r
8073 var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);
\r
8075 map.panInside(this._latlng, {
\r
8076 paddingTopLeft: anchor,
\r
8077 paddingBottomRight: size.subtract(anchor)
\r
8081 _getPopupAnchor: function () {
\r
8082 return this.options.icon.options.popupAnchor;
\r
8085 _getTooltipAnchor: function () {
\r
8086 return this.options.icon.options.tooltipAnchor;
\r
8091 // factory L.marker(latlng: LatLng, options? : Marker options)
\r
8093 // @factory L.marker(latlng: LatLng, options? : Marker options)
\r
8094 // Instantiates a Marker object given a geographical point and optionally an options object.
\r
8095 function marker(latlng, options) {
\r
8096 return new Marker(latlng, options);
\r
8102 * @inherits Interactive layer
8104 * An abstract class that contains options and constants shared between vector
8105 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8108 var Path = Layer.extend({
8111 // @aka Path options
8113 // @option stroke: Boolean = true
8114 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8117 // @option color: String = '#3388ff'
8121 // @option weight: Number = 3
8122 // Stroke width in pixels
8125 // @option opacity: Number = 1.0
8129 // @option lineCap: String= 'round'
8130 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8133 // @option lineJoin: String = 'round'
8134 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8137 // @option dashArray: String = null
8138 // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
8141 // @option dashOffset: String = null
8142 // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
8145 // @option fill: Boolean = depends
8146 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8149 // @option fillColor: String = *
8150 // Fill color. Defaults to the value of the [`color`](#path-color) option
8153 // @option fillOpacity: Number = 0.2
8157 // @option fillRule: String = 'evenodd'
8158 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8159 fillRule: 'evenodd',
8163 // Option inherited from "Interactive layer" abstract class
8166 // @option bubblingMouseEvents: Boolean = true
8167 // When `true`, a mouse event on this path will trigger the same event on the map
8168 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
8169 bubblingMouseEvents: true
8172 beforeAdd: function (map) {
8173 // Renderer is set here because we need to call renderer.getEvents
8174 // before this.getEvents.
8175 this._renderer = map.getRenderer(this);
8178 onAdd: function () {
8179 this._renderer._initPath(this);
8181 this._renderer._addPath(this);
8184 onRemove: function () {
8185 this._renderer._removePath(this);
8188 // @method redraw(): this
8189 // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8190 redraw: function () {
8192 this._renderer._updatePath(this);
8197 // @method setStyle(style: Path options): this
8198 // Changes the appearance of a Path based on the options in the `Path options` object.
8199 setStyle: function (style) {
8200 setOptions(this, style);
8201 if (this._renderer) {
8202 this._renderer._updateStyle(this);
8203 if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
8204 this._updateBounds();
8210 // @method bringToFront(): this
8211 // Brings the layer to the top of all path layers.
8212 bringToFront: function () {
8213 if (this._renderer) {
8214 this._renderer._bringToFront(this);
8219 // @method bringToBack(): this
8220 // Brings the layer to the bottom of all path layers.
8221 bringToBack: function () {
8222 if (this._renderer) {
8223 this._renderer._bringToBack(this);
8228 getElement: function () {
8232 _reset: function () {
8233 // defined in child classes
8238 _clickTolerance: function () {
8239 // used when doing hit detection for Canvas layers
8240 return (this.options.stroke ? this.options.weight / 2 : 0) +
8241 (this._renderer.options.tolerance || 0);
8246 * @class CircleMarker
8247 * @aka L.CircleMarker
8250 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8253 var CircleMarker = Path.extend({
8256 // @aka CircleMarker options
8260 // @option radius: Number = 10
8261 // Radius of the circle marker, in pixels
8265 initialize: function (latlng, options) {
8266 setOptions(this, options);
8267 this._latlng = toLatLng(latlng);
8268 this._radius = this.options.radius;
8271 // @method setLatLng(latLng: LatLng): this
8272 // Sets the position of a circle marker to a new location.
8273 setLatLng: function (latlng) {
8274 var oldLatLng = this._latlng;
8275 this._latlng = toLatLng(latlng);
8278 // @event move: Event
8279 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
8280 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
8283 // @method getLatLng(): LatLng
8284 // Returns the current geographical position of the circle marker
8285 getLatLng: function () {
8286 return this._latlng;
8289 // @method setRadius(radius: Number): this
8290 // Sets the radius of a circle marker. Units are in pixels.
8291 setRadius: function (radius) {
8292 this.options.radius = this._radius = radius;
8293 return this.redraw();
8296 // @method getRadius(): Number
8297 // Returns the current radius of the circle
8298 getRadius: function () {
8299 return this._radius;
8302 setStyle : function (options) {
8303 var radius = options && options.radius || this._radius;
8304 Path.prototype.setStyle.call(this, options);
8305 this.setRadius(radius);
8309 _project: function () {
8310 this._point = this._map.latLngToLayerPoint(this._latlng);
8311 this._updateBounds();
8314 _updateBounds: function () {
8315 var r = this._radius,
8316 r2 = this._radiusY || r,
8317 w = this._clickTolerance(),
8318 p = [r + w, r2 + w];
8319 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
8322 _update: function () {
8328 _updatePath: function () {
8329 this._renderer._updateCircle(this);
8332 _empty: function () {
8333 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8336 // Needed by the `Canvas` renderer for interactivity
8337 _containsPoint: function (p) {
8338 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8343 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8344 // Instantiates a circle marker object given a geographical point, and an optional options object.
8345 function circleMarker(latlng, options) {
8346 return new CircleMarker(latlng, options);
8352 * @inherits CircleMarker
8354 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8356 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8361 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8365 var Circle = CircleMarker.extend({
8367 initialize: function (latlng, options, legacyOptions) {
8368 if (typeof options === 'number') {
8369 // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8370 options = extend({}, legacyOptions, {radius: options});
8372 setOptions(this, options);
8373 this._latlng = toLatLng(latlng);
8375 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8378 // @aka Circle options
8379 // @option radius: Number; Radius of the circle, in meters.
8380 this._mRadius = this.options.radius;
8383 // @method setRadius(radius: Number): this
8384 // Sets the radius of a circle. Units are in meters.
8385 setRadius: function (radius) {
8386 this._mRadius = radius;
8387 return this.redraw();
8390 // @method getRadius(): Number
8391 // Returns the current radius of a circle. Units are in meters.
8392 getRadius: function () {
8393 return this._mRadius;
8396 // @method getBounds(): LatLngBounds
8397 // Returns the `LatLngBounds` of the path.
8398 getBounds: function () {
8399 var half = [this._radius, this._radiusY || this._radius];
8401 return new LatLngBounds(
8402 this._map.layerPointToLatLng(this._point.subtract(half)),
8403 this._map.layerPointToLatLng(this._point.add(half)));
8406 setStyle: Path.prototype.setStyle,
8408 _project: function () {
8410 var lng = this._latlng.lng,
8411 lat = this._latlng.lat,
8413 crs = map.options.crs;
8415 if (crs.distance === Earth.distance) {
8416 var d = Math.PI / 180,
8417 latR = (this._mRadius / Earth.R) / d,
8418 top = map.project([lat + latR, lng]),
8419 bottom = map.project([lat - latR, lng]),
8420 p = top.add(bottom).divideBy(2),
8421 lat2 = map.unproject(p).lat,
8422 lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8423 (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8425 if (isNaN(lngR) || lngR === 0) {
8426 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8429 this._point = p.subtract(map.getPixelOrigin());
8430 this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8431 this._radiusY = p.y - top.y;
8434 var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8436 this._point = map.latLngToLayerPoint(this._latlng);
8437 this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8440 this._updateBounds();
8444 // @factory L.circle(latlng: LatLng, options?: Circle options)
8445 // Instantiates a circle object given a geographical point, and an options object
8446 // which contains the circle radius.
8448 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8449 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8450 // Do not use in new applications or plugins.
8451 function circle(latlng, options, legacyOptions) {
8452 return new Circle(latlng, options, legacyOptions);
8460 * A class for drawing polyline overlays on a map. Extends `Path`.
8465 * // create a red polyline from an array of LatLng points
8472 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8474 * // zoom the map to the polyline
8475 * map.fitBounds(polyline.getBounds());
8478 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8481 * // create a red polyline from an array of arrays of LatLng points
8483 * [[45.51, -122.68],
8494 var Polyline = Path.extend({
8497 // @aka Polyline options
8499 // @option smoothFactor: Number = 1.0
8500 // How much to simplify the polyline on each zoom level. More means
8501 // better performance and smoother look, and less means more accurate representation.
8504 // @option noClip: Boolean = false
8505 // Disable polyline clipping.
8509 initialize: function (latlngs, options) {
8510 setOptions(this, options);
8511 this._setLatLngs(latlngs);
8514 // @method getLatLngs(): LatLng[]
8515 // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8516 getLatLngs: function () {
8517 return this._latlngs;
8520 // @method setLatLngs(latlngs: LatLng[]): this
8521 // Replaces all the points in the polyline with the given array of geographical points.
8522 setLatLngs: function (latlngs) {
8523 this._setLatLngs(latlngs);
8524 return this.redraw();
8527 // @method isEmpty(): Boolean
8528 // Returns `true` if the Polyline has no LatLngs.
8529 isEmpty: function () {
8530 return !this._latlngs.length;
8533 // @method closestLayerPoint(p: Point): Point
8534 // Returns the point closest to `p` on the Polyline.
8535 closestLayerPoint: function (p) {
8536 var minDistance = Infinity,
8538 closest = _sqClosestPointOnSegment,
8541 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8542 var points = this._parts[j];
8544 for (var i = 1, len = points.length; i < len; i++) {
8548 var sqDist = closest(p, p1, p2, true);
8550 if (sqDist < minDistance) {
8551 minDistance = sqDist;
8552 minPoint = closest(p, p1, p2);
8557 minPoint.distance = Math.sqrt(minDistance);
8562 // @method getCenter(): LatLng
8563 // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
8564 getCenter: function () {
8565 // throws error when not yet added to map as this center calculation requires projected coordinates
8567 throw new Error('Must add layer to map before using getCenter()');
8569 return polylineCenter(this._defaultShape(), this._map.options.crs);
8572 // @method getBounds(): LatLngBounds
8573 // Returns the `LatLngBounds` of the path.
8574 getBounds: function () {
8575 return this._bounds;
8578 // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
8579 // Adds a given point to the polyline. By default, adds to the first ring of
8580 // the polyline in case of a multi-polyline, but can be overridden by passing
8581 // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8582 addLatLng: function (latlng, latlngs) {
8583 latlngs = latlngs || this._defaultShape();
8584 latlng = toLatLng(latlng);
8585 latlngs.push(latlng);
8586 this._bounds.extend(latlng);
8587 return this.redraw();
8590 _setLatLngs: function (latlngs) {
8591 this._bounds = new LatLngBounds();
8592 this._latlngs = this._convertLatLngs(latlngs);
8595 _defaultShape: function () {
8596 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8599 // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8600 _convertLatLngs: function (latlngs) {
8602 flat = isFlat(latlngs);
8604 for (var i = 0, len = latlngs.length; i < len; i++) {
8606 result[i] = toLatLng(latlngs[i]);
8607 this._bounds.extend(result[i]);
8609 result[i] = this._convertLatLngs(latlngs[i]);
8616 _project: function () {
8617 var pxBounds = new Bounds();
8619 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8621 if (this._bounds.isValid() && pxBounds.isValid()) {
8622 this._rawPxBounds = pxBounds;
8623 this._updateBounds();
8627 _updateBounds: function () {
8628 var w = this._clickTolerance(),
8629 p = new Point(w, w);
8631 if (!this._rawPxBounds) {
8635 this._pxBounds = new Bounds([
8636 this._rawPxBounds.min.subtract(p),
8637 this._rawPxBounds.max.add(p)
8641 // recursively turns latlngs into a set of rings with projected coordinates
8642 _projectLatlngs: function (latlngs, result, projectedBounds) {
8643 var flat = latlngs[0] instanceof LatLng,
8644 len = latlngs.length,
8649 for (i = 0; i < len; i++) {
8650 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8651 projectedBounds.extend(ring[i]);
8655 for (i = 0; i < len; i++) {
8656 this._projectLatlngs(latlngs[i], result, projectedBounds);
8661 // clip polyline by renderer bounds so that we have less to render for performance
8662 _clipPoints: function () {
8663 var bounds = this._renderer._bounds;
8666 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8670 if (this.options.noClip) {
8671 this._parts = this._rings;
8675 var parts = this._parts,
8676 i, j, k, len, len2, segment, points;
8678 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8679 points = this._rings[i];
8681 for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8682 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8684 if (!segment) { continue; }
8686 parts[k] = parts[k] || [];
8687 parts[k].push(segment[0]);
8689 // if segment goes out of screen, or it's the last one, it's the end of the line part
8690 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8691 parts[k].push(segment[1]);
8698 // simplify each clipped part of the polyline for performance
8699 _simplifyPoints: function () {
8700 var parts = this._parts,
8701 tolerance = this.options.smoothFactor;
8703 for (var i = 0, len = parts.length; i < len; i++) {
8704 parts[i] = simplify(parts[i], tolerance);
8708 _update: function () {
8709 if (!this._map) { return; }
8712 this._simplifyPoints();
8716 _updatePath: function () {
8717 this._renderer._updatePoly(this);
8720 // Needed by the `Canvas` renderer for interactivity
8721 _containsPoint: function (p, closed) {
8722 var i, j, k, len, len2, part,
8723 w = this._clickTolerance();
8725 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8727 // hit detection for polylines
8728 for (i = 0, len = this._parts.length; i < len; i++) {
8729 part = this._parts[i];
8731 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8732 if (!closed && (j === 0)) { continue; }
8734 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8743 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8744 // Instantiates a polyline object given an array of geographical points and
8745 // optionally an options object. You can create a `Polyline` object with
8746 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8747 // of geographic points.
8748 function polyline(latlngs, options) {
8749 return new Polyline(latlngs, options);
8752 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8753 Polyline._flat = _flat;
8758 * @inherits Polyline
8760 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8762 * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
8768 * // create a red polygon from an array of LatLng points
8769 * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8771 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8773 * // zoom the map to the polygon
8774 * map.fitBounds(polygon.getBounds());
8777 * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
8781 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8782 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8786 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8790 * [ // first polygon
8791 * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8792 * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8794 * [ // second polygon
8795 * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8801 var Polygon = Polyline.extend({
8807 isEmpty: function () {
8808 return !this._latlngs.length || !this._latlngs[0].length;
8811 // @method getCenter(): LatLng
8812 // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
8813 getCenter: function () {
8814 // throws error when not yet added to map as this center calculation requires projected coordinates
8816 throw new Error('Must add layer to map before using getCenter()');
8818 return polygonCenter(this._defaultShape(), this._map.options.crs);
8821 _convertLatLngs: function (latlngs) {
8822 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8823 len = result.length;
8825 // remove last point if it equals first one
8826 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8832 _setLatLngs: function (latlngs) {
8833 Polyline.prototype._setLatLngs.call(this, latlngs);
8834 if (isFlat(this._latlngs)) {
8835 this._latlngs = [this._latlngs];
8839 _defaultShape: function () {
8840 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8843 _clipPoints: function () {
8844 // polygons need a different clipping algorithm so we redefine that
8846 var bounds = this._renderer._bounds,
8847 w = this.options.weight,
8848 p = new Point(w, w);
8850 // increase clip padding by stroke width to avoid stroke on clip edges
8851 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8854 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8858 if (this.options.noClip) {
8859 this._parts = this._rings;
8863 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8864 clipped = clipPolygon(this._rings[i], bounds, true);
8865 if (clipped.length) {
8866 this._parts.push(clipped);
8871 _updatePath: function () {
8872 this._renderer._updatePoly(this, true);
8875 // Needed by the `Canvas` renderer for interactivity
8876 _containsPoint: function (p) {
8878 part, p1, p2, i, j, k, len, len2;
8880 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8882 // ray casting algorithm for detecting if point is in polygon
8883 for (i = 0, len = this._parts.length; i < len; i++) {
8884 part = this._parts[i];
8886 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8890 if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
8896 // also check if it's on polygon stroke
8897 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8903 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8904 function polygon(latlngs, options) {
8905 return new Polygon(latlngs, options);
8911 * @inherits FeatureGroup
\r
8913 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
\r
8914 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
\r
8919 * L.geoJSON(data, {
\r
8920 * style: function (feature) {
\r
8921 * return {color: feature.properties.color};
\r
8923 * }).bindPopup(function (layer) {
\r
8924 * return layer.feature.properties.description;
\r
8929 var GeoJSON = FeatureGroup.extend({
\r
8932 * @aka GeoJSON options
\r
8934 * @option pointToLayer: Function = *
\r
8935 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
\r
8936 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
\r
8937 * The default is to spawn a default `Marker`:
\r
8939 * function(geoJsonPoint, latlng) {
\r
8940 * return L.marker(latlng);
\r
8944 * @option style: Function = *
\r
8945 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
\r
8946 * called internally when data is added.
\r
8947 * The default value is to not override any defaults:
\r
8949 * function (geoJsonFeature) {
\r
8954 * @option onEachFeature: Function = *
\r
8955 * A `Function` that will be called once for each created `Feature`, after it has
\r
8956 * been created and styled. Useful for attaching events and popups to features.
\r
8957 * The default is to do nothing with the newly created layers:
\r
8959 * function (feature, layer) {}
\r
8962 * @option filter: Function = *
\r
8963 * A `Function` that will be used to decide whether to include a feature or not.
\r
8964 * The default is to include all features:
\r
8966 * function (geoJsonFeature) {
\r
8970 * Note: dynamically changing the `filter` option will have effect only on newly
\r
8971 * added data. It will _not_ re-evaluate already included features.
\r
8973 * @option coordsToLatLng: Function = *
\r
8974 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
\r
8975 * The default is the `coordsToLatLng` static method.
\r
8977 * @option markersInheritOptions: Boolean = false
\r
8978 * Whether default Markers for "Point" type Features inherit from group options.
\r
8981 initialize: function (geojson, options) {
\r
8982 setOptions(this, options);
\r
8984 this._layers = {};
\r
8987 this.addData(geojson);
\r
8991 // @method addData( <GeoJSON> data ): this
\r
8992 // Adds a GeoJSON object to the layer.
\r
8993 addData: function (geojson) {
\r
8994 var features = isArray(geojson) ? geojson : geojson.features,
\r
8998 for (i = 0, len = features.length; i < len; i++) {
\r
8999 // only add this if geometry or geometries are set and not null
\r
9000 feature = features[i];
\r
9001 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
\r
9002 this.addData(feature);
\r
9008 var options = this.options;
\r
9010 if (options.filter && !options.filter(geojson)) { return this; }
\r
9012 var layer = geometryToLayer(geojson, options);
\r
9016 layer.feature = asFeature(geojson);
\r
9018 layer.defaultOptions = layer.options;
\r
9019 this.resetStyle(layer);
\r
9021 if (options.onEachFeature) {
\r
9022 options.onEachFeature(geojson, layer);
\r
9025 return this.addLayer(layer);
\r
9028 // @method resetStyle( <Path> layer? ): this
\r
9029 // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
\r
9030 // If `layer` is omitted, the style of all features in the current layer is reset.
\r
9031 resetStyle: function (layer) {
\r
9032 if (layer === undefined) {
\r
9033 return this.eachLayer(this.resetStyle, this);
\r
9035 // reset any custom styles
\r
9036 layer.options = extend({}, layer.defaultOptions);
\r
9037 this._setLayerStyle(layer, this.options.style);
\r
9041 // @method setStyle( <Function> style ): this
\r
9042 // Changes styles of GeoJSON vector layers with the given style function.
\r
9043 setStyle: function (style) {
\r
9044 return this.eachLayer(function (layer) {
\r
9045 this._setLayerStyle(layer, style);
\r
9049 _setLayerStyle: function (layer, style) {
\r
9050 if (layer.setStyle) {
\r
9051 if (typeof style === 'function') {
\r
9052 style = style(layer.feature);
\r
9054 layer.setStyle(style);
\r
9060 // There are several static functions which can be called without instantiating L.GeoJSON:
\r
9062 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
\r
9063 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
\r
9064 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
\r
9065 // functions if provided as options.
\r
9066 function geometryToLayer(geojson, options) {
\r
9068 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
\r
9069 coords = geometry ? geometry.coordinates : null,
\r
9071 pointToLayer = options && options.pointToLayer,
\r
9072 _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
\r
9073 latlng, latlngs, i, len;
\r
9075 if (!coords && !geometry) {
\r
9079 switch (geometry.type) {
\r
9081 latlng = _coordsToLatLng(coords);
\r
9082 return _pointToLayer(pointToLayer, geojson, latlng, options);
\r
9084 case 'MultiPoint':
\r
9085 for (i = 0, len = coords.length; i < len; i++) {
\r
9086 latlng = _coordsToLatLng(coords[i]);
\r
9087 layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
\r
9089 return new FeatureGroup(layers);
\r
9091 case 'LineString':
\r
9092 case 'MultiLineString':
\r
9093 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
\r
9094 return new Polyline(latlngs, options);
\r
9097 case 'MultiPolygon':
\r
9098 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
\r
9099 return new Polygon(latlngs, options);
\r
9101 case 'GeometryCollection':
\r
9102 for (i = 0, len = geometry.geometries.length; i < len; i++) {
\r
9103 var geoLayer = geometryToLayer({
\r
9104 geometry: geometry.geometries[i],
\r
9106 properties: geojson.properties
\r
9110 layers.push(geoLayer);
\r
9113 return new FeatureGroup(layers);
\r
9115 case 'FeatureCollection':
\r
9116 for (i = 0, len = geometry.features.length; i < len; i++) {
\r
9117 var featureLayer = geometryToLayer(geometry.features[i], options);
\r
9119 if (featureLayer) {
\r
9120 layers.push(featureLayer);
\r
9123 return new FeatureGroup(layers);
\r
9126 throw new Error('Invalid GeoJSON object.');
\r
9130 function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
\r
9131 return pointToLayerFn ?
\r
9132 pointToLayerFn(geojson, latlng) :
\r
9133 new Marker(latlng, options && options.markersInheritOptions && options);
\r
9136 // @function coordsToLatLng(coords: Array): LatLng
\r
9137 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
\r
9138 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
\r
9139 function coordsToLatLng(coords) {
\r
9140 return new LatLng(coords[1], coords[0], coords[2]);
\r
9143 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
\r
9144 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
\r
9145 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
\r
9146 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
\r
9147 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
\r
9150 for (var i = 0, len = coords.length, latlng; i < len; i++) {
\r
9151 latlng = levelsDeep ?
\r
9152 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
\r
9153 (_coordsToLatLng || coordsToLatLng)(coords[i]);
\r
9155 latlngs.push(latlng);
\r
9161 // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
\r
9162 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
\r
9163 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
\r
9164 function latLngToCoords(latlng, precision) {
\r
9165 latlng = toLatLng(latlng);
\r
9166 return latlng.alt !== undefined ?
\r
9167 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
\r
9168 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
\r
9171 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
\r
9172 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
\r
9173 // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
\r
9174 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
\r
9175 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
\r
9178 for (var i = 0, len = latlngs.length; i < len; i++) {
\r
9179 // Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion
\r
9180 coords.push(levelsDeep ?
\r
9181 latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :
\r
9182 latLngToCoords(latlngs[i], precision));
\r
9185 if (!levelsDeep && closed && coords.length > 0) {
\r
9186 coords.push(coords[0].slice());
\r
9192 function getFeature(layer, newGeometry) {
\r
9193 return layer.feature ?
\r
9194 extend({}, layer.feature, {geometry: newGeometry}) :
\r
9195 asFeature(newGeometry);
\r
9198 // @function asFeature(geojson: Object): Object
\r
9199 // Normalize GeoJSON geometries/features into GeoJSON features.
\r
9200 function asFeature(geojson) {
\r
9201 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
\r
9212 var PointToGeoJSON = {
\r
9213 toGeoJSON: function (precision) {
\r
9214 return getFeature(this, {
\r
9216 coordinates: latLngToCoords(this.getLatLng(), precision)
\r
9221 // @namespace Marker
\r
9222 // @section Other methods
\r
9223 // @method toGeoJSON(precision?: Number|false): Object
\r
9224 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9225 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
\r
9226 Marker.include(PointToGeoJSON);
\r
9228 // @namespace CircleMarker
\r
9229 // @method toGeoJSON(precision?: Number|false): Object
\r
9230 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9231 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
\r
9232 Circle.include(PointToGeoJSON);
\r
9233 CircleMarker.include(PointToGeoJSON);
\r
9236 // @namespace Polyline
\r
9237 // @method toGeoJSON(precision?: Number|false): Object
\r
9238 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9239 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
\r
9240 Polyline.include({
\r
9241 toGeoJSON: function (precision) {
\r
9242 var multi = !isFlat(this._latlngs);
\r
9244 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
\r
9246 return getFeature(this, {
\r
9247 type: (multi ? 'Multi' : '') + 'LineString',
\r
9248 coordinates: coords
\r
9253 // @namespace Polygon
\r
9254 // @method toGeoJSON(precision?: Number|false): Object
\r
9255 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9256 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
\r
9258 toGeoJSON: function (precision) {
\r
9259 var holes = !isFlat(this._latlngs),
\r
9260 multi = holes && !isFlat(this._latlngs[0]);
\r
9262 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
\r
9265 coords = [coords];
\r
9268 return getFeature(this, {
\r
9269 type: (multi ? 'Multi' : '') + 'Polygon',
\r
9270 coordinates: coords
\r
9276 // @namespace LayerGroup
\r
9277 LayerGroup.include({
\r
9278 toMultiPoint: function (precision) {
\r
9281 this.eachLayer(function (layer) {
\r
9282 coords.push(layer.toGeoJSON(precision).geometry.coordinates);
\r
9285 return getFeature(this, {
\r
9286 type: 'MultiPoint',
\r
9287 coordinates: coords
\r
9291 // @method toGeoJSON(precision?: Number|false): Object
\r
9292 // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
\r
9293 // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
\r
9294 toGeoJSON: function (precision) {
\r
9296 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
\r
9298 if (type === 'MultiPoint') {
\r
9299 return this.toMultiPoint(precision);
\r
9302 var isGeometryCollection = type === 'GeometryCollection',
\r
9305 this.eachLayer(function (layer) {
\r
9306 if (layer.toGeoJSON) {
\r
9307 var json = layer.toGeoJSON(precision);
\r
9308 if (isGeometryCollection) {
\r
9309 jsons.push(json.geometry);
\r
9311 var feature = asFeature(json);
\r
9312 // Squash nested feature collections
\r
9313 if (feature.type === 'FeatureCollection') {
\r
9314 jsons.push.apply(jsons, feature.features);
\r
9316 jsons.push(feature);
\r
9322 if (isGeometryCollection) {
\r
9323 return getFeature(this, {
\r
9324 geometries: jsons,
\r
9325 type: 'GeometryCollection'
\r
9330 type: 'FeatureCollection',
\r
9336 // @namespace GeoJSON
\r
9337 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
\r
9338 // Creates a GeoJSON layer. Optionally accepts an object in
\r
9339 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
\r
9340 // (you can alternatively add it later with `addData` method) and an `options` object.
\r
9341 function geoJSON(geojson, options) {
\r
9342 return new GeoJSON(geojson, options);
\r
9345 // Backward compatibility.
\r
9346 var geoJson = geoJSON;
9349 * @class ImageOverlay
\r
9350 * @aka L.ImageOverlay
\r
9351 * @inherits Interactive layer
\r
9353 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
\r
9358 * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
\r
9359 * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
\r
9360 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
\r
9364 var ImageOverlay = Layer.extend({
\r
9367 // @aka ImageOverlay options
\r
9369 // @option opacity: Number = 1.0
\r
9370 // The opacity of the image overlay.
\r
9373 // @option alt: String = ''
\r
9374 // Text for the `alt` attribute of the image (useful for accessibility).
\r
9377 // @option interactive: Boolean = false
\r
9378 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
\r
9379 interactive: false,
\r
9381 // @option crossOrigin: Boolean|String = false
\r
9382 // Whether the crossOrigin attribute will be added to the image.
\r
9383 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
\r
9384 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
\r
9385 crossOrigin: false,
\r
9387 // @option errorOverlayUrl: String = ''
\r
9388 // URL to the overlay image to show in place of the overlay that failed to load.
\r
9389 errorOverlayUrl: '',
\r
9391 // @option zIndex: Number = 1
\r
9392 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
\r
9395 // @option className: String = ''
\r
9396 // A custom class name to assign to the image. Empty by default.
\r
9400 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
\r
9402 this._bounds = toLatLngBounds(bounds);
\r
9404 setOptions(this, options);
\r
9407 onAdd: function () {
\r
9408 if (!this._image) {
\r
9409 this._initImage();
\r
9411 if (this.options.opacity < 1) {
\r
9412 this._updateOpacity();
\r
9416 if (this.options.interactive) {
\r
9417 addClass(this._image, 'leaflet-interactive');
\r
9418 this.addInteractiveTarget(this._image);
\r
9421 this.getPane().appendChild(this._image);
\r
9425 onRemove: function () {
\r
9426 remove(this._image);
\r
9427 if (this.options.interactive) {
\r
9428 this.removeInteractiveTarget(this._image);
\r
9432 // @method setOpacity(opacity: Number): this
\r
9433 // Sets the opacity of the overlay.
\r
9434 setOpacity: function (opacity) {
\r
9435 this.options.opacity = opacity;
\r
9437 if (this._image) {
\r
9438 this._updateOpacity();
\r
9443 setStyle: function (styleOpts) {
\r
9444 if (styleOpts.opacity) {
\r
9445 this.setOpacity(styleOpts.opacity);
\r
9450 // @method bringToFront(): this
\r
9451 // Brings the layer to the top of all overlays.
\r
9452 bringToFront: function () {
\r
9454 toFront(this._image);
\r
9459 // @method bringToBack(): this
\r
9460 // Brings the layer to the bottom of all overlays.
\r
9461 bringToBack: function () {
\r
9463 toBack(this._image);
\r
9468 // @method setUrl(url: String): this
\r
9469 // Changes the URL of the image.
\r
9470 setUrl: function (url) {
\r
9473 if (this._image) {
\r
9474 this._image.src = url;
\r
9479 // @method setBounds(bounds: LatLngBounds): this
\r
9480 // Update the bounds that this ImageOverlay covers
\r
9481 setBounds: function (bounds) {
\r
9482 this._bounds = toLatLngBounds(bounds);
\r
9490 getEvents: function () {
\r
9492 zoom: this._reset,
\r
9493 viewreset: this._reset
\r
9496 if (this._zoomAnimated) {
\r
9497 events.zoomanim = this._animateZoom;
\r
9503 // @method setZIndex(value: Number): this
\r
9504 // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
\r
9505 setZIndex: function (value) {
\r
9506 this.options.zIndex = value;
\r
9507 this._updateZIndex();
\r
9511 // @method getBounds(): LatLngBounds
\r
9512 // Get the bounds that this ImageOverlay covers
\r
9513 getBounds: function () {
\r
9514 return this._bounds;
\r
9517 // @method getElement(): HTMLElement
\r
9518 // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
\r
9519 // used by this overlay.
\r
9520 getElement: function () {
\r
9521 return this._image;
\r
9524 _initImage: function () {
\r
9525 var wasElementSupplied = this._url.tagName === 'IMG';
\r
9526 var img = this._image = wasElementSupplied ? this._url : create$1('img');
\r
9528 addClass(img, 'leaflet-image-layer');
\r
9529 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
\r
9530 if (this.options.className) { addClass(img, this.options.className); }
\r
9532 img.onselectstart = falseFn;
\r
9533 img.onmousemove = falseFn;
\r
9535 // @event load: Event
\r
9536 // Fired when the ImageOverlay layer has loaded its image
\r
9537 img.onload = bind(this.fire, this, 'load');
\r
9538 img.onerror = bind(this._overlayOnError, this, 'error');
\r
9540 if (this.options.crossOrigin || this.options.crossOrigin === '') {
\r
9541 img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
\r
9544 if (this.options.zIndex) {
\r
9545 this._updateZIndex();
\r
9548 if (wasElementSupplied) {
\r
9549 this._url = img.src;
\r
9553 img.src = this._url;
\r
9554 img.alt = this.options.alt;
\r
9557 _animateZoom: function (e) {
\r
9558 var scale = this._map.getZoomScale(e.zoom),
\r
9559 offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
\r
9561 setTransform(this._image, offset, scale);
\r
9564 _reset: function () {
\r
9565 var image = this._image,
\r
9566 bounds = new Bounds(
\r
9567 this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
\r
9568 this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
\r
9569 size = bounds.getSize();
\r
9571 setPosition(image, bounds.min);
\r
9573 image.style.width = size.x + 'px';
\r
9574 image.style.height = size.y + 'px';
\r
9577 _updateOpacity: function () {
\r
9578 setOpacity(this._image, this.options.opacity);
\r
9581 _updateZIndex: function () {
\r
9582 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
\r
9583 this._image.style.zIndex = this.options.zIndex;
\r
9587 _overlayOnError: function () {
\r
9588 // @event error: Event
\r
9589 // Fired when the ImageOverlay layer fails to load its image
\r
9590 this.fire('error');
\r
9592 var errorUrl = this.options.errorOverlayUrl;
\r
9593 if (errorUrl && this._url !== errorUrl) {
\r
9594 this._url = errorUrl;
\r
9595 this._image.src = errorUrl;
\r
9599 // @method getCenter(): LatLng
\r
9600 // Returns the center of the ImageOverlay.
\r
9601 getCenter: function () {
\r
9602 return this._bounds.getCenter();
\r
9606 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
\r
9607 // Instantiates an image overlay object given the URL of the image and the
\r
9608 // geographical bounds it is tied to.
\r
9609 var imageOverlay = function (url, bounds, options) {
\r
9610 return new ImageOverlay(url, bounds, options);
\r
9614 * @class VideoOverlay
\r
9615 * @aka L.VideoOverlay
\r
9616 * @inherits ImageOverlay
\r
9618 * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
\r
9620 * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
\r
9626 * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
\r
9627 * videoBounds = [[ 32, -130], [ 13, -100]];
\r
9628 * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
\r
9632 var VideoOverlay = ImageOverlay.extend({
\r
9635 // @aka VideoOverlay options
\r
9637 // @option autoplay: Boolean = true
\r
9638 // Whether the video starts playing automatically when loaded.
\r
9639 // On some browsers autoplay will only work with `muted: true`
\r
9642 // @option loop: Boolean = true
\r
9643 // Whether the video will loop back to the beginning when played.
\r
9646 // @option keepAspectRatio: Boolean = true
\r
9647 // Whether the video will save aspect ratio after the projection.
\r
9648 // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
\r
9649 keepAspectRatio: true,
\r
9651 // @option muted: Boolean = false
\r
9652 // Whether the video starts on mute when loaded.
\r
9655 // @option playsInline: Boolean = true
\r
9656 // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
\r
9660 _initImage: function () {
\r
9661 var wasElementSupplied = this._url.tagName === 'VIDEO';
\r
9662 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
\r
9664 addClass(vid, 'leaflet-image-layer');
\r
9665 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
\r
9666 if (this.options.className) { addClass(vid, this.options.className); }
\r
9668 vid.onselectstart = falseFn;
\r
9669 vid.onmousemove = falseFn;
\r
9671 // @event load: Event
\r
9672 // Fired when the video has finished loading the first frame
\r
9673 vid.onloadeddata = bind(this.fire, this, 'load');
\r
9675 if (wasElementSupplied) {
\r
9676 var sourceElements = vid.getElementsByTagName('source');
\r
9678 for (var j = 0; j < sourceElements.length; j++) {
\r
9679 sources.push(sourceElements[j].src);
\r
9682 this._url = (sourceElements.length > 0) ? sources : [vid.src];
\r
9686 if (!isArray(this._url)) { this._url = [this._url]; }
\r
9688 if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
\r
9689 vid.style['objectFit'] = 'fill';
\r
9691 vid.autoplay = !!this.options.autoplay;
\r
9692 vid.loop = !!this.options.loop;
\r
9693 vid.muted = !!this.options.muted;
\r
9694 vid.playsInline = !!this.options.playsInline;
\r
9695 for (var i = 0; i < this._url.length; i++) {
\r
9696 var source = create$1('source');
\r
9697 source.src = this._url[i];
\r
9698 vid.appendChild(source);
\r
9702 // @method getElement(): HTMLVideoElement
\r
9703 // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
\r
9704 // used by this overlay.
\r
9708 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
\r
9709 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
\r
9710 // geographical bounds it is tied to.
\r
9712 function videoOverlay(video, bounds, options) {
\r
9713 return new VideoOverlay(video, bounds, options);
\r
9719 * @inherits ImageOverlay
9721 * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9723 * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9728 * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9729 * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9730 * svgElement.setAttribute('viewBox', "0 0 200 200");
9731 * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
9732 * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9733 * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9737 var SVGOverlay = ImageOverlay.extend({
9738 _initImage: function () {
9739 var el = this._image = this._url;
9741 addClass(el, 'leaflet-image-layer');
9742 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9743 if (this.options.className) { addClass(el, this.options.className); }
9745 el.onselectstart = falseFn;
9746 el.onmousemove = falseFn;
9749 // @method getElement(): SVGElement
9750 // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9751 // used by this overlay.
9755 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9756 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9757 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9759 function svgOverlay(el, bounds, options) {
9760 return new SVGOverlay(el, bounds, options);
9764 * @class DivOverlay
\r
9765 * @inherits Interactive layer
\r
9766 * @aka L.DivOverlay
\r
9767 * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
\r
9770 // @namespace DivOverlay
\r
9771 var DivOverlay = Layer.extend({
\r
9774 // @aka DivOverlay options
\r
9776 // @option interactive: Boolean = false
\r
9777 // If true, the popup/tooltip will listen to the mouse events.
\r
9778 interactive: false,
\r
9780 // @option offset: Point = Point(0, 0)
\r
9781 // The offset of the overlay position.
\r
9784 // @option className: String = ''
\r
9785 // A custom CSS class name to assign to the overlay.
\r
9788 // @option pane: String = undefined
\r
9789 // `Map pane` where the overlay will be added.
\r
9792 // @option content: String|HTMLElement|Function = ''
\r
9793 // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be
\r
9794 // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.
\r
9798 initialize: function (options, source) {
\r
9799 if (options && (options instanceof LatLng || isArray(options))) {
\r
9800 this._latlng = toLatLng(options);
\r
9801 setOptions(this, source);
\r
9803 setOptions(this, options);
\r
9804 this._source = source;
\r
9806 if (this.options.content) {
\r
9807 this._content = this.options.content;
\r
9811 // @method openOn(map: Map): this
\r
9812 // Adds the overlay to the map.
\r
9813 // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
\r
9814 openOn: function (map) {
\r
9815 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
\r
9816 if (!map.hasLayer(this)) {
\r
9817 map.addLayer(this);
\r
9822 // @method close(): this
\r
9823 // Closes the overlay.
\r
9824 // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
\r
9825 // and `layer.closePopup()`/`.closeTooltip()`.
\r
9826 close: function () {
\r
9828 this._map.removeLayer(this);
\r
9833 // @method toggle(layer?: Layer): this
\r
9834 // Opens or closes the overlay bound to layer depending on its current state.
\r
9835 // Argument may be omitted only for overlay bound to layer.
\r
9836 // Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
\r
9837 toggle: function (layer) {
\r
9841 if (arguments.length) {
\r
9842 this._source = layer;
\r
9844 layer = this._source;
\r
9846 this._prepareOpen();
\r
9848 // open the overlay on the map
\r
9849 this.openOn(layer._map);
\r
9854 onAdd: function (map) {
\r
9855 this._zoomAnimated = map._zoomAnimated;
\r
9857 if (!this._container) {
\r
9858 this._initLayout();
\r
9861 if (map._fadeAnimated) {
\r
9862 setOpacity(this._container, 0);
\r
9865 clearTimeout(this._removeTimeout);
\r
9866 this.getPane().appendChild(this._container);
\r
9869 if (map._fadeAnimated) {
\r
9870 setOpacity(this._container, 1);
\r
9873 this.bringToFront();
\r
9875 if (this.options.interactive) {
\r
9876 addClass(this._container, 'leaflet-interactive');
\r
9877 this.addInteractiveTarget(this._container);
\r
9881 onRemove: function (map) {
\r
9882 if (map._fadeAnimated) {
\r
9883 setOpacity(this._container, 0);
\r
9884 this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
\r
9886 remove(this._container);
\r
9889 if (this.options.interactive) {
\r
9890 removeClass(this._container, 'leaflet-interactive');
\r
9891 this.removeInteractiveTarget(this._container);
\r
9895 // @namespace DivOverlay
\r
9896 // @method getLatLng: LatLng
\r
9897 // Returns the geographical point of the overlay.
\r
9898 getLatLng: function () {
\r
9899 return this._latlng;
\r
9902 // @method setLatLng(latlng: LatLng): this
\r
9903 // Sets the geographical point where the overlay will open.
\r
9904 setLatLng: function (latlng) {
\r
9905 this._latlng = toLatLng(latlng);
\r
9907 this._updatePosition();
\r
9908 this._adjustPan();
\r
9913 // @method getContent: String|HTMLElement
\r
9914 // Returns the content of the overlay.
\r
9915 getContent: function () {
\r
9916 return this._content;
\r
9919 // @method setContent(htmlContent: String|HTMLElement|Function): this
\r
9920 // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
\r
9921 // The function should return a `String` or `HTMLElement` to be used in the overlay.
\r
9922 setContent: function (content) {
\r
9923 this._content = content;
\r
9928 // @method getElement: String|HTMLElement
\r
9929 // Returns the HTML container of the overlay.
\r
9930 getElement: function () {
\r
9931 return this._container;
\r
9934 // @method update: null
\r
9935 // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
\r
9936 update: function () {
\r
9937 if (!this._map) { return; }
\r
9939 this._container.style.visibility = 'hidden';
\r
9941 this._updateContent();
\r
9942 this._updateLayout();
\r
9943 this._updatePosition();
\r
9945 this._container.style.visibility = '';
\r
9947 this._adjustPan();
\r
9950 getEvents: function () {
\r
9952 zoom: this._updatePosition,
\r
9953 viewreset: this._updatePosition
\r
9956 if (this._zoomAnimated) {
\r
9957 events.zoomanim = this._animateZoom;
\r
9962 // @method isOpen: Boolean
\r
9963 // Returns `true` when the overlay is visible on the map.
\r
9964 isOpen: function () {
\r
9965 return !!this._map && this._map.hasLayer(this);
\r
9968 // @method bringToFront: this
\r
9969 // Brings this overlay in front of other overlays (in the same map pane).
\r
9970 bringToFront: function () {
\r
9972 toFront(this._container);
\r
9977 // @method bringToBack: this
\r
9978 // Brings this overlay to the back of other overlays (in the same map pane).
\r
9979 bringToBack: function () {
\r
9981 toBack(this._container);
\r
9986 // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
\r
9987 _prepareOpen: function (latlng) {
\r
9988 var source = this._source;
\r
9989 if (!source._map) { return false; }
\r
9991 if (source instanceof FeatureGroup) {
\r
9993 var layers = this._source._layers;
\r
9994 for (var id in layers) {
\r
9995 if (layers[id]._map) {
\r
9996 source = layers[id];
\r
10000 if (!source) { return false; } // Unable to get source layer.
\r
10002 // set overlay source to this layer
\r
10003 this._source = source;
\r
10007 if (source.getCenter) {
\r
10008 latlng = source.getCenter();
\r
10009 } else if (source.getLatLng) {
\r
10010 latlng = source.getLatLng();
\r
10011 } else if (source.getBounds) {
\r
10012 latlng = source.getBounds().getCenter();
\r
10014 throw new Error('Unable to get source layer LatLng.');
\r
10017 this.setLatLng(latlng);
\r
10020 // update the overlay (content, layout, etc...)
\r
10027 _updateContent: function () {
\r
10028 if (!this._content) { return; }
\r
10030 var node = this._contentNode;
\r
10031 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
\r
10033 if (typeof content === 'string') {
\r
10034 node.innerHTML = content;
\r
10036 while (node.hasChildNodes()) {
\r
10037 node.removeChild(node.firstChild);
\r
10039 node.appendChild(content);
\r
10042 // @namespace DivOverlay
\r
10043 // @section DivOverlay events
\r
10044 // @event contentupdate: Event
\r
10045 // Fired when the content of the overlay is updated
\r
10046 this.fire('contentupdate');
\r
10049 _updatePosition: function () {
\r
10050 if (!this._map) { return; }
\r
10052 var pos = this._map.latLngToLayerPoint(this._latlng),
\r
10053 offset = toPoint(this.options.offset),
\r
10054 anchor = this._getAnchor();
\r
10056 if (this._zoomAnimated) {
\r
10057 setPosition(this._container, pos.add(anchor));
\r
10059 offset = offset.add(pos).add(anchor);
\r
10062 var bottom = this._containerBottom = -offset.y,
\r
10063 left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
\r
10065 // bottom position the overlay in case the height of the overlay changes (images loading etc)
\r
10066 this._container.style.bottom = bottom + 'px';
\r
10067 this._container.style.left = left + 'px';
\r
10070 _getAnchor: function () {
\r
10077 _initOverlay: function (OverlayClass, content, latlng, options) {
\r
10078 var overlay = content;
\r
10079 if (!(overlay instanceof OverlayClass)) {
\r
10080 overlay = new OverlayClass(options).setContent(content);
\r
10083 overlay.setLatLng(latlng);
\r
10091 _initOverlay: function (OverlayClass, old, content, options) {
\r
10092 var overlay = content;
\r
10093 if (overlay instanceof OverlayClass) {
\r
10094 setOptions(overlay, options);
\r
10095 overlay._source = this;
\r
10097 overlay = (old && !options) ? old : new OverlayClass(options, this);
\r
10098 overlay.setContent(content);
\r
10106 * @inherits DivOverlay
\r
10108 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
\r
10109 * open popups while making sure that only one popup is open at one time
\r
10110 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
\r
10114 * If you want to just bind a popup to marker click and then open it, it's really easy:
\r
10117 * marker.bindPopup(popupContent).openPopup();
\r
10119 * Path overlays like polylines also have a `bindPopup` method.
\r
10121 * A popup can be also standalone:
\r
10124 * var popup = L.popup()
\r
10125 * .setLatLng(latlng)
\r
10126 * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
\r
10131 * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')
\r
10137 // @namespace Popup
\r
10138 var Popup = DivOverlay.extend({
\r
10141 // @aka Popup options
\r
10143 // @option pane: String = 'popupPane'
\r
10144 // `Map pane` where the popup will be added.
\r
10145 pane: 'popupPane',
\r
10147 // @option offset: Point = Point(0, 7)
\r
10148 // The offset of the popup position.
\r
10151 // @option maxWidth: Number = 300
\r
10152 // Max width of the popup, in pixels.
\r
10155 // @option minWidth: Number = 50
\r
10156 // Min width of the popup, in pixels.
\r
10159 // @option maxHeight: Number = null
\r
10160 // If set, creates a scrollable container of the given height
\r
10161 // inside a popup if its content exceeds it.
\r
10162 // The scrollable container can be styled using the
\r
10163 // `leaflet-popup-scrolled` CSS class selector.
\r
10166 // @option autoPan: Boolean = true
\r
10167 // Set it to `false` if you don't want the map to do panning animation
\r
10168 // to fit the opened popup.
\r
10171 // @option autoPanPaddingTopLeft: Point = null
\r
10172 // The margin between the popup and the top left corner of the map
\r
10173 // view after autopanning was performed.
\r
10174 autoPanPaddingTopLeft: null,
\r
10176 // @option autoPanPaddingBottomRight: Point = null
\r
10177 // The margin between the popup and the bottom right corner of the map
\r
10178 // view after autopanning was performed.
\r
10179 autoPanPaddingBottomRight: null,
\r
10181 // @option autoPanPadding: Point = Point(5, 5)
\r
10182 // Equivalent of setting both top left and bottom right autopan padding to the same value.
\r
10183 autoPanPadding: [5, 5],
\r
10185 // @option keepInView: Boolean = false
\r
10186 // Set it to `true` if you want to prevent users from panning the popup
\r
10187 // off of the screen while it is open.
\r
10188 keepInView: false,
\r
10190 // @option closeButton: Boolean = true
\r
10191 // Controls the presence of a close button in the popup.
\r
10192 closeButton: true,
\r
10194 // @option autoClose: Boolean = true
\r
10195 // Set it to `false` if you want to override the default behavior of
\r
10196 // the popup closing when another popup is opened.
\r
10199 // @option closeOnEscapeKey: Boolean = true
\r
10200 // Set it to `false` if you want to override the default behavior of
\r
10201 // the ESC key for closing of the popup.
\r
10202 closeOnEscapeKey: true,
\r
10204 // @option closeOnClick: Boolean = *
\r
10205 // Set it if you want to override the default behavior of the popup closing when user clicks
\r
10206 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
\r
10208 // @option className: String = ''
\r
10209 // A custom CSS class name to assign to the popup.
\r
10213 // @namespace Popup
\r
10214 // @method openOn(map: Map): this
\r
10215 // Alternative to `map.openPopup(popup)`.
\r
10216 // Adds the popup to the map and closes the previous one.
\r
10217 openOn: function (map) {
\r
10218 map = arguments.length ? map : this._source._map; // experimental, not the part of public api
\r
10220 if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
\r
10221 map.removeLayer(map._popup);
\r
10223 map._popup = this;
\r
10225 return DivOverlay.prototype.openOn.call(this, map);
\r
10228 onAdd: function (map) {
\r
10229 DivOverlay.prototype.onAdd.call(this, map);
\r
10231 // @namespace Map
\r
10232 // @section Popup events
\r
10233 // @event popupopen: PopupEvent
\r
10234 // Fired when a popup is opened in the map
\r
10235 map.fire('popupopen', {popup: this});
\r
10237 if (this._source) {
\r
10238 // @namespace Layer
\r
10239 // @section Popup events
\r
10240 // @event popupopen: PopupEvent
\r
10241 // Fired when a popup bound to this layer is opened
\r
10242 this._source.fire('popupopen', {popup: this}, true);
\r
10243 // For non-path layers, we toggle the popup when clicking
\r
10244 // again the layer, so prevent the map to reopen it.
\r
10245 if (!(this._source instanceof Path)) {
\r
10246 this._source.on('preclick', stopPropagation);
\r
10251 onRemove: function (map) {
\r
10252 DivOverlay.prototype.onRemove.call(this, map);
\r
10254 // @namespace Map
\r
10255 // @section Popup events
\r
10256 // @event popupclose: PopupEvent
\r
10257 // Fired when a popup in the map is closed
\r
10258 map.fire('popupclose', {popup: this});
\r
10260 if (this._source) {
\r
10261 // @namespace Layer
\r
10262 // @section Popup events
\r
10263 // @event popupclose: PopupEvent
\r
10264 // Fired when a popup bound to this layer is closed
\r
10265 this._source.fire('popupclose', {popup: this}, true);
\r
10266 if (!(this._source instanceof Path)) {
\r
10267 this._source.off('preclick', stopPropagation);
\r
10272 getEvents: function () {
\r
10273 var events = DivOverlay.prototype.getEvents.call(this);
\r
10275 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
\r
10276 events.preclick = this.close;
\r
10279 if (this.options.keepInView) {
\r
10280 events.moveend = this._adjustPan;
\r
10286 _initLayout: function () {
\r
10287 var prefix = 'leaflet-popup',
\r
10288 container = this._container = create$1('div',
\r
10289 prefix + ' ' + (this.options.className || '') +
\r
10290 ' leaflet-zoom-animated');
\r
10292 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
\r
10293 this._contentNode = create$1('div', prefix + '-content', wrapper);
\r
10295 disableClickPropagation(container);
\r
10296 disableScrollPropagation(this._contentNode);
\r
10297 on(container, 'contextmenu', stopPropagation);
\r
10299 this._tipContainer = create$1('div', prefix + '-tip-container', container);
\r
10300 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
\r
10302 if (this.options.closeButton) {
\r
10303 var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
\r
10304 closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
\r
10305 closeButton.setAttribute('aria-label', 'Close popup');
\r
10306 closeButton.href = '#close';
\r
10307 closeButton.innerHTML = '<span aria-hidden="true">×</span>';
\r
10309 on(closeButton, 'click', function (ev) {
\r
10310 preventDefault(ev);
\r
10316 _updateLayout: function () {
\r
10317 var container = this._contentNode,
\r
10318 style = container.style;
\r
10320 style.width = '';
\r
10321 style.whiteSpace = 'nowrap';
\r
10323 var width = container.offsetWidth;
\r
10324 width = Math.min(width, this.options.maxWidth);
\r
10325 width = Math.max(width, this.options.minWidth);
\r
10327 style.width = (width + 1) + 'px';
\r
10328 style.whiteSpace = '';
\r
10330 style.height = '';
\r
10332 var height = container.offsetHeight,
\r
10333 maxHeight = this.options.maxHeight,
\r
10334 scrolledClass = 'leaflet-popup-scrolled';
\r
10336 if (maxHeight && height > maxHeight) {
\r
10337 style.height = maxHeight + 'px';
\r
10338 addClass(container, scrolledClass);
\r
10340 removeClass(container, scrolledClass);
\r
10343 this._containerWidth = this._container.offsetWidth;
\r
10346 _animateZoom: function (e) {
\r
10347 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
\r
10348 anchor = this._getAnchor();
\r
10349 setPosition(this._container, pos.add(anchor));
\r
10352 _adjustPan: function () {
\r
10353 if (!this.options.autoPan) { return; }
\r
10354 if (this._map._panAnim) { this._map._panAnim.stop(); }
\r
10356 // We can endlessly recurse if keepInView is set and the view resets.
\r
10357 // Let's guard against that by exiting early if we're responding to our own autopan.
\r
10358 if (this._autopanning) {
\r
10359 this._autopanning = false;
\r
10363 var map = this._map,
\r
10364 marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
\r
10365 containerHeight = this._container.offsetHeight + marginBottom,
\r
10366 containerWidth = this._containerWidth,
\r
10367 layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
\r
10369 layerPos._add(getPosition(this._container));
\r
10371 var containerPos = map.layerPointToContainerPoint(layerPos),
\r
10372 padding = toPoint(this.options.autoPanPadding),
\r
10373 paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
\r
10374 paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
\r
10375 size = map.getSize(),
\r
10379 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
\r
10380 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
\r
10382 if (containerPos.x - dx - paddingTL.x < 0) { // left
\r
10383 dx = containerPos.x - paddingTL.x;
\r
10385 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
\r
10386 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
\r
10388 if (containerPos.y - dy - paddingTL.y < 0) { // top
\r
10389 dy = containerPos.y - paddingTL.y;
\r
10392 // @namespace Map
\r
10393 // @section Popup events
\r
10394 // @event autopanstart: Event
\r
10395 // Fired when the map starts autopanning when opening a popup.
\r
10397 // Track that we're autopanning, as this function will be re-ran on moveend
\r
10398 if (this.options.keepInView) {
\r
10399 this._autopanning = true;
\r
10403 .fire('autopanstart')
\r
10404 .panBy([dx, dy]);
\r
10408 _getAnchor: function () {
\r
10409 // Where should we anchor the popup on the source layer?
\r
10410 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
\r
10415 // @namespace Popup
\r
10416 // @factory L.popup(options?: Popup options, source?: Layer)
\r
10417 // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
\r
10419 // @factory L.popup(latlng: LatLng, options?: Popup options)
\r
10420 // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.
\r
10421 var popup = function (options, source) {
\r
10422 return new Popup(options, source);
\r
10426 /* @namespace Map
\r
10427 * @section Interaction Options
\r
10428 * @option closePopupOnClick: Boolean = true
\r
10429 * Set it to `false` if you don't want popups to close when user clicks the map.
\r
10431 Map.mergeOptions({
\r
10432 closePopupOnClick: true
\r
10436 // @namespace Map
\r
10437 // @section Methods for Layers and Controls
\r
10439 // @method openPopup(popup: Popup): this
\r
10440 // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
\r
10442 // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
\r
10443 // Creates a popup with the specified content and options and opens it in the given point on a map.
\r
10444 openPopup: function (popup, latlng, options) {
\r
10445 this._initOverlay(Popup, popup, latlng, options)
\r
10451 // @method closePopup(popup?: Popup): this
\r
10452 // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
\r
10453 closePopup: function (popup) {
\r
10454 popup = arguments.length ? popup : this._popup;
\r
10463 * @namespace Layer
\r
10464 * @section Popup methods example
\r
10466 * All layers share a set of methods convenient for binding popups to it.
\r
10469 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
\r
10470 * layer.openPopup();
\r
10471 * layer.closePopup();
\r
10474 * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
\r
10477 // @section Popup methods
\r
10480 // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
\r
10481 // Binds a popup to the layer with the passed `content` and sets up the
\r
10482 // necessary event listeners. If a `Function` is passed it will receive
\r
10483 // the layer as the first argument and should return a `String` or `HTMLElement`.
\r
10484 bindPopup: function (content, options) {
\r
10485 this._popup = this._initOverlay(Popup, this._popup, content, options);
\r
10486 if (!this._popupHandlersAdded) {
\r
10488 click: this._openPopup,
\r
10489 keypress: this._onKeyPress,
\r
10490 remove: this.closePopup,
\r
10491 move: this._movePopup
\r
10493 this._popupHandlersAdded = true;
\r
10499 // @method unbindPopup(): this
\r
10500 // Removes the popup previously bound with `bindPopup`.
\r
10501 unbindPopup: function () {
\r
10502 if (this._popup) {
\r
10504 click: this._openPopup,
\r
10505 keypress: this._onKeyPress,
\r
10506 remove: this.closePopup,
\r
10507 move: this._movePopup
\r
10509 this._popupHandlersAdded = false;
\r
10510 this._popup = null;
\r
10515 // @method openPopup(latlng?: LatLng): this
\r
10516 // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
\r
10517 openPopup: function (latlng) {
\r
10518 if (this._popup) {
\r
10519 if (!(this instanceof FeatureGroup)) {
\r
10520 this._popup._source = this;
\r
10522 if (this._popup._prepareOpen(latlng || this._latlng)) {
\r
10523 // open the popup on the map
\r
10524 this._popup.openOn(this._map);
\r
10530 // @method closePopup(): this
\r
10531 // Closes the popup bound to this layer if it is open.
\r
10532 closePopup: function () {
\r
10533 if (this._popup) {
\r
10534 this._popup.close();
\r
10539 // @method togglePopup(): this
\r
10540 // Opens or closes the popup bound to this layer depending on its current state.
\r
10541 togglePopup: function () {
\r
10542 if (this._popup) {
\r
10543 this._popup.toggle(this);
\r
10548 // @method isPopupOpen(): boolean
\r
10549 // Returns `true` if the popup bound to this layer is currently open.
\r
10550 isPopupOpen: function () {
\r
10551 return (this._popup ? this._popup.isOpen() : false);
\r
10554 // @method setPopupContent(content: String|HTMLElement|Popup): this
\r
10555 // Sets the content of the popup bound to this layer.
\r
10556 setPopupContent: function (content) {
\r
10557 if (this._popup) {
\r
10558 this._popup.setContent(content);
\r
10563 // @method getPopup(): Popup
\r
10564 // Returns the popup bound to this layer.
\r
10565 getPopup: function () {
\r
10566 return this._popup;
\r
10569 _openPopup: function (e) {
\r
10570 if (!this._popup || !this._map) {
\r
10573 // prevent map click
\r
10576 var target = e.layer || e.target;
\r
10577 if (this._popup._source === target && !(target instanceof Path)) {
\r
10578 // treat it like a marker and figure out
\r
10579 // if we should toggle it open/closed
\r
10580 if (this._map.hasLayer(this._popup)) {
\r
10581 this.closePopup();
\r
10583 this.openPopup(e.latlng);
\r
10587 this._popup._source = target;
\r
10588 this.openPopup(e.latlng);
\r
10591 _movePopup: function (e) {
\r
10592 this._popup.setLatLng(e.latlng);
\r
10595 _onKeyPress: function (e) {
\r
10596 if (e.originalEvent.keyCode === 13) {
\r
10597 this._openPopup(e);
\r
10604 * @inherits DivOverlay
10606 * Used to display small texts on top of map layers.
10609 * If you want to just bind a tooltip to marker:
10612 * marker.bindTooltip("my tooltip text").openTooltip();
10614 * Path overlays like polylines also have a `bindTooltip` method.
10616 * A tooltip can be also standalone:
10619 * var tooltip = L.tooltip()
10620 * .setLatLng(latlng)
10621 * .setContent('Hello world!<br />This is a nice tooltip.')
10626 * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
10631 * Note about tooltip offset. Leaflet takes two options in consideration
10632 * for computing tooltip offsetting:
10633 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10634 * Add a positive x offset to move the tooltip to the right, and a positive y offset to
10635 * move it to the bottom. Negatives will move to the left and top.
10636 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10637 * should adapt this value if you use a custom icon.
10641 // @namespace Tooltip
10642 var Tooltip = DivOverlay.extend({
10645 // @aka Tooltip options
10647 // @option pane: String = 'tooltipPane'
10648 // `Map pane` where the tooltip will be added.
10649 pane: 'tooltipPane',
10651 // @option offset: Point = Point(0, 0)
10652 // Optional offset of the tooltip position.
10655 // @option direction: String = 'auto'
10656 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10657 // `top`, `bottom`, `center`, `auto`.
10658 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10659 // position on the map.
10662 // @option permanent: Boolean = false
10663 // Whether to open the tooltip permanently or only on mouseover.
10666 // @option sticky: Boolean = false
10667 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10670 // @option opacity: Number = 0.9
10671 // Tooltip container opacity.
10675 onAdd: function (map) {
10676 DivOverlay.prototype.onAdd.call(this, map);
10677 this.setOpacity(this.options.opacity);
10680 // @section Tooltip events
10681 // @event tooltipopen: TooltipEvent
10682 // Fired when a tooltip is opened in the map.
10683 map.fire('tooltipopen', {tooltip: this});
10685 if (this._source) {
10686 this.addEventParent(this._source);
10688 // @namespace Layer
10689 // @section Tooltip events
10690 // @event tooltipopen: TooltipEvent
10691 // Fired when a tooltip bound to this layer is opened.
10692 this._source.fire('tooltipopen', {tooltip: this}, true);
10696 onRemove: function (map) {
10697 DivOverlay.prototype.onRemove.call(this, map);
10700 // @section Tooltip events
10701 // @event tooltipclose: TooltipEvent
10702 // Fired when a tooltip in the map is closed.
10703 map.fire('tooltipclose', {tooltip: this});
10705 if (this._source) {
10706 this.removeEventParent(this._source);
10708 // @namespace Layer
10709 // @section Tooltip events
10710 // @event tooltipclose: TooltipEvent
10711 // Fired when a tooltip bound to this layer is closed.
10712 this._source.fire('tooltipclose', {tooltip: this}, true);
10716 getEvents: function () {
10717 var events = DivOverlay.prototype.getEvents.call(this);
10719 if (!this.options.permanent) {
10720 events.preclick = this.close;
10726 _initLayout: function () {
10727 var prefix = 'leaflet-tooltip',
10728 className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10730 this._contentNode = this._container = create$1('div', className);
10732 this._container.setAttribute('role', 'tooltip');
10733 this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this));
10736 _updateLayout: function () {},
10738 _adjustPan: function () {},
10740 _setPosition: function (pos) {
10743 container = this._container,
10744 centerPoint = map.latLngToContainerPoint(map.getCenter()),
10745 tooltipPoint = map.layerPointToContainerPoint(pos),
10746 direction = this.options.direction,
10747 tooltipWidth = container.offsetWidth,
10748 tooltipHeight = container.offsetHeight,
10749 offset = toPoint(this.options.offset),
10750 anchor = this._getAnchor();
10752 if (direction === 'top') {
10753 subX = tooltipWidth / 2;
10754 subY = tooltipHeight;
10755 } else if (direction === 'bottom') {
10756 subX = tooltipWidth / 2;
10758 } else if (direction === 'center') {
10759 subX = tooltipWidth / 2;
10760 subY = tooltipHeight / 2;
10761 } else if (direction === 'right') {
10763 subY = tooltipHeight / 2;
10764 } else if (direction === 'left') {
10765 subX = tooltipWidth;
10766 subY = tooltipHeight / 2;
10767 } else if (tooltipPoint.x < centerPoint.x) {
10768 direction = 'right';
10770 subY = tooltipHeight / 2;
10772 direction = 'left';
10773 subX = tooltipWidth + (offset.x + anchor.x) * 2;
10774 subY = tooltipHeight / 2;
10777 pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);
10779 removeClass(container, 'leaflet-tooltip-right');
10780 removeClass(container, 'leaflet-tooltip-left');
10781 removeClass(container, 'leaflet-tooltip-top');
10782 removeClass(container, 'leaflet-tooltip-bottom');
10783 addClass(container, 'leaflet-tooltip-' + direction);
10784 setPosition(container, pos);
10787 _updatePosition: function () {
10788 var pos = this._map.latLngToLayerPoint(this._latlng);
10789 this._setPosition(pos);
10792 setOpacity: function (opacity) {
10793 this.options.opacity = opacity;
10795 if (this._container) {
10796 setOpacity(this._container, opacity);
10800 _animateZoom: function (e) {
10801 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10802 this._setPosition(pos);
10805 _getAnchor: function () {
10806 // Where should we anchor the tooltip on the source layer?
10807 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10812 // @namespace Tooltip
10813 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10814 // Instantiates a `Tooltip` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
10816 // @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
10817 // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
10818 var tooltip = function (options, source) {
10819 return new Tooltip(options, source);
10823 // @section Methods for Layers and Controls
10826 // @method openTooltip(tooltip: Tooltip): this
10827 // Opens the specified tooltip.
10829 // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10830 // Creates a tooltip with the specified content and options and open it.
10831 openTooltip: function (tooltip, latlng, options) {
10832 this._initOverlay(Tooltip, tooltip, latlng, options)
10838 // @method closeTooltip(tooltip: Tooltip): this
10839 // Closes the tooltip given as parameter.
10840 closeTooltip: function (tooltip) {
10849 * @section Tooltip methods example
10851 * All layers share a set of methods convenient for binding tooltips to it.
10854 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10855 * layer.openTooltip();
10856 * layer.closeTooltip();
10860 // @section Tooltip methods
10863 // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10864 // Binds a tooltip to the layer with the passed `content` and sets up the
10865 // necessary event listeners. If a `Function` is passed it will receive
10866 // the layer as the first argument and should return a `String` or `HTMLElement`.
10867 bindTooltip: function (content, options) {
10869 if (this._tooltip && this.isTooltipOpen()) {
10870 this.unbindTooltip();
10873 this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
10874 this._initTooltipInteractions();
10876 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10877 this.openTooltip();
10883 // @method unbindTooltip(): this
10884 // Removes the tooltip previously bound with `bindTooltip`.
10885 unbindTooltip: function () {
10886 if (this._tooltip) {
10887 this._initTooltipInteractions(true);
10888 this.closeTooltip();
10889 this._tooltip = null;
10894 _initTooltipInteractions: function (remove) {
10895 if (!remove && this._tooltipHandlersAdded) { return; }
10896 var onOff = remove ? 'off' : 'on',
10898 remove: this.closeTooltip,
10899 move: this._moveTooltip
10901 if (!this._tooltip.options.permanent) {
10902 events.mouseover = this._openTooltip;
10903 events.mouseout = this.closeTooltip;
10904 events.click = this._openTooltip;
10906 this._addFocusListeners();
10908 events.add = this._addFocusListeners;
10911 events.add = this._openTooltip;
10913 if (this._tooltip.options.sticky) {
10914 events.mousemove = this._moveTooltip;
10916 this[onOff](events);
10917 this._tooltipHandlersAdded = !remove;
10920 // @method openTooltip(latlng?: LatLng): this
10921 // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10922 openTooltip: function (latlng) {
10923 if (this._tooltip) {
10924 if (!(this instanceof FeatureGroup)) {
10925 this._tooltip._source = this;
10927 if (this._tooltip._prepareOpen(latlng)) {
10928 // open the tooltip on the map
10929 this._tooltip.openOn(this._map);
10931 if (this.getElement) {
10932 this._setAriaDescribedByOnLayer(this);
10933 } else if (this.eachLayer) {
10934 this.eachLayer(this._setAriaDescribedByOnLayer, this);
10941 // @method closeTooltip(): this
10942 // Closes the tooltip bound to this layer if it is open.
10943 closeTooltip: function () {
10944 if (this._tooltip) {
10945 return this._tooltip.close();
10949 // @method toggleTooltip(): this
10950 // Opens or closes the tooltip bound to this layer depending on its current state.
10951 toggleTooltip: function () {
10952 if (this._tooltip) {
10953 this._tooltip.toggle(this);
10958 // @method isTooltipOpen(): boolean
10959 // Returns `true` if the tooltip bound to this layer is currently open.
10960 isTooltipOpen: function () {
10961 return this._tooltip.isOpen();
10964 // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10965 // Sets the content of the tooltip bound to this layer.
10966 setTooltipContent: function (content) {
10967 if (this._tooltip) {
10968 this._tooltip.setContent(content);
10973 // @method getTooltip(): Tooltip
10974 // Returns the tooltip bound to this layer.
10975 getTooltip: function () {
10976 return this._tooltip;
10979 _addFocusListeners: function () {
10980 if (this.getElement) {
10981 this._addFocusListenersOnLayer(this);
10982 } else if (this.eachLayer) {
10983 this.eachLayer(this._addFocusListenersOnLayer, this);
10987 _addFocusListenersOnLayer: function (layer) {
10988 var el = typeof layer.getElement === 'function' && layer.getElement();
10990 on(el, 'focus', function () {
10991 this._tooltip._source = layer;
10992 this.openTooltip();
10994 on(el, 'blur', this.closeTooltip, this);
10998 _setAriaDescribedByOnLayer: function (layer) {
10999 var el = typeof layer.getElement === 'function' && layer.getElement();
11001 el.setAttribute('aria-describedby', this._tooltip._container.id);
11006 _openTooltip: function (e) {
11007 if (!this._tooltip || !this._map) {
11011 // If the map is moving, we will show the tooltip after it's done.
11012 if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) {
11013 this._openOnceFlag = true;
11015 this._map.once('moveend', function () {
11016 that._openOnceFlag = false;
11017 that._openTooltip(e);
11022 this._tooltip._source = e.layer || e.target;
11024 this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
11027 _moveTooltip: function (e) {
11028 var latlng = e.latlng, containerPoint, layerPoint;
11029 if (this._tooltip.options.sticky && e.originalEvent) {
11030 containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
11031 layerPoint = this._map.containerPointToLayerPoint(containerPoint);
11032 latlng = this._map.layerPointToLatLng(layerPoint);
11034 this._tooltip.setLatLng(latlng);
11043 * Represents a lightweight icon for markers that uses a simple `<div>`
11044 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
11048 * var myIcon = L.divIcon({className: 'my-div-icon'});
11049 * // you can set .my-div-icon styles in CSS
11051 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
11054 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
11057 var DivIcon = Icon.extend({
11060 // @aka DivIcon options
11061 iconSize: [12, 12], // also can be set through CSS
11063 // iconAnchor: (Point),
11064 // popupAnchor: (Point),
11066 // @option html: String|HTMLElement = ''
11067 // Custom HTML code to put inside the div element, empty by default. Alternatively,
11068 // an instance of `HTMLElement`.
11071 // @option bgPos: Point = [0, 0]
11072 // Optional relative position of the background, in pixels
11075 className: 'leaflet-div-icon'
11078 createIcon: function (oldIcon) {
11079 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
11080 options = this.options;
11082 if (options.html instanceof Element) {
11084 div.appendChild(options.html);
11086 div.innerHTML = options.html !== false ? options.html : '';
11089 if (options.bgPos) {
11090 var bgPos = toPoint(options.bgPos);
11091 div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
11093 this._setIconStyles(div, 'icon');
11098 createShadow: function () {
11103 // @factory L.divIcon(options: DivIcon options)
11104 // Creates a `DivIcon` instance with the given options.
11105 function divIcon(options) {
11106 return new DivIcon(options);
11109 Icon.Default = IconDefault;
11116 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
11117 * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
11120 * @section Synchronous usage
11123 * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
11126 * var CanvasLayer = L.GridLayer.extend({
11127 * createTile: function(coords){
11128 * // create a <canvas> element for drawing
11129 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11131 * // setup tile width and height according to the options
11132 * var size = this.getTileSize();
11133 * tile.width = size.x;
11134 * tile.height = size.y;
11136 * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
11137 * var ctx = tile.getContext('2d');
11139 * // return the tile so it can be rendered on screen
11145 * @section Asynchronous usage
11148 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
11151 * var CanvasLayer = L.GridLayer.extend({
11152 * createTile: function(coords, done){
11155 * // create a <canvas> element for drawing
11156 * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
11158 * // setup tile width and height according to the options
11159 * var size = this.getTileSize();
11160 * tile.width = size.x;
11161 * tile.height = size.y;
11163 * // draw something asynchronously and pass the tile to the done() callback
11164 * setTimeout(function() {
11165 * done(error, tile);
11177 var GridLayer = Layer.extend({
11180 // @aka GridLayer options
11182 // @option tileSize: Number|Point = 256
11183 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
11186 // @option opacity: Number = 1.0
11187 // Opacity of the tiles. Can be used in the `createTile()` function.
11190 // @option updateWhenIdle: Boolean = (depends)
11191 // Load new tiles only when panning ends.
11192 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
11193 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
11194 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
11195 updateWhenIdle: Browser.mobile,
11197 // @option updateWhenZooming: Boolean = true
11198 // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
11199 updateWhenZooming: true,
11201 // @option updateInterval: Number = 200
11202 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
11203 updateInterval: 200,
11205 // @option zIndex: Number = 1
11206 // The explicit zIndex of the tile layer.
11209 // @option bounds: LatLngBounds = undefined
11210 // If set, tiles will only be loaded inside the set `LatLngBounds`.
11213 // @option minZoom: Number = 0
11214 // The minimum zoom level down to which this layer will be displayed (inclusive).
11217 // @option maxZoom: Number = undefined
11218 // The maximum zoom level up to which this layer will be displayed (inclusive).
11219 maxZoom: undefined,
11221 // @option maxNativeZoom: Number = undefined
11222 // Maximum zoom number the tile source has available. If it is specified,
11223 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
11224 // from `maxNativeZoom` level and auto-scaled.
11225 maxNativeZoom: undefined,
11227 // @option minNativeZoom: Number = undefined
11228 // Minimum zoom number the tile source has available. If it is specified,
11229 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
11230 // from `minNativeZoom` level and auto-scaled.
11231 minNativeZoom: undefined,
11233 // @option noWrap: Boolean = false
11234 // Whether the layer is wrapped around the antimeridian. If `true`, the
11235 // GridLayer will only be displayed once at low zoom levels. Has no
11236 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
11237 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
11238 // tiles outside the CRS limits.
11241 // @option pane: String = 'tilePane'
11242 // `Map pane` where the grid layer will be added.
11245 // @option className: String = ''
11246 // A custom class name to assign to the tile layer. Empty by default.
11249 // @option keepBuffer: Number = 2
11250 // When panning the map, keep this many rows and columns of tiles before unloading them.
11254 initialize: function (options) {
11255 setOptions(this, options);
11258 onAdd: function () {
11259 this._initContainer();
11264 this._resetView(); // implicit _update() call
11267 beforeAdd: function (map) {
11268 map._addZoomLimit(this);
11271 onRemove: function (map) {
11272 this._removeAllTiles();
11273 remove(this._container);
11274 map._removeZoomLimit(this);
11275 this._container = null;
11276 this._tileZoom = undefined;
11279 // @method bringToFront: this
11280 // Brings the tile layer to the top of all tile layers.
11281 bringToFront: function () {
11283 toFront(this._container);
11284 this._setAutoZIndex(Math.max);
11289 // @method bringToBack: this
11290 // Brings the tile layer to the bottom of all tile layers.
11291 bringToBack: function () {
11293 toBack(this._container);
11294 this._setAutoZIndex(Math.min);
11299 // @method getContainer: HTMLElement
11300 // Returns the HTML element that contains the tiles for this layer.
11301 getContainer: function () {
11302 return this._container;
11305 // @method setOpacity(opacity: Number): this
11306 // Changes the [opacity](#gridlayer-opacity) of the grid layer.
11307 setOpacity: function (opacity) {
11308 this.options.opacity = opacity;
11309 this._updateOpacity();
11313 // @method setZIndex(zIndex: Number): this
11314 // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
11315 setZIndex: function (zIndex) {
11316 this.options.zIndex = zIndex;
11317 this._updateZIndex();
11322 // @method isLoading: Boolean
11323 // Returns `true` if any tile in the grid layer has not finished loading.
11324 isLoading: function () {
11325 return this._loading;
11328 // @method redraw: this
11329 // Causes the layer to clear all the tiles and request them again.
11330 redraw: function () {
11332 this._removeAllTiles();
11333 var tileZoom = this._clampZoom(this._map.getZoom());
11334 if (tileZoom !== this._tileZoom) {
11335 this._tileZoom = tileZoom;
11336 this._updateLevels();
11343 getEvents: function () {
11345 viewprereset: this._invalidateAll,
11346 viewreset: this._resetView,
11347 zoom: this._resetView,
11348 moveend: this._onMoveEnd
11351 if (!this.options.updateWhenIdle) {
11352 // update tiles on move, but not more often than once per given interval
11353 if (!this._onMove) {
11354 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
11357 events.move = this._onMove;
11360 if (this._zoomAnimated) {
11361 events.zoomanim = this._animateZoom;
11367 // @section Extension methods
11368 // Layers extending `GridLayer` shall reimplement the following method.
11369 // @method createTile(coords: Object, done?: Function): HTMLElement
11370 // Called only internally, must be overridden by classes extending `GridLayer`.
11371 // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
11372 // is specified, it must be called when the tile has finished loading and drawing.
11373 createTile: function () {
11374 return document.createElement('div');
11378 // @method getTileSize: Point
11379 // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
11380 getTileSize: function () {
11381 var s = this.options.tileSize;
11382 return s instanceof Point ? s : new Point(s, s);
11385 _updateZIndex: function () {
11386 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
11387 this._container.style.zIndex = this.options.zIndex;
11391 _setAutoZIndex: function (compare) {
11392 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
11394 var layers = this.getPane().children,
11395 edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
11397 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
11399 zIndex = layers[i].style.zIndex;
11401 if (layers[i] !== this._container && zIndex) {
11402 edgeZIndex = compare(edgeZIndex, +zIndex);
11406 if (isFinite(edgeZIndex)) {
11407 this.options.zIndex = edgeZIndex + compare(-1, 1);
11408 this._updateZIndex();
11412 _updateOpacity: function () {
11413 if (!this._map) { return; }
11415 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
11416 if (Browser.ielt9) { return; }
11418 setOpacity(this._container, this.options.opacity);
11420 var now = +new Date(),
11424 for (var key in this._tiles) {
11425 var tile = this._tiles[key];
11426 if (!tile.current || !tile.loaded) { continue; }
11428 var fade = Math.min(1, (now - tile.loaded) / 200);
11430 setOpacity(tile.el, fade);
11437 this._onOpaqueTile(tile);
11439 tile.active = true;
11443 if (willPrune && !this._noPrune) { this._pruneTiles(); }
11446 cancelAnimFrame(this._fadeFrame);
11447 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11451 _onOpaqueTile: falseFn,
11453 _initContainer: function () {
11454 if (this._container) { return; }
11456 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11457 this._updateZIndex();
11459 if (this.options.opacity < 1) {
11460 this._updateOpacity();
11463 this.getPane().appendChild(this._container);
11466 _updateLevels: function () {
11468 var zoom = this._tileZoom,
11469 maxZoom = this.options.maxZoom;
11471 if (zoom === undefined) { return undefined; }
11473 for (var z in this._levels) {
11475 if (this._levels[z].el.children.length || z === zoom) {
11476 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11477 this._onUpdateLevel(z);
11479 remove(this._levels[z].el);
11480 this._removeTilesAtZoom(z);
11481 this._onRemoveLevel(z);
11482 delete this._levels[z];
11486 var level = this._levels[zoom],
11490 level = this._levels[zoom] = {};
11492 level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11493 level.el.style.zIndex = maxZoom;
11495 level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11498 this._setZoomTransform(level, map.getCenter(), map.getZoom());
11500 // force the browser to consider the newly added element for transition
11501 falseFn(level.el.offsetWidth);
11503 this._onCreateLevel(level);
11506 this._level = level;
11511 _onUpdateLevel: falseFn,
11513 _onRemoveLevel: falseFn,
11515 _onCreateLevel: falseFn,
11517 _pruneTiles: function () {
11524 var zoom = this._map.getZoom();
11525 if (zoom > this.options.maxZoom ||
11526 zoom < this.options.minZoom) {
11527 this._removeAllTiles();
11531 for (key in this._tiles) {
11532 tile = this._tiles[key];
11533 tile.retain = tile.current;
11536 for (key in this._tiles) {
11537 tile = this._tiles[key];
11538 if (tile.current && !tile.active) {
11539 var coords = tile.coords;
11540 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11541 this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11546 for (key in this._tiles) {
11547 if (!this._tiles[key].retain) {
11548 this._removeTile(key);
11553 _removeTilesAtZoom: function (zoom) {
11554 for (var key in this._tiles) {
11555 if (this._tiles[key].coords.z !== zoom) {
11558 this._removeTile(key);
11562 _removeAllTiles: function () {
11563 for (var key in this._tiles) {
11564 this._removeTile(key);
11568 _invalidateAll: function () {
11569 for (var z in this._levels) {
11570 remove(this._levels[z].el);
11571 this._onRemoveLevel(Number(z));
11572 delete this._levels[z];
11574 this._removeAllTiles();
11576 this._tileZoom = undefined;
11579 _retainParent: function (x, y, z, minZoom) {
11580 var x2 = Math.floor(x / 2),
11581 y2 = Math.floor(y / 2),
11583 coords2 = new Point(+x2, +y2);
11586 var key = this._tileCoordsToKey(coords2),
11587 tile = this._tiles[key];
11589 if (tile && tile.active) {
11590 tile.retain = true;
11593 } else if (tile && tile.loaded) {
11594 tile.retain = true;
11597 if (z2 > minZoom) {
11598 return this._retainParent(x2, y2, z2, minZoom);
11604 _retainChildren: function (x, y, z, maxZoom) {
11606 for (var i = 2 * x; i < 2 * x + 2; i++) {
11607 for (var j = 2 * y; j < 2 * y + 2; j++) {
11609 var coords = new Point(i, j);
11612 var key = this._tileCoordsToKey(coords),
11613 tile = this._tiles[key];
11615 if (tile && tile.active) {
11616 tile.retain = true;
11619 } else if (tile && tile.loaded) {
11620 tile.retain = true;
11623 if (z + 1 < maxZoom) {
11624 this._retainChildren(i, j, z + 1, maxZoom);
11630 _resetView: function (e) {
11631 var animating = e && (e.pinch || e.flyTo);
11632 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11635 _animateZoom: function (e) {
11636 this._setView(e.center, e.zoom, true, e.noUpdate);
11639 _clampZoom: function (zoom) {
11640 var options = this.options;
11642 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11643 return options.minNativeZoom;
11646 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11647 return options.maxNativeZoom;
11653 _setView: function (center, zoom, noPrune, noUpdate) {
11654 var tileZoom = Math.round(zoom);
11655 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11656 (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11657 tileZoom = undefined;
11659 tileZoom = this._clampZoom(tileZoom);
11662 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11664 if (!noUpdate || tileZoomChanged) {
11666 this._tileZoom = tileZoom;
11668 if (this._abortLoading) {
11669 this._abortLoading();
11672 this._updateLevels();
11675 if (tileZoom !== undefined) {
11676 this._update(center);
11680 this._pruneTiles();
11683 // Flag to prevent _updateOpacity from pruning tiles during
11684 // a zoom anim or a pinch gesture
11685 this._noPrune = !!noPrune;
11688 this._setZoomTransforms(center, zoom);
11691 _setZoomTransforms: function (center, zoom) {
11692 for (var i in this._levels) {
11693 this._setZoomTransform(this._levels[i], center, zoom);
11697 _setZoomTransform: function (level, center, zoom) {
11698 var scale = this._map.getZoomScale(zoom, level.zoom),
11699 translate = level.origin.multiplyBy(scale)
11700 .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11702 if (Browser.any3d) {
11703 setTransform(level.el, translate, scale);
11705 setPosition(level.el, translate);
11709 _resetGrid: function () {
11710 var map = this._map,
11711 crs = map.options.crs,
11712 tileSize = this._tileSize = this.getTileSize(),
11713 tileZoom = this._tileZoom;
11715 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11717 this._globalTileRange = this._pxBoundsToTileRange(bounds);
11720 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11721 Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11722 Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11724 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11725 Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11726 Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11730 _onMoveEnd: function () {
11731 if (!this._map || this._map._animatingZoom) { return; }
11736 _getTiledPixelBounds: function (center) {
11737 var map = this._map,
11738 mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11739 scale = map.getZoomScale(mapZoom, this._tileZoom),
11740 pixelCenter = map.project(center, this._tileZoom).floor(),
11741 halfSize = map.getSize().divideBy(scale * 2);
11743 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11746 // Private method to load tiles in the grid's active zoom level according to map bounds
11747 _update: function (center) {
11748 var map = this._map;
11749 if (!map) { return; }
11750 var zoom = this._clampZoom(map.getZoom());
11752 if (center === undefined) { center = map.getCenter(); }
11753 if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
11755 var pixelBounds = this._getTiledPixelBounds(center),
11756 tileRange = this._pxBoundsToTileRange(pixelBounds),
11757 tileCenter = tileRange.getCenter(),
11759 margin = this.options.keepBuffer,
11760 noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11761 tileRange.getTopRight().add([margin, -margin]));
11763 // Sanity check: panic if the tile range contains Infinity somewhere.
11764 if (!(isFinite(tileRange.min.x) &&
11765 isFinite(tileRange.min.y) &&
11766 isFinite(tileRange.max.x) &&
11767 isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11769 for (var key in this._tiles) {
11770 var c = this._tiles[key].coords;
11771 if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11772 this._tiles[key].current = false;
11776 // _update just loads more tiles. If the tile zoom level differs too much
11777 // from the map's, let _setView reset levels and prune old tiles.
11778 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11780 // create a queue of coordinates to load tiles from
11781 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11782 for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11783 var coords = new Point(i, j);
11784 coords.z = this._tileZoom;
11786 if (!this._isValidTile(coords)) { continue; }
11788 var tile = this._tiles[this._tileCoordsToKey(coords)];
11790 tile.current = true;
11792 queue.push(coords);
11797 // sort tile queue to load tiles in order of their distance to center
11798 queue.sort(function (a, b) {
11799 return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11802 if (queue.length !== 0) {
11803 // if it's the first batch of tiles to load
11804 if (!this._loading) {
11805 this._loading = true;
11806 // @event loading: Event
11807 // Fired when the grid layer starts loading tiles.
11808 this.fire('loading');
11811 // create DOM fragment to append tiles in one batch
11812 var fragment = document.createDocumentFragment();
11814 for (i = 0; i < queue.length; i++) {
11815 this._addTile(queue[i], fragment);
11818 this._level.el.appendChild(fragment);
11822 _isValidTile: function (coords) {
11823 var crs = this._map.options.crs;
11825 if (!crs.infinite) {
11826 // don't load tile if it's out of bounds and not wrapped
11827 var bounds = this._globalTileRange;
11828 if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11829 (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11832 if (!this.options.bounds) { return true; }
11834 // don't load tile if it doesn't intersect the bounds in options
11835 var tileBounds = this._tileCoordsToBounds(coords);
11836 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11839 _keyToBounds: function (key) {
11840 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11843 _tileCoordsToNwSe: function (coords) {
11844 var map = this._map,
11845 tileSize = this.getTileSize(),
11846 nwPoint = coords.scaleBy(tileSize),
11847 sePoint = nwPoint.add(tileSize),
11848 nw = map.unproject(nwPoint, coords.z),
11849 se = map.unproject(sePoint, coords.z);
11853 // converts tile coordinates to its geographical bounds
11854 _tileCoordsToBounds: function (coords) {
11855 var bp = this._tileCoordsToNwSe(coords),
11856 bounds = new LatLngBounds(bp[0], bp[1]);
11858 if (!this.options.noWrap) {
11859 bounds = this._map.wrapLatLngBounds(bounds);
11863 // converts tile coordinates to key for the tile cache
11864 _tileCoordsToKey: function (coords) {
11865 return coords.x + ':' + coords.y + ':' + coords.z;
11868 // converts tile cache key to coordinates
11869 _keyToTileCoords: function (key) {
11870 var k = key.split(':'),
11871 coords = new Point(+k[0], +k[1]);
11876 _removeTile: function (key) {
11877 var tile = this._tiles[key];
11878 if (!tile) { return; }
11882 delete this._tiles[key];
11884 // @event tileunload: TileEvent
11885 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11886 this.fire('tileunload', {
11888 coords: this._keyToTileCoords(key)
11892 _initTile: function (tile) {
11893 addClass(tile, 'leaflet-tile');
11895 var tileSize = this.getTileSize();
11896 tile.style.width = tileSize.x + 'px';
11897 tile.style.height = tileSize.y + 'px';
11899 tile.onselectstart = falseFn;
11900 tile.onmousemove = falseFn;
11902 // update opacity on tiles in IE7-8 because of filter inheritance problems
11903 if (Browser.ielt9 && this.options.opacity < 1) {
11904 setOpacity(tile, this.options.opacity);
11908 _addTile: function (coords, container) {
11909 var tilePos = this._getTilePos(coords),
11910 key = this._tileCoordsToKey(coords);
11912 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11914 this._initTile(tile);
11916 // if createTile is defined with a second argument ("done" callback),
11917 // we know that tile is async and will be ready later; otherwise
11918 if (this.createTile.length < 2) {
11919 // mark tile as ready, but delay one frame for opacity animation to happen
11920 requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11923 setPosition(tile, tilePos);
11925 // save tile in cache
11926 this._tiles[key] = {
11932 container.appendChild(tile);
11933 // @event tileloadstart: TileEvent
11934 // Fired when a tile is requested and starts loading.
11935 this.fire('tileloadstart', {
11941 _tileReady: function (coords, err, tile) {
11943 // @event tileerror: TileErrorEvent
11944 // Fired when there is an error loading a tile.
11945 this.fire('tileerror', {
11952 var key = this._tileCoordsToKey(coords);
11954 tile = this._tiles[key];
11955 if (!tile) { return; }
11957 tile.loaded = +new Date();
11958 if (this._map._fadeAnimated) {
11959 setOpacity(tile.el, 0);
11960 cancelAnimFrame(this._fadeFrame);
11961 this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11963 tile.active = true;
11964 this._pruneTiles();
11968 addClass(tile.el, 'leaflet-tile-loaded');
11970 // @event tileload: TileEvent
11971 // Fired when a tile loads.
11972 this.fire('tileload', {
11978 if (this._noTilesToLoad()) {
11979 this._loading = false;
11980 // @event load: Event
11981 // Fired when the grid layer loaded all visible tiles.
11984 if (Browser.ielt9 || !this._map._fadeAnimated) {
11985 requestAnimFrame(this._pruneTiles, this);
11987 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11988 // to trigger a pruning.
11989 setTimeout(bind(this._pruneTiles, this), 250);
11994 _getTilePos: function (coords) {
11995 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11998 _wrapCoords: function (coords) {
11999 var newCoords = new Point(
12000 this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
12001 this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
12002 newCoords.z = coords.z;
12006 _pxBoundsToTileRange: function (bounds) {
12007 var tileSize = this.getTileSize();
12009 bounds.min.unscaleBy(tileSize).floor(),
12010 bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
12013 _noTilesToLoad: function () {
12014 for (var key in this._tiles) {
12015 if (!this._tiles[key].loaded) { return false; }
12021 // @factory L.gridLayer(options?: GridLayer options)
12022 // Creates a new instance of GridLayer with the supplied options.
12023 function gridLayer(options) {
12024 return new GridLayer(options);
12028 * @class TileLayer
\r
12029 * @inherits GridLayer
\r
12030 * @aka L.TileLayer
\r
12031 * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
\r
12036 * L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
12039 * @section URL template
\r
12042 * A string of the following form:
\r
12045 * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
\r
12048 * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add "@2x" to the URL to load retina tiles.
\r
12050 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
\r
12053 * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
\r
12058 var TileLayer = GridLayer.extend({
\r
12061 // @aka TileLayer options
\r
12063 // @option minZoom: Number = 0
\r
12064 // The minimum zoom level down to which this layer will be displayed (inclusive).
\r
12067 // @option maxZoom: Number = 18
\r
12068 // The maximum zoom level up to which this layer will be displayed (inclusive).
\r
12071 // @option subdomains: String|String[] = 'abc'
\r
12072 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
\r
12073 subdomains: 'abc',
\r
12075 // @option errorTileUrl: String = ''
\r
12076 // URL to the tile image to show in place of the tile that failed to load.
\r
12077 errorTileUrl: '',
\r
12079 // @option zoomOffset: Number = 0
\r
12080 // The zoom number used in tile URLs will be offset with this value.
\r
12083 // @option tms: Boolean = false
\r
12084 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
\r
12087 // @option zoomReverse: Boolean = false
\r
12088 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
\r
12089 zoomReverse: false,
\r
12091 // @option detectRetina: Boolean = false
\r
12092 // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
\r
12093 detectRetina: false,
\r
12095 // @option crossOrigin: Boolean|String = false
\r
12096 // Whether the crossOrigin attribute will be added to the tiles.
\r
12097 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
\r
12098 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
\r
12099 crossOrigin: false,
\r
12101 // @option referrerPolicy: Boolean|String = false
\r
12102 // Whether the referrerPolicy attribute will be added to the tiles.
\r
12103 // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
\r
12104 // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
\r
12105 // (e.g. to validate an API token).
\r
12106 // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
\r
12107 referrerPolicy: false
\r
12110 initialize: function (url, options) {
\r
12114 options = setOptions(this, options);
\r
12116 // detecting retina displays, adjusting tileSize and zoom levels
\r
12117 if (options.detectRetina && Browser.retina && options.maxZoom > 0) {
\r
12119 options.tileSize = Math.floor(options.tileSize / 2);
\r
12121 if (!options.zoomReverse) {
\r
12122 options.zoomOffset++;
\r
12123 options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);
\r
12125 options.zoomOffset--;
\r
12126 options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);
\r
12129 options.minZoom = Math.max(0, options.minZoom);
\r
12130 } else if (!options.zoomReverse) {
\r
12131 // make sure maxZoom is gte minZoom
\r
12132 options.maxZoom = Math.max(options.minZoom, options.maxZoom);
\r
12134 // make sure minZoom is lte maxZoom
\r
12135 options.minZoom = Math.min(options.maxZoom, options.minZoom);
\r
12138 if (typeof options.subdomains === 'string') {
\r
12139 options.subdomains = options.subdomains.split('');
\r
12142 this.on('tileunload', this._onTileRemove);
\r
12145 // @method setUrl(url: String, noRedraw?: Boolean): this
\r
12146 // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
\r
12147 // If the URL does not change, the layer will not be redrawn unless
\r
12148 // the noRedraw parameter is set to false.
\r
12149 setUrl: function (url, noRedraw) {
\r
12150 if (this._url === url && noRedraw === undefined) {
\r
12162 // @method createTile(coords: Object, done?: Function): HTMLElement
\r
12163 // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
\r
12164 // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
\r
12165 // callback is called when the tile has been loaded.
\r
12166 createTile: function (coords, done) {
\r
12167 var tile = document.createElement('img');
\r
12169 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
\r
12170 on(tile, 'error', bind(this._tileOnError, this, done, tile));
\r
12172 if (this.options.crossOrigin || this.options.crossOrigin === '') {
\r
12173 tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
\r
12176 // for this new option we follow the documented behavior
\r
12177 // more closely by only setting the property when string
\r
12178 if (typeof this.options.referrerPolicy === 'string') {
\r
12179 tile.referrerPolicy = this.options.referrerPolicy;
\r
12182 // The alt attribute is set to the empty string,
\r
12183 // allowing screen readers to ignore the decorative image tiles.
\r
12184 // https://www.w3.org/WAI/tutorials/images/decorative/
\r
12185 // https://www.w3.org/TR/html-aria/#el-img-empty-alt
\r
12188 tile.src = this.getTileUrl(coords);
\r
12193 // @section Extension methods
\r
12194 // @uninheritable
\r
12195 // Layers extending `TileLayer` might reimplement the following method.
\r
12196 // @method getTileUrl(coords: Object): String
\r
12197 // Called only internally, returns the URL for a tile given its coordinates.
\r
12198 // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
\r
12199 getTileUrl: function (coords) {
\r
12201 r: Browser.retina ? '@2x' : '',
\r
12202 s: this._getSubdomain(coords),
\r
12205 z: this._getZoomForUrl()
\r
12207 if (this._map && !this._map.options.crs.infinite) {
\r
12208 var invertedY = this._globalTileRange.max.y - coords.y;
\r
12209 if (this.options.tms) {
\r
12210 data['y'] = invertedY;
\r
12212 data['-y'] = invertedY;
\r
12215 return template(this._url, extend(data, this.options));
\r
12218 _tileOnLoad: function (done, tile) {
\r
12219 // For https://github.com/Leaflet/Leaflet/issues/3332
\r
12220 if (Browser.ielt9) {
\r
12221 setTimeout(bind(done, this, null, tile), 0);
\r
12223 done(null, tile);
\r
12227 _tileOnError: function (done, tile, e) {
\r
12228 var errorUrl = this.options.errorTileUrl;
\r
12229 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
\r
12230 tile.src = errorUrl;
\r
12235 _onTileRemove: function (e) {
\r
12236 e.tile.onload = null;
\r
12239 _getZoomForUrl: function () {
\r
12240 var zoom = this._tileZoom,
\r
12241 maxZoom = this.options.maxZoom,
\r
12242 zoomReverse = this.options.zoomReverse,
\r
12243 zoomOffset = this.options.zoomOffset;
\r
12245 if (zoomReverse) {
\r
12246 zoom = maxZoom - zoom;
\r
12249 return zoom + zoomOffset;
\r
12252 _getSubdomain: function (tilePoint) {
\r
12253 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
\r
12254 return this.options.subdomains[index];
\r
12257 // stops loading all tiles in the background layer
\r
12258 _abortLoading: function () {
\r
12260 for (i in this._tiles) {
\r
12261 if (this._tiles[i].coords.z !== this._tileZoom) {
\r
12262 tile = this._tiles[i].el;
\r
12264 tile.onload = falseFn;
\r
12265 tile.onerror = falseFn;
\r
12267 if (!tile.complete) {
\r
12268 tile.src = emptyImageUrl;
\r
12269 var coords = this._tiles[i].coords;
\r
12271 delete this._tiles[i];
\r
12272 // @event tileabort: TileEvent
\r
12273 // Fired when a tile was loading but is now not wanted.
\r
12274 this.fire('tileabort', {
\r
12283 _removeTile: function (key) {
\r
12284 var tile = this._tiles[key];
\r
12285 if (!tile) { return; }
\r
12287 // Cancels any pending http requests associated with the tile
\r
12288 tile.el.setAttribute('src', emptyImageUrl);
\r
12290 return GridLayer.prototype._removeTile.call(this, key);
\r
12293 _tileReady: function (coords, err, tile) {
\r
12294 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
\r
12298 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
\r
12303 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
\r
12304 // Instantiates a tile layer object given a `URL template` and optionally an options object.
\r
12306 function tileLayer(url, options) {
\r
12307 return new TileLayer(url, options);
\r
12311 * @class TileLayer.WMS
\r
12312 * @inherits TileLayer
\r
12313 * @aka L.TileLayer.WMS
\r
12314 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
\r
12319 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
\r
12320 * layers: 'nexrad-n0r-900913',
\r
12321 * format: 'image/png',
\r
12322 * transparent: true,
\r
12323 * attribution: "Weather data © 2012 IEM Nexrad"
\r
12328 var TileLayerWMS = TileLayer.extend({
\r
12331 // @aka TileLayer.WMS options
\r
12332 // If any custom options not documented here are used, they will be sent to the
\r
12333 // WMS server as extra parameters in each request URL. This can be useful for
\r
12334 // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
\r
12335 defaultWmsParams: {
\r
12337 request: 'GetMap',
\r
12339 // @option layers: String = ''
\r
12340 // **(required)** Comma-separated list of WMS layers to show.
\r
12343 // @option styles: String = ''
\r
12344 // Comma-separated list of WMS styles.
\r
12347 // @option format: String = 'image/jpeg'
\r
12348 // WMS image format (use `'image/png'` for layers with transparency).
\r
12349 format: 'image/jpeg',
\r
12351 // @option transparent: Boolean = false
\r
12352 // If `true`, the WMS service will return images with transparency.
\r
12353 transparent: false,
\r
12355 // @option version: String = '1.1.1'
\r
12356 // Version of the WMS service to use
\r
12361 // @option crs: CRS = null
\r
12362 // Coordinate Reference System to use for the WMS requests, defaults to
\r
12363 // map CRS. Don't change this if you're not sure what it means.
\r
12366 // @option uppercase: Boolean = false
\r
12367 // If `true`, WMS request parameter keys will be uppercase.
\r
12371 initialize: function (url, options) {
\r
12375 var wmsParams = extend({}, this.defaultWmsParams);
\r
12377 // all keys that are not TileLayer options go to WMS params
\r
12378 for (var i in options) {
\r
12379 if (!(i in this.options)) {
\r
12380 wmsParams[i] = options[i];
\r
12384 options = setOptions(this, options);
\r
12386 var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
\r
12387 var tileSize = this.getTileSize();
\r
12388 wmsParams.width = tileSize.x * realRetina;
\r
12389 wmsParams.height = tileSize.y * realRetina;
\r
12391 this.wmsParams = wmsParams;
\r
12394 onAdd: function (map) {
\r
12396 this._crs = this.options.crs || map.options.crs;
\r
12397 this._wmsVersion = parseFloat(this.wmsParams.version);
\r
12399 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
\r
12400 this.wmsParams[projectionKey] = this._crs.code;
\r
12402 TileLayer.prototype.onAdd.call(this, map);
\r
12405 getTileUrl: function (coords) {
\r
12407 var tileBounds = this._tileCoordsToNwSe(coords),
\r
12409 bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
\r
12410 min = bounds.min,
\r
12411 max = bounds.max,
\r
12412 bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
\r
12413 [min.y, min.x, max.y, max.x] :
\r
12414 [min.x, min.y, max.x, max.y]).join(','),
\r
12415 url = TileLayer.prototype.getTileUrl.call(this, coords);
\r
12417 getParamString(this.wmsParams, url, this.options.uppercase) +
\r
12418 (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
\r
12421 // @method setParams(params: Object, noRedraw?: Boolean): this
\r
12422 // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
\r
12423 setParams: function (params, noRedraw) {
\r
12425 extend(this.wmsParams, params);
\r
12436 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
\r
12437 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
\r
12438 function tileLayerWMS(url, options) {
\r
12439 return new TileLayerWMS(url, options);
\r
12442 TileLayer.WMS = TileLayerWMS;
12443 tileLayer.wms = tileLayerWMS;
12450 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
12451 * DOM container of the renderer, its bounds, and its zoom animation.
12453 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
12454 * itself can be added or removed to the map. All paths use a renderer, which can
12455 * be implicit (the map will decide the type of renderer and use it automatically)
12456 * or explicit (using the [`renderer`](#path-renderer) option of the path).
12458 * Do not use this class directly, use `SVG` and `Canvas` instead.
12460 * @event update: Event
12461 * Fired when the renderer updates its bounds, center and zoom, for example when
12462 * its map has moved
12465 var Renderer = Layer.extend({
12468 // @aka Renderer options
12470 // @option padding: Number = 0.1
12471 // How much to extend the clip area around the map view (relative to its size)
12472 // e.g. 0.1 would be 10% of map view in each direction
12476 initialize: function (options) {
12477 setOptions(this, options);
12479 this._layers = this._layers || {};
12482 onAdd: function () {
12483 if (!this._container) {
12484 this._initContainer(); // defined by renderer implementations
12486 // always keep transform-origin as 0 0
12487 addClass(this._container, 'leaflet-zoom-animated');
12490 this.getPane().appendChild(this._container);
12492 this.on('update', this._updatePaths, this);
12495 onRemove: function () {
12496 this.off('update', this._updatePaths, this);
12497 this._destroyContainer();
12500 getEvents: function () {
12502 viewreset: this._reset,
12503 zoom: this._onZoom,
12504 moveend: this._update,
12505 zoomend: this._onZoomEnd
12507 if (this._zoomAnimated) {
12508 events.zoomanim = this._onAnimZoom;
12513 _onAnimZoom: function (ev) {
12514 this._updateTransform(ev.center, ev.zoom);
12517 _onZoom: function () {
12518 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12521 _updateTransform: function (center, zoom) {
12522 var scale = this._map.getZoomScale(zoom, this._zoom),
12523 viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12524 currentCenterPoint = this._map.project(this._center, zoom),
12526 topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
12527 .subtract(this._map._getNewPixelOrigin(center, zoom));
12529 if (Browser.any3d) {
12530 setTransform(this._container, topLeftOffset, scale);
12532 setPosition(this._container, topLeftOffset);
12536 _reset: function () {
12538 this._updateTransform(this._center, this._zoom);
12540 for (var id in this._layers) {
12541 this._layers[id]._reset();
12545 _onZoomEnd: function () {
12546 for (var id in this._layers) {
12547 this._layers[id]._project();
12551 _updatePaths: function () {
12552 for (var id in this._layers) {
12553 this._layers[id]._update();
12557 _update: function () {
12558 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12559 // Subclasses are responsible of firing the 'update' event.
12560 var p = this.options.padding,
12561 size = this._map.getSize(),
12562 min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12564 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12566 this._center = this._map.getCenter();
12567 this._zoom = this._map.getZoom();
12573 * @inherits Renderer
12576 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12577 * Inherits `Renderer`.
12579 * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
12580 * available in all web browsers, notably IE8, and overlapping geometries might
12581 * not display properly in some edge cases.
12585 * Use Canvas by default for all paths in the map:
12588 * var map = L.map('map', {
12589 * renderer: L.canvas()
12593 * Use a Canvas renderer with extra padding for specific vector geometries:
12596 * var map = L.map('map');
12597 * var myRenderer = L.canvas({ padding: 0.5 });
12598 * var line = L.polyline( coordinates, { renderer: myRenderer } );
12599 * var circle = L.circle( center, { renderer: myRenderer } );
12603 var Canvas = Renderer.extend({
12606 // @aka Canvas options
12608 // @option tolerance: Number = 0
12609 // How much to extend the click tolerance around a path/object on the map.
12613 getEvents: function () {
12614 var events = Renderer.prototype.getEvents.call(this);
12615 events.viewprereset = this._onViewPreReset;
12619 _onViewPreReset: function () {
12620 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12621 this._postponeUpdatePaths = true;
12624 onAdd: function () {
12625 Renderer.prototype.onAdd.call(this);
12627 // Redraw vectors since canvas is cleared upon removal,
12628 // in case of removing the renderer itself from the map.
12632 _initContainer: function () {
12633 var container = this._container = document.createElement('canvas');
12635 on(container, 'mousemove', this._onMouseMove, this);
12636 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12637 on(container, 'mouseout', this._handleMouseOut, this);
12638 container['_leaflet_disable_events'] = true;
12640 this._ctx = container.getContext('2d');
12643 _destroyContainer: function () {
12644 cancelAnimFrame(this._redrawRequest);
12646 remove(this._container);
12647 off(this._container);
12648 delete this._container;
12651 _updatePaths: function () {
12652 if (this._postponeUpdatePaths) { return; }
12655 this._redrawBounds = null;
12656 for (var id in this._layers) {
12657 layer = this._layers[id];
12663 _update: function () {
12664 if (this._map._animatingZoom && this._bounds) { return; }
12666 Renderer.prototype._update.call(this);
12668 var b = this._bounds,
12669 container = this._container,
12670 size = b.getSize(),
12671 m = Browser.retina ? 2 : 1;
12673 setPosition(container, b.min);
12675 // set canvas size (also clearing it); use double size on retina
12676 container.width = m * size.x;
12677 container.height = m * size.y;
12678 container.style.width = size.x + 'px';
12679 container.style.height = size.y + 'px';
12681 if (Browser.retina) {
12682 this._ctx.scale(2, 2);
12685 // translate so we use the same path coordinates after canvas element moves
12686 this._ctx.translate(-b.min.x, -b.min.y);
12688 // Tell paths to redraw themselves
12689 this.fire('update');
12692 _reset: function () {
12693 Renderer.prototype._reset.call(this);
12695 if (this._postponeUpdatePaths) {
12696 this._postponeUpdatePaths = false;
12697 this._updatePaths();
12701 _initPath: function (layer) {
12702 this._updateDashArray(layer);
12703 this._layers[stamp(layer)] = layer;
12705 var order = layer._order = {
12707 prev: this._drawLast,
12710 if (this._drawLast) { this._drawLast.next = order; }
12711 this._drawLast = order;
12712 this._drawFirst = this._drawFirst || this._drawLast;
12715 _addPath: function (layer) {
12716 this._requestRedraw(layer);
12719 _removePath: function (layer) {
12720 var order = layer._order;
12721 var next = order.next;
12722 var prev = order.prev;
12727 this._drawLast = prev;
12732 this._drawFirst = next;
12735 delete layer._order;
12737 delete this._layers[stamp(layer)];
12739 this._requestRedraw(layer);
12742 _updatePath: function (layer) {
12743 // Redraw the union of the layer's old pixel
12744 // bounds and the new pixel bounds.
12745 this._extendRedrawBounds(layer);
12748 // The redraw will extend the redraw bounds
12749 // with the new pixel bounds.
12750 this._requestRedraw(layer);
12753 _updateStyle: function (layer) {
12754 this._updateDashArray(layer);
12755 this._requestRedraw(layer);
12758 _updateDashArray: function (layer) {
12759 if (typeof layer.options.dashArray === 'string') {
12760 var parts = layer.options.dashArray.split(/[, ]+/),
12764 for (i = 0; i < parts.length; i++) {
12765 dashValue = Number(parts[i]);
12766 // Ignore dash array containing invalid lengths
12767 if (isNaN(dashValue)) { return; }
12768 dashArray.push(dashValue);
12770 layer.options._dashArray = dashArray;
12772 layer.options._dashArray = layer.options.dashArray;
12776 _requestRedraw: function (layer) {
12777 if (!this._map) { return; }
12779 this._extendRedrawBounds(layer);
12780 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12783 _extendRedrawBounds: function (layer) {
12784 if (layer._pxBounds) {
12785 var padding = (layer.options.weight || 0) + 1;
12786 this._redrawBounds = this._redrawBounds || new Bounds();
12787 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12788 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12792 _redraw: function () {
12793 this._redrawRequest = null;
12795 if (this._redrawBounds) {
12796 this._redrawBounds.min._floor();
12797 this._redrawBounds.max._ceil();
12800 this._clear(); // clear layers in redraw bounds
12801 this._draw(); // draw layers
12803 this._redrawBounds = null;
12806 _clear: function () {
12807 var bounds = this._redrawBounds;
12809 var size = bounds.getSize();
12810 this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12813 this._ctx.setTransform(1, 0, 0, 1, 0, 0);
12814 this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12815 this._ctx.restore();
12819 _draw: function () {
12820 var layer, bounds = this._redrawBounds;
12823 var size = bounds.getSize();
12824 this._ctx.beginPath();
12825 this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12829 this._drawing = true;
12831 for (var order = this._drawFirst; order; order = order.next) {
12832 layer = order.layer;
12833 if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12834 layer._updatePath();
12838 this._drawing = false;
12840 this._ctx.restore(); // Restore state before clipping.
12843 _updatePoly: function (layer, closed) {
12844 if (!this._drawing) { return; }
12847 parts = layer._parts,
12848 len = parts.length,
12851 if (!len) { return; }
12855 for (i = 0; i < len; i++) {
12856 for (j = 0, len2 = parts[i].length; j < len2; j++) {
12858 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12865 this._fillStroke(ctx, layer);
12867 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12870 _updateCircle: function (layer) {
12872 if (!this._drawing || layer._empty()) { return; }
12874 var p = layer._point,
12876 r = Math.max(Math.round(layer._radius), 1),
12877 s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12885 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12891 this._fillStroke(ctx, layer);
12894 _fillStroke: function (ctx, layer) {
12895 var options = layer.options;
12897 if (options.fill) {
12898 ctx.globalAlpha = options.fillOpacity;
12899 ctx.fillStyle = options.fillColor || options.color;
12900 ctx.fill(options.fillRule || 'evenodd');
12903 if (options.stroke && options.weight !== 0) {
12904 if (ctx.setLineDash) {
12905 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12907 ctx.globalAlpha = options.opacity;
12908 ctx.lineWidth = options.weight;
12909 ctx.strokeStyle = options.color;
12910 ctx.lineCap = options.lineCap;
12911 ctx.lineJoin = options.lineJoin;
12916 // Canvas obviously doesn't have mouse events for individual drawn objects,
12917 // so we emulate that by calculating what's under the mouse on mousemove/click manually
12919 _onClick: function (e) {
12920 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12922 for (var order = this._drawFirst; order; order = order.next) {
12923 layer = order.layer;
12924 if (layer.options.interactive && layer._containsPoint(point)) {
12925 if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
12926 clickedLayer = layer;
12930 this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
12933 _onMouseMove: function (e) {
12934 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12936 var point = this._map.mouseEventToLayerPoint(e);
12937 this._handleMouseHover(e, point);
12941 _handleMouseOut: function (e) {
12942 var layer = this._hoveredLayer;
12944 // if we're leaving the layer, fire mouseout
12945 removeClass(this._container, 'leaflet-interactive');
12946 this._fireEvent([layer], e, 'mouseout');
12947 this._hoveredLayer = null;
12948 this._mouseHoverThrottled = false;
12952 _handleMouseHover: function (e, point) {
12953 if (this._mouseHoverThrottled) {
12957 var layer, candidateHoveredLayer;
12959 for (var order = this._drawFirst; order; order = order.next) {
12960 layer = order.layer;
12961 if (layer.options.interactive && layer._containsPoint(point)) {
12962 candidateHoveredLayer = layer;
12966 if (candidateHoveredLayer !== this._hoveredLayer) {
12967 this._handleMouseOut(e);
12969 if (candidateHoveredLayer) {
12970 addClass(this._container, 'leaflet-interactive'); // change cursor
12971 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12972 this._hoveredLayer = candidateHoveredLayer;
12976 this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);
12978 this._mouseHoverThrottled = true;
12979 setTimeout(bind(function () {
12980 this._mouseHoverThrottled = false;
12984 _fireEvent: function (layers, e, type) {
12985 this._map._fireDOMEvent(e, type || e.type, layers);
12988 _bringToFront: function (layer) {
12989 var order = layer._order;
12991 if (!order) { return; }
12993 var next = order.next;
12994 var prev = order.prev;
13005 // Update first entry unless this is the
13007 this._drawFirst = next;
13010 order.prev = this._drawLast;
13011 this._drawLast.next = order;
13014 this._drawLast = order;
13016 this._requestRedraw(layer);
13019 _bringToBack: function (layer) {
13020 var order = layer._order;
13022 if (!order) { return; }
13024 var next = order.next;
13025 var prev = order.prev;
13036 // Update last entry unless this is the
13038 this._drawLast = prev;
13043 order.next = this._drawFirst;
13044 this._drawFirst.prev = order;
13045 this._drawFirst = order;
13047 this._requestRedraw(layer);
13051 // @factory L.canvas(options?: Renderer options)
13052 // Creates a Canvas renderer with the given options.
13053 function canvas(options) {
13054 return Browser.canvas ? new Canvas(options) : null;
13058 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
13062 var vmlCreate = (function () {
13064 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
13065 return function (name) {
13066 return document.createElement('<lvml:' + name + ' class="lvml">');
13069 // Do not return fn from catch block so `e` can be garbage collected
13070 // See https://github.com/Leaflet/Leaflet/pull/7279
13072 return function (name) {
13073 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
13082 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
13083 * with old versions of Internet Explorer.
13086 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
13089 _initContainer: function () {
13090 this._container = create$1('div', 'leaflet-vml-container');
13093 _update: function () {
13094 if (this._map._animatingZoom) { return; }
13095 Renderer.prototype._update.call(this);
13096 this.fire('update');
13099 _initPath: function (layer) {
13100 var container = layer._container = vmlCreate('shape');
13102 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
13104 container.coordsize = '1 1';
13106 layer._path = vmlCreate('path');
13107 container.appendChild(layer._path);
13109 this._updateStyle(layer);
13110 this._layers[stamp(layer)] = layer;
13113 _addPath: function (layer) {
13114 var container = layer._container;
13115 this._container.appendChild(container);
13117 if (layer.options.interactive) {
13118 layer.addInteractiveTarget(container);
13122 _removePath: function (layer) {
13123 var container = layer._container;
13125 layer.removeInteractiveTarget(container);
13126 delete this._layers[stamp(layer)];
13129 _updateStyle: function (layer) {
13130 var stroke = layer._stroke,
13131 fill = layer._fill,
13132 options = layer.options,
13133 container = layer._container;
13135 container.stroked = !!options.stroke;
13136 container.filled = !!options.fill;
13138 if (options.stroke) {
13140 stroke = layer._stroke = vmlCreate('stroke');
13142 container.appendChild(stroke);
13143 stroke.weight = options.weight + 'px';
13144 stroke.color = options.color;
13145 stroke.opacity = options.opacity;
13147 if (options.dashArray) {
13148 stroke.dashStyle = isArray(options.dashArray) ?
13149 options.dashArray.join(' ') :
13150 options.dashArray.replace(/( *, *)/g, ' ');
13152 stroke.dashStyle = '';
13154 stroke.endcap = options.lineCap.replace('butt', 'flat');
13155 stroke.joinstyle = options.lineJoin;
13157 } else if (stroke) {
13158 container.removeChild(stroke);
13159 layer._stroke = null;
13162 if (options.fill) {
13164 fill = layer._fill = vmlCreate('fill');
13166 container.appendChild(fill);
13167 fill.color = options.fillColor || options.color;
13168 fill.opacity = options.fillOpacity;
13171 container.removeChild(fill);
13172 layer._fill = null;
13176 _updateCircle: function (layer) {
13177 var p = layer._point.round(),
13178 r = Math.round(layer._radius),
13179 r2 = Math.round(layer._radiusY || r);
13181 this._setPath(layer, layer._empty() ? 'M0 0' :
13182 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
13185 _setPath: function (layer, path) {
13186 layer._path.v = path;
13189 _bringToFront: function (layer) {
13190 toFront(layer._container);
13193 _bringToBack: function (layer) {
13194 toBack(layer._container);
13198 var create = Browser.vml ? vmlCreate : svgCreate;
13202 * @inherits Renderer
13205 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
13206 * Inherits `Renderer`.
13208 * Due to [technical limitations](https://caniuse.com/svg), SVG is not
13209 * available in all web browsers, notably Android 2.x and 3.x.
13211 * Although SVG is not available on IE7 and IE8, these browsers support
13212 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
13213 * (a now deprecated technology), and the SVG renderer will fall back to VML in
13218 * Use SVG by default for all paths in the map:
13221 * var map = L.map('map', {
13222 * renderer: L.svg()
13226 * Use a SVG renderer with extra padding for specific vector geometries:
13229 * var map = L.map('map');
13230 * var myRenderer = L.svg({ padding: 0.5 });
13231 * var line = L.polyline( coordinates, { renderer: myRenderer } );
13232 * var circle = L.circle( center, { renderer: myRenderer } );
13236 var SVG = Renderer.extend({
13238 _initContainer: function () {
13239 this._container = create('svg');
13241 // makes it possible to click through svg root; we'll reset it back in individual paths
13242 this._container.setAttribute('pointer-events', 'none');
13244 this._rootGroup = create('g');
13245 this._container.appendChild(this._rootGroup);
13248 _destroyContainer: function () {
13249 remove(this._container);
13250 off(this._container);
13251 delete this._container;
13252 delete this._rootGroup;
13253 delete this._svgSize;
13256 _update: function () {
13257 if (this._map._animatingZoom && this._bounds) { return; }
13259 Renderer.prototype._update.call(this);
13261 var b = this._bounds,
13262 size = b.getSize(),
13263 container = this._container;
13265 // set size of svg-container if changed
13266 if (!this._svgSize || !this._svgSize.equals(size)) {
13267 this._svgSize = size;
13268 container.setAttribute('width', size.x);
13269 container.setAttribute('height', size.y);
13272 // movement: update container viewBox so that we don't have to change coordinates of individual layers
13273 setPosition(container, b.min);
13274 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
13276 this.fire('update');
13279 // methods below are called by vector layers implementations
13281 _initPath: function (layer) {
13282 var path = layer._path = create('path');
13285 // @option className: String = null
13286 // Custom class name set on an element. Only for SVG renderer.
13287 if (layer.options.className) {
13288 addClass(path, layer.options.className);
13291 if (layer.options.interactive) {
13292 addClass(path, 'leaflet-interactive');
13295 this._updateStyle(layer);
13296 this._layers[stamp(layer)] = layer;
13299 _addPath: function (layer) {
13300 if (!this._rootGroup) { this._initContainer(); }
13301 this._rootGroup.appendChild(layer._path);
13302 layer.addInteractiveTarget(layer._path);
13305 _removePath: function (layer) {
13306 remove(layer._path);
13307 layer.removeInteractiveTarget(layer._path);
13308 delete this._layers[stamp(layer)];
13311 _updatePath: function (layer) {
13316 _updateStyle: function (layer) {
13317 var path = layer._path,
13318 options = layer.options;
13320 if (!path) { return; }
13322 if (options.stroke) {
13323 path.setAttribute('stroke', options.color);
13324 path.setAttribute('stroke-opacity', options.opacity);
13325 path.setAttribute('stroke-width', options.weight);
13326 path.setAttribute('stroke-linecap', options.lineCap);
13327 path.setAttribute('stroke-linejoin', options.lineJoin);
13329 if (options.dashArray) {
13330 path.setAttribute('stroke-dasharray', options.dashArray);
13332 path.removeAttribute('stroke-dasharray');
13335 if (options.dashOffset) {
13336 path.setAttribute('stroke-dashoffset', options.dashOffset);
13338 path.removeAttribute('stroke-dashoffset');
13341 path.setAttribute('stroke', 'none');
13344 if (options.fill) {
13345 path.setAttribute('fill', options.fillColor || options.color);
13346 path.setAttribute('fill-opacity', options.fillOpacity);
13347 path.setAttribute('fill-rule', options.fillRule || 'evenodd');
13349 path.setAttribute('fill', 'none');
13353 _updatePoly: function (layer, closed) {
13354 this._setPath(layer, pointsToPath(layer._parts, closed));
13357 _updateCircle: function (layer) {
13358 var p = layer._point,
13359 r = Math.max(Math.round(layer._radius), 1),
13360 r2 = Math.max(Math.round(layer._radiusY), 1) || r,
13361 arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
13363 // drawing a circle with two half-arcs
13364 var d = layer._empty() ? 'M0 0' :
13365 'M' + (p.x - r) + ',' + p.y +
13366 arc + (r * 2) + ',0 ' +
13367 arc + (-r * 2) + ',0 ';
13369 this._setPath(layer, d);
13372 _setPath: function (layer, path) {
13373 layer._path.setAttribute('d', path);
13376 // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
13377 _bringToFront: function (layer) {
13378 toFront(layer._path);
13381 _bringToBack: function (layer) {
13382 toBack(layer._path);
13387 SVG.include(vmlMixin);
13391 // @factory L.svg(options?: Renderer options)
13392 // Creates a SVG renderer with the given options.
13393 function svg(options) {
13394 return Browser.svg || Browser.vml ? new SVG(options) : null;
13398 // @namespace Map; @method getRenderer(layer: Path): Renderer
13399 // Returns the instance of `Renderer` that should be used to render the given
13400 // `Path`. It will ensure that the `renderer` options of the map and paths
13401 // are respected, and that the renderers do exist on the map.
13402 getRenderer: function (layer) {
13403 // @namespace Path; @option renderer: Renderer
13404 // Use this specific instance of `Renderer` for this path. Takes
13405 // precedence over the map's [default renderer](#map-renderer).
13406 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
13409 renderer = this._renderer = this._createRenderer();
13412 if (!this.hasLayer(renderer)) {
13413 this.addLayer(renderer);
13418 _getPaneRenderer: function (name) {
13419 if (name === 'overlayPane' || name === undefined) {
13423 var renderer = this._paneRenderers[name];
13424 if (renderer === undefined) {
13425 renderer = this._createRenderer({pane: name});
13426 this._paneRenderers[name] = renderer;
13431 _createRenderer: function (options) {
13432 // @namespace Map; @option preferCanvas: Boolean = false
13433 // Whether `Path`s should be rendered on a `Canvas` renderer.
13434 // By default, all `Path`s are rendered in a `SVG` renderer.
13435 return (this.options.preferCanvas && canvas(options)) || svg(options);
13440 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
13446 * @inherits Polygon
13448 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
13453 * // define rectangle geographical bounds
13454 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
13456 * // create an orange rectangle
13457 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
13459 * // zoom the map to the rectangle bounds
13460 * map.fitBounds(bounds);
13466 var Rectangle = Polygon.extend({
13467 initialize: function (latLngBounds, options) {
13468 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13471 // @method setBounds(latLngBounds: LatLngBounds): this
13472 // Redraws the rectangle with the passed bounds.
13473 setBounds: function (latLngBounds) {
13474 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13477 _boundsToLatLngs: function (latLngBounds) {
13478 latLngBounds = toLatLngBounds(latLngBounds);
13480 latLngBounds.getSouthWest(),
13481 latLngBounds.getNorthWest(),
13482 latLngBounds.getNorthEast(),
13483 latLngBounds.getSouthEast()
13489 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13490 function rectangle(latLngBounds, options) {
13491 return new Rectangle(latLngBounds, options);
13494 SVG.create = create;
13495 SVG.pointsToPath = pointsToPath;
13497 GeoJSON.geometryToLayer = geometryToLayer;
13498 GeoJSON.coordsToLatLng = coordsToLatLng;
13499 GeoJSON.coordsToLatLngs = coordsToLatLngs;
13500 GeoJSON.latLngToCoords = latLngToCoords;
13501 GeoJSON.latLngsToCoords = latLngsToCoords;
13502 GeoJSON.getFeature = getFeature;
13503 GeoJSON.asFeature = asFeature;
13506 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13507 * (zoom to a selected bounding box), enabled by default.
13511 // @section Interaction Options
13513 // @option boxZoom: Boolean = true
13514 // Whether the map can be zoomed to a rectangular area specified by
13515 // dragging the mouse while pressing the shift key.
13519 var BoxZoom = Handler.extend({
13520 initialize: function (map) {
13522 this._container = map._container;
13523 this._pane = map._panes.overlayPane;
13524 this._resetStateTimeout = 0;
13525 map.on('unload', this._destroy, this);
13528 addHooks: function () {
13529 on(this._container, 'mousedown', this._onMouseDown, this);
13532 removeHooks: function () {
13533 off(this._container, 'mousedown', this._onMouseDown, this);
13536 moved: function () {
13537 return this._moved;
13540 _destroy: function () {
13541 remove(this._pane);
13545 _resetState: function () {
13546 this._resetStateTimeout = 0;
13547 this._moved = false;
13550 _clearDeferredResetState: function () {
13551 if (this._resetStateTimeout !== 0) {
13552 clearTimeout(this._resetStateTimeout);
13553 this._resetStateTimeout = 0;
13557 _onMouseDown: function (e) {
13558 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13560 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13561 // will interrupt the interaction and orphan a box element in the container.
13562 this._clearDeferredResetState();
13563 this._resetState();
13565 disableTextSelection();
13566 disableImageDrag();
13568 this._startPoint = this._map.mouseEventToContainerPoint(e);
13572 mousemove: this._onMouseMove,
13573 mouseup: this._onMouseUp,
13574 keydown: this._onKeyDown
13578 _onMouseMove: function (e) {
13579 if (!this._moved) {
13580 this._moved = true;
13582 this._box = create$1('div', 'leaflet-zoom-box', this._container);
13583 addClass(this._container, 'leaflet-crosshair');
13585 this._map.fire('boxzoomstart');
13588 this._point = this._map.mouseEventToContainerPoint(e);
13590 var bounds = new Bounds(this._point, this._startPoint),
13591 size = bounds.getSize();
13593 setPosition(this._box, bounds.min);
13595 this._box.style.width = size.x + 'px';
13596 this._box.style.height = size.y + 'px';
13599 _finish: function () {
13602 removeClass(this._container, 'leaflet-crosshair');
13605 enableTextSelection();
13610 mousemove: this._onMouseMove,
13611 mouseup: this._onMouseUp,
13612 keydown: this._onKeyDown
13616 _onMouseUp: function (e) {
13617 if ((e.which !== 1) && (e.button !== 1)) { return; }
13621 if (!this._moved) { return; }
13622 // Postpone to next JS tick so internal click event handling
13623 // still see it as "moved".
13624 this._clearDeferredResetState();
13625 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13627 var bounds = new LatLngBounds(
13628 this._map.containerPointToLatLng(this._startPoint),
13629 this._map.containerPointToLatLng(this._point));
13633 .fire('boxzoomend', {boxZoomBounds: bounds});
13636 _onKeyDown: function (e) {
13637 if (e.keyCode === 27) {
13639 this._clearDeferredResetState();
13640 this._resetState();
13645 // @section Handlers
13646 // @property boxZoom: Handler
13647 // Box (shift-drag with mouse) zoom handler.
13648 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13651 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13655 // @section Interaction Options
13658 // @option doubleClickZoom: Boolean|String = true
13659 // Whether the map can be zoomed in by double clicking on it and
13660 // zoomed out by double clicking while holding shift. If passed
13661 // `'center'`, double-click zoom will zoom to the center of the
13662 // view regardless of where the mouse was.
13663 doubleClickZoom: true
13666 var DoubleClickZoom = Handler.extend({
13667 addHooks: function () {
13668 this._map.on('dblclick', this._onDoubleClick, this);
13671 removeHooks: function () {
13672 this._map.off('dblclick', this._onDoubleClick, this);
13675 _onDoubleClick: function (e) {
13676 var map = this._map,
13677 oldZoom = map.getZoom(),
13678 delta = map.options.zoomDelta,
13679 zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13681 if (map.options.doubleClickZoom === 'center') {
13684 map.setZoomAround(e.containerPoint, zoom);
13689 // @section Handlers
13691 // Map properties include interaction handlers that allow you to control
13692 // interaction behavior in runtime, enabling or disabling certain features such
13693 // as dragging or touch zoom (see `Handler` methods). For example:
13696 // map.doubleClickZoom.disable();
13699 // @property doubleClickZoom: Handler
13700 // Double click zoom handler.
13701 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13704 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13708 // @section Interaction Options
13710 // @option dragging: Boolean = true
13711 // Whether the map is draggable with mouse/touch or not.
13714 // @section Panning Inertia Options
13715 // @option inertia: Boolean = *
13716 // If enabled, panning of the map will have an inertia effect where
13717 // the map builds momentum while dragging and continues moving in
13718 // the same direction for some time. Feels especially nice on touch
13719 // devices. Enabled by default.
13722 // @option inertiaDeceleration: Number = 3000
13723 // The rate with which the inertial movement slows down, in pixels/second².
13724 inertiaDeceleration: 3400, // px/s^2
13726 // @option inertiaMaxSpeed: Number = Infinity
13727 // Max speed of the inertial movement, in pixels/second.
13728 inertiaMaxSpeed: Infinity, // px/s
13730 // @option easeLinearity: Number = 0.2
13731 easeLinearity: 0.2,
13733 // TODO refactor, move to CRS
13734 // @option worldCopyJump: Boolean = false
13735 // With this option enabled, the map tracks when you pan to another "copy"
13736 // of the world and seamlessly jumps to the original one so that all overlays
13737 // like markers and vector layers are still visible.
13738 worldCopyJump: false,
13740 // @option maxBoundsViscosity: Number = 0.0
13741 // If `maxBounds` is set, this option will control how solid the bounds
13742 // are when dragging the map around. The default value of `0.0` allows the
13743 // user to drag outside the bounds at normal speed, higher values will
13744 // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13745 // solid, preventing the user from dragging outside the bounds.
13746 maxBoundsViscosity: 0.0
13749 var Drag = Handler.extend({
13750 addHooks: function () {
13751 if (!this._draggable) {
13752 var map = this._map;
13754 this._draggable = new Draggable(map._mapPane, map._container);
13756 this._draggable.on({
13757 dragstart: this._onDragStart,
13758 drag: this._onDrag,
13759 dragend: this._onDragEnd
13762 this._draggable.on('predrag', this._onPreDragLimit, this);
13763 if (map.options.worldCopyJump) {
13764 this._draggable.on('predrag', this._onPreDragWrap, this);
13765 map.on('zoomend', this._onZoomEnd, this);
13767 map.whenReady(this._onZoomEnd, this);
13770 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13771 this._draggable.enable();
13772 this._positions = [];
13776 removeHooks: function () {
13777 removeClass(this._map._container, 'leaflet-grab');
13778 removeClass(this._map._container, 'leaflet-touch-drag');
13779 this._draggable.disable();
13782 moved: function () {
13783 return this._draggable && this._draggable._moved;
13786 moving: function () {
13787 return this._draggable && this._draggable._moving;
13790 _onDragStart: function () {
13791 var map = this._map;
13794 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13795 var bounds = toLatLngBounds(this._map.options.maxBounds);
13797 this._offsetLimit = toBounds(
13798 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13799 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13800 .add(this._map.getSize()));
13802 this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13804 this._offsetLimit = null;
13809 .fire('dragstart');
13811 if (map.options.inertia) {
13812 this._positions = [];
13817 _onDrag: function (e) {
13818 if (this._map.options.inertia) {
13819 var time = this._lastTime = +new Date(),
13820 pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13822 this._positions.push(pos);
13823 this._times.push(time);
13825 this._prunePositions(time);
13833 _prunePositions: function (time) {
13834 while (this._positions.length > 1 && time - this._times[0] > 50) {
13835 this._positions.shift();
13836 this._times.shift();
13840 _onZoomEnd: function () {
13841 var pxCenter = this._map.getSize().divideBy(2),
13842 pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13844 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13845 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13848 _viscousLimit: function (value, threshold) {
13849 return value - (value - threshold) * this._viscosity;
13852 _onPreDragLimit: function () {
13853 if (!this._viscosity || !this._offsetLimit) { return; }
13855 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13857 var limit = this._offsetLimit;
13858 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13859 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13860 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13861 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13863 this._draggable._newPos = this._draggable._startPos.add(offset);
13866 _onPreDragWrap: function () {
13867 // TODO refactor to be able to adjust map pane position after zoom
13868 var worldWidth = this._worldWidth,
13869 halfWidth = Math.round(worldWidth / 2),
13870 dx = this._initialWorldOffset,
13871 x = this._draggable._newPos.x,
13872 newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13873 newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13874 newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13876 this._draggable._absPos = this._draggable._newPos.clone();
13877 this._draggable._newPos.x = newX;
13880 _onDragEnd: function (e) {
13881 var map = this._map,
13882 options = map.options,
13884 noInertia = !options.inertia || e.noInertia || this._times.length < 2;
13886 map.fire('dragend', e);
13889 map.fire('moveend');
13892 this._prunePositions(+new Date());
13894 var direction = this._lastPos.subtract(this._positions[0]),
13895 duration = (this._lastTime - this._times[0]) / 1000,
13896 ease = options.easeLinearity,
13898 speedVector = direction.multiplyBy(ease / duration),
13899 speed = speedVector.distanceTo([0, 0]),
13901 limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13902 limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13904 decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13905 offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13907 if (!offset.x && !offset.y) {
13908 map.fire('moveend');
13911 offset = map._limitOffset(offset, map.options.maxBounds);
13913 requestAnimFrame(function () {
13914 map.panBy(offset, {
13915 duration: decelerationDuration,
13916 easeLinearity: ease,
13926 // @section Handlers
13927 // @property dragging: Handler
13928 // Map dragging handler (by both mouse and touch).
13929 Map.addInitHook('addHandler', 'dragging', Drag);
13932 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13936 // @section Keyboard Navigation Options
13938 // @option keyboard: Boolean = true
13939 // Makes the map focusable and allows users to navigate the map with keyboard
13940 // arrows and `+`/`-` keys.
13943 // @option keyboardPanDelta: Number = 80
13944 // Amount of pixels to pan when pressing an arrow key.
13945 keyboardPanDelta: 80
13948 var Keyboard = Handler.extend({
13955 zoomIn: [187, 107, 61, 171],
13956 zoomOut: [189, 109, 54, 173]
13959 initialize: function (map) {
13962 this._setPanDelta(map.options.keyboardPanDelta);
13963 this._setZoomDelta(map.options.zoomDelta);
13966 addHooks: function () {
13967 var container = this._map._container;
13969 // make the container focusable by tabbing
13970 if (container.tabIndex <= 0) {
13971 container.tabIndex = '0';
13975 focus: this._onFocus,
13976 blur: this._onBlur,
13977 mousedown: this._onMouseDown
13981 focus: this._addHooks,
13982 blur: this._removeHooks
13986 removeHooks: function () {
13987 this._removeHooks();
13989 off(this._map._container, {
13990 focus: this._onFocus,
13991 blur: this._onBlur,
13992 mousedown: this._onMouseDown
13996 focus: this._addHooks,
13997 blur: this._removeHooks
14001 _onMouseDown: function () {
14002 if (this._focused) { return; }
14004 var body = document.body,
14005 docEl = document.documentElement,
14006 top = body.scrollTop || docEl.scrollTop,
14007 left = body.scrollLeft || docEl.scrollLeft;
14009 this._map._container.focus();
14011 window.scrollTo(left, top);
14014 _onFocus: function () {
14015 this._focused = true;
14016 this._map.fire('focus');
14019 _onBlur: function () {
14020 this._focused = false;
14021 this._map.fire('blur');
14024 _setPanDelta: function (panDelta) {
14025 var keys = this._panKeys = {},
14026 codes = this.keyCodes,
14029 for (i = 0, len = codes.left.length; i < len; i++) {
14030 keys[codes.left[i]] = [-1 * panDelta, 0];
14032 for (i = 0, len = codes.right.length; i < len; i++) {
14033 keys[codes.right[i]] = [panDelta, 0];
14035 for (i = 0, len = codes.down.length; i < len; i++) {
14036 keys[codes.down[i]] = [0, panDelta];
14038 for (i = 0, len = codes.up.length; i < len; i++) {
14039 keys[codes.up[i]] = [0, -1 * panDelta];
14043 _setZoomDelta: function (zoomDelta) {
14044 var keys = this._zoomKeys = {},
14045 codes = this.keyCodes,
14048 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
14049 keys[codes.zoomIn[i]] = zoomDelta;
14051 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
14052 keys[codes.zoomOut[i]] = -zoomDelta;
14056 _addHooks: function () {
14057 on(document, 'keydown', this._onKeyDown, this);
14060 _removeHooks: function () {
14061 off(document, 'keydown', this._onKeyDown, this);
14064 _onKeyDown: function (e) {
14065 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
14067 var key = e.keyCode,
14071 if (key in this._panKeys) {
14072 if (!map._panAnim || !map._panAnim._inProgress) {
14073 offset = this._panKeys[key];
14075 offset = toPoint(offset).multiplyBy(3);
14078 if (map.options.maxBounds) {
14079 offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
14082 if (map.options.worldCopyJump) {
14083 var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
14084 map.panTo(newLatLng);
14089 } else if (key in this._zoomKeys) {
14090 map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
14092 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
14103 // @section Handlers
14104 // @section Handlers
14105 // @property keyboard: Handler
14106 // Keyboard navigation handler.
14107 Map.addInitHook('addHandler', 'keyboard', Keyboard);
14110 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
14114 // @section Interaction Options
14116 // @section Mouse wheel options
14117 // @option scrollWheelZoom: Boolean|String = true
14118 // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
14119 // it will zoom to the center of the view regardless of where the mouse was.
14120 scrollWheelZoom: true,
14122 // @option wheelDebounceTime: Number = 40
14123 // Limits the rate at which a wheel can fire (in milliseconds). By default
14124 // user can't zoom via wheel more often than once per 40 ms.
14125 wheelDebounceTime: 40,
14127 // @option wheelPxPerZoomLevel: Number = 60
14128 // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
14129 // mean a change of one full zoom level. Smaller values will make wheel-zooming
14130 // faster (and vice versa).
14131 wheelPxPerZoomLevel: 60
14134 var ScrollWheelZoom = Handler.extend({
14135 addHooks: function () {
14136 on(this._map._container, 'wheel', this._onWheelScroll, this);
14141 removeHooks: function () {
14142 off(this._map._container, 'wheel', this._onWheelScroll, this);
14145 _onWheelScroll: function (e) {
14146 var delta = getWheelDelta(e);
14148 var debounce = this._map.options.wheelDebounceTime;
14150 this._delta += delta;
14151 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
14153 if (!this._startTime) {
14154 this._startTime = +new Date();
14157 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
14159 clearTimeout(this._timer);
14160 this._timer = setTimeout(bind(this._performZoom, this), left);
14165 _performZoom: function () {
14166 var map = this._map,
14167 zoom = map.getZoom(),
14168 snap = this._map.options.zoomSnap || 0;
14170 map._stop(); // stop panning and fly animations if any
14172 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
14173 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
14174 d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
14175 d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
14176 delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
14179 this._startTime = null;
14181 if (!delta) { return; }
14183 if (map.options.scrollWheelZoom === 'center') {
14184 map.setZoom(zoom + delta);
14186 map.setZoomAround(this._lastMousePos, zoom + delta);
14191 // @section Handlers
14192 // @property scrollWheelZoom: Handler
14193 // Scroll wheel zoom handler.
14194 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
14197 * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
14198 * which otherwise is not fired by mobile Safari.
14201 var tapHoldDelay = 600;
14204 // @section Interaction Options
14206 // @section Touch interaction options
14207 // @option tapHold: Boolean
14208 // Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
14209 tapHold: Browser.touchNative && Browser.safari && Browser.mobile,
14211 // @option tapTolerance: Number = 15
14212 // The max number of pixels a user can shift his finger during touch
14213 // for it to be considered a valid tap.
14217 var TapHold = Handler.extend({
14218 addHooks: function () {
14219 on(this._map._container, 'touchstart', this._onDown, this);
14222 removeHooks: function () {
14223 off(this._map._container, 'touchstart', this._onDown, this);
14226 _onDown: function (e) {
14227 clearTimeout(this._holdTimeout);
14228 if (e.touches.length !== 1) { return; }
14230 var first = e.touches[0];
14231 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
14233 this._holdTimeout = setTimeout(bind(function () {
14235 if (!this._isTapValid()) { return; }
14237 // prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
14238 on(document, 'touchend', preventDefault);
14239 on(document, 'touchend touchcancel', this._cancelClickPrevent);
14240 this._simulateEvent('contextmenu', first);
14241 }, this), tapHoldDelay);
14243 on(document, 'touchend touchcancel contextmenu', this._cancel, this);
14244 on(document, 'touchmove', this._onMove, this);
14247 _cancelClickPrevent: function cancelClickPrevent() {
14248 off(document, 'touchend', preventDefault);
14249 off(document, 'touchend touchcancel', cancelClickPrevent);
14252 _cancel: function () {
14253 clearTimeout(this._holdTimeout);
14254 off(document, 'touchend touchcancel contextmenu', this._cancel, this);
14255 off(document, 'touchmove', this._onMove, this);
14258 _onMove: function (e) {
14259 var first = e.touches[0];
14260 this._newPos = new Point(first.clientX, first.clientY);
14263 _isTapValid: function () {
14264 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
14267 _simulateEvent: function (type, e) {
14268 var simulatedEvent = new MouseEvent(type, {
14273 screenX: e.screenX,
14274 screenY: e.screenY,
14275 clientX: e.clientX,
14276 clientY: e.clientY,
14281 simulatedEvent._simulated = true;
14283 e.target.dispatchEvent(simulatedEvent);
14287 // @section Handlers
14288 // @property tapHold: Handler
14289 // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
14290 Map.addInitHook('addHandler', 'tapHold', TapHold);
14293 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
14297 // @section Interaction Options
14299 // @section Touch interaction options
14300 // @option touchZoom: Boolean|String = *
14301 // Whether the map can be zoomed by touch-dragging with two fingers. If
14302 // passed `'center'`, it will zoom to the center of the view regardless of
14303 // where the touch events (fingers) were. Enabled for touch-capable web
14305 touchZoom: Browser.touch,
14307 // @option bounceAtZoomLimits: Boolean = true
14308 // Set it to false if you don't want the map to zoom beyond min/max zoom
14309 // and then bounce back when pinch-zooming.
14310 bounceAtZoomLimits: true
14313 var TouchZoom = Handler.extend({
14314 addHooks: function () {
14315 addClass(this._map._container, 'leaflet-touch-zoom');
14316 on(this._map._container, 'touchstart', this._onTouchStart, this);
14319 removeHooks: function () {
14320 removeClass(this._map._container, 'leaflet-touch-zoom');
14321 off(this._map._container, 'touchstart', this._onTouchStart, this);
14324 _onTouchStart: function (e) {
14325 var map = this._map;
14326 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
14328 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
14329 p2 = map.mouseEventToContainerPoint(e.touches[1]);
14331 this._centerPoint = map.getSize()._divideBy(2);
14332 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
14333 if (map.options.touchZoom !== 'center') {
14334 this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
14337 this._startDist = p1.distanceTo(p2);
14338 this._startZoom = map.getZoom();
14340 this._moved = false;
14341 this._zooming = true;
14345 on(document, 'touchmove', this._onTouchMove, this);
14346 on(document, 'touchend touchcancel', this._onTouchEnd, this);
14351 _onTouchMove: function (e) {
14352 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
14354 var map = this._map,
14355 p1 = map.mouseEventToContainerPoint(e.touches[0]),
14356 p2 = map.mouseEventToContainerPoint(e.touches[1]),
14357 scale = p1.distanceTo(p2) / this._startDist;
14359 this._zoom = map.getScaleZoom(scale, this._startZoom);
14361 if (!map.options.bounceAtZoomLimits && (
14362 (this._zoom < map.getMinZoom() && scale < 1) ||
14363 (this._zoom > map.getMaxZoom() && scale > 1))) {
14364 this._zoom = map._limitZoom(this._zoom);
14367 if (map.options.touchZoom === 'center') {
14368 this._center = this._startLatLng;
14369 if (scale === 1) { return; }
14371 // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
14372 var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
14373 if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
14374 this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
14377 if (!this._moved) {
14378 map._moveStart(true, false);
14379 this._moved = true;
14382 cancelAnimFrame(this._animRequest);
14384 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
14385 this._animRequest = requestAnimFrame(moveFn, this, true);
14390 _onTouchEnd: function () {
14391 if (!this._moved || !this._zooming) {
14392 this._zooming = false;
14396 this._zooming = false;
14397 cancelAnimFrame(this._animRequest);
14399 off(document, 'touchmove', this._onTouchMove, this);
14400 off(document, 'touchend touchcancel', this._onTouchEnd, this);
14402 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
14403 if (this._map.options.zoomAnimation) {
14404 this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
14406 this._map._resetView(this._center, this._map._limitZoom(this._zoom));
14411 // @section Handlers
14412 // @property touchZoom: Handler
14413 // Touch zoom handler.
14414 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
14416 Map.BoxZoom = BoxZoom;
14417 Map.DoubleClickZoom = DoubleClickZoom;
14419 Map.Keyboard = Keyboard;
14420 Map.ScrollWheelZoom = ScrollWheelZoom;
14421 Map.TapHold = TapHold;
14422 Map.TouchZoom = TouchZoom;
14424 exports.Bounds = Bounds;
14425 exports.Browser = Browser;
14427 exports.Canvas = Canvas;
14428 exports.Circle = Circle;
14429 exports.CircleMarker = CircleMarker;
14430 exports.Class = Class;
14431 exports.Control = Control;
14432 exports.DivIcon = DivIcon;
14433 exports.DivOverlay = DivOverlay;
14434 exports.DomEvent = DomEvent;
14435 exports.DomUtil = DomUtil;
14436 exports.Draggable = Draggable;
14437 exports.Evented = Evented;
14438 exports.FeatureGroup = FeatureGroup;
14439 exports.GeoJSON = GeoJSON;
14440 exports.GridLayer = GridLayer;
14441 exports.Handler = Handler;
14442 exports.Icon = Icon;
14443 exports.ImageOverlay = ImageOverlay;
14444 exports.LatLng = LatLng;
14445 exports.LatLngBounds = LatLngBounds;
14446 exports.Layer = Layer;
14447 exports.LayerGroup = LayerGroup;
14448 exports.LineUtil = LineUtil;
14450 exports.Marker = Marker;
14451 exports.Mixin = Mixin;
14452 exports.Path = Path;
14453 exports.Point = Point;
14454 exports.PolyUtil = PolyUtil;
14455 exports.Polygon = Polygon;
14456 exports.Polyline = Polyline;
14457 exports.Popup = Popup;
14458 exports.PosAnimation = PosAnimation;
14459 exports.Projection = index;
14460 exports.Rectangle = Rectangle;
14461 exports.Renderer = Renderer;
14463 exports.SVGOverlay = SVGOverlay;
14464 exports.TileLayer = TileLayer;
14465 exports.Tooltip = Tooltip;
14466 exports.Transformation = Transformation;
14467 exports.Util = Util;
14468 exports.VideoOverlay = VideoOverlay;
14469 exports.bind = bind;
14470 exports.bounds = toBounds;
14471 exports.canvas = canvas;
14472 exports.circle = circle;
14473 exports.circleMarker = circleMarker;
14474 exports.control = control;
14475 exports.divIcon = divIcon;
14476 exports.extend = extend;
14477 exports.featureGroup = featureGroup;
14478 exports.geoJSON = geoJSON;
14479 exports.geoJson = geoJson;
14480 exports.gridLayer = gridLayer;
14481 exports.icon = icon;
14482 exports.imageOverlay = imageOverlay;
14483 exports.latLng = toLatLng;
14484 exports.latLngBounds = toLatLngBounds;
14485 exports.layerGroup = layerGroup;
14486 exports.map = createMap;
14487 exports.marker = marker;
14488 exports.point = toPoint;
14489 exports.polygon = polygon;
14490 exports.polyline = polyline;
14491 exports.popup = popup;
14492 exports.rectangle = rectangle;
14493 exports.setOptions = setOptions;
14494 exports.stamp = stamp;
14496 exports.svgOverlay = svgOverlay;
14497 exports.tileLayer = tileLayer;
14498 exports.tooltip = tooltip;
14499 exports.transformation = toTransformation;
14500 exports.version = version;
14501 exports.videoOverlay = videoOverlay;
14503 var oldL = window.L;
14504 exports.noConflict = function() {
14508 // Always export us to window global (see #2364)
14509 window.L = exports;
14512 //# sourceMappingURL=leaflet-src.js.map