leaflet
Version:
JavaScript library for mobile-friendly interactive maps
1,581 lines (1,316 loc) • 450 kB
JavaScript
/* @preserve
* Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com
* (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {}));
})(this, (function (exports) { 'use strict';
var version = "1.9.4";
/*
* @namespace Util
*
* Various utility functions, used by Leaflet internally.
*/
// @function extend(dest: Object, src?: Object): Object
// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
function extend(dest) {
var i, j, len, src;
for (j = 1, len = arguments.length; j < len; j++) {
src = arguments[j];
for (i in src) {
dest[i] = src[i];
}
}
return dest;
}
// @function create(proto: Object, properties?: Object): Object
// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
var create$2 = Object.create || (function () {
function F() {}
return function (proto) {
F.prototype = proto;
return new F();
};
})();
// @function bind(fn: Function, …): Function
// 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).
// Has a `L.bind()` shortcut.
function bind(fn, obj) {
var slice = Array.prototype.slice;
if (fn.bind) {
return fn.bind.apply(fn, slice.call(arguments, 1));
}
var args = slice.call(arguments, 2);
return function () {
return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
};
}
// @property lastId: Number
// Last unique ID used by [`stamp()`](#util-stamp)
var lastId = 0;
// @function stamp(obj: Object): Number
// Returns the unique ID of an object, assigning it one if it doesn't have it.
function stamp(obj) {
if (!('_leaflet_id' in obj)) {
obj['_leaflet_id'] = ++lastId;
}
return obj._leaflet_id;
}
// @function throttle(fn: Function, time: Number, context: Object): Function
// Returns a function which executes function `fn` with the given scope `context`
// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
// `fn` will be called no more than one time per given amount of `time`. The arguments
// received by the bound function will be any arguments passed when binding the
// function, followed by any arguments passed when invoking the bound function.
// Has an `L.throttle` shortcut.
function throttle(fn, time, context) {
var lock, args, wrapperFn, later;
later = function () {
// reset lock and call if queued
lock = false;
if (args) {
wrapperFn.apply(context, args);
args = false;
}
};
wrapperFn = function () {
if (lock) {
// called too soon, queue to call later
args = arguments;
} else {
// call and lock until later
fn.apply(context, arguments);
setTimeout(later, time);
lock = true;
}
};
return wrapperFn;
}
// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
// Returns the number `num` modulo `range` in such a way so it lies within
// `range[0]` and `range[1]`. The returned value will be always smaller than
// `range[1]` unless `includeMax` is set to `true`.
function wrapNum(x, range, includeMax) {
var max = range[1],
min = range[0],
d = max - min;
return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
}
// @function falseFn(): Function
// Returns a function which always returns `false`.
function falseFn() { return false; }
// @function formatNum(num: Number, precision?: Number|false): Number
// Returns the number `num` rounded with specified `precision`.
// The default `precision` value is 6 decimal places.
// `false` can be passed to skip any processing (can be useful to avoid round-off errors).
function formatNum(num, precision) {
if (precision === false) { return num; }
var pow = Math.pow(10, precision === undefined ? 6 : precision);
return Math.round(num * pow) / pow;
}
// @function trim(str: String): String
// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
function trim(str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}
// @function splitWords(str: String): String[]
// Trims and splits the string on whitespace and returns the array of parts.
function splitWords(str) {
return trim(str).split(/\s+/);
}
// @function setOptions(obj: Object, options: Object): Object
// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
function setOptions(obj, options) {
if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
obj.options = obj.options ? create$2(obj.options) : {};
}
for (var i in options) {
obj.options[i] = options[i];
}
return obj.options;
}
// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
// be appended at the end. If `uppercase` is `true`, the parameter names will
// be uppercased (e.g. `'?A=foo&B=bar'`)
function getParamString(obj, existingUrl, uppercase) {
var params = [];
for (var i in obj) {
params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
}
return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
}
var templateRe = /\{ *([\w_ -]+) *\}/g;
// @function template(str: String, data: Object): String
// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
// `('Hello foo, bar')`. You can also specify functions instead of strings for
// data values — they will be evaluated passing `data` as an argument.
function template(str, data) {
return str.replace(templateRe, function (str, key) {
var value = data[key];
if (value === undefined) {
throw new Error('No value provided for variable ' + str);
} else if (typeof value === 'function') {
value = value(data);
}
return value;
});
}
// @function isArray(obj): Boolean
// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
var isArray = Array.isArray || function (obj) {
return (Object.prototype.toString.call(obj) === '[object Array]');
};
// @function indexOf(array: Array, el: Object): Number
// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
function indexOf(array, el) {
for (var i = 0; i < array.length; i++) {
if (array[i] === el) { return i; }
}
return -1;
}
// @property emptyImageUrl: String
// Data URI string containing a base64-encoded empty GIF image.
// Used as a hack to free memory from unused images on WebKit-powered
// mobile devices (by setting image `src` to this string).
var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
// inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/
function getPrefixed(name) {
return window['webkit' + name] || window['moz' + name] || window['ms' + name];
}
var lastTime = 0;
// fallback for IE 7-8
function timeoutDefer(fn) {
var time = +new Date(),
timeToCall = Math.max(0, 16 - (time - lastTime));
lastTime = time + timeToCall;
return window.setTimeout(fn, timeToCall);
}
var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
// `context` if given. When `immediate` is set, `fn` is called immediately if
// the browser doesn't have native support for
// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
function requestAnimFrame(fn, context, immediate) {
if (immediate && requestFn === timeoutDefer) {
fn.call(context);
} else {
return requestFn.call(window, bind(fn, context));
}
}
// @function cancelAnimFrame(id: Number): undefined
// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
function cancelAnimFrame(id) {
if (id) {
cancelFn.call(window, id);
}
}
var Util = {
__proto__: null,
extend: extend,
create: create$2,
bind: bind,
get lastId () { return lastId; },
stamp: stamp,
throttle: throttle,
wrapNum: wrapNum,
falseFn: falseFn,
formatNum: formatNum,
trim: trim,
splitWords: splitWords,
setOptions: setOptions,
getParamString: getParamString,
template: template,
isArray: isArray,
indexOf: indexOf,
emptyImageUrl: emptyImageUrl,
requestFn: requestFn,
cancelFn: cancelFn,
requestAnimFrame: requestAnimFrame,
cancelAnimFrame: cancelAnimFrame
};
// @class Class
// @aka L.Class
// @section
// @uninheritable
// Thanks to John Resig and Dean Edwards for inspiration!
function Class() {}
Class.extend = function (props) {
// @function extend(props: Object): Function
// [Extends the current class](#class-inheritance) given the properties to be included.
// Returns a Javascript function that is a class constructor (to be called with `new`).
var NewClass = function () {
setOptions(this);
// call the constructor
if (this.initialize) {
this.initialize.apply(this, arguments);
}
// call all constructor hooks
this.callInitHooks();
};
var parentProto = NewClass.__super__ = this.prototype;
var proto = create$2(parentProto);
proto.constructor = NewClass;
NewClass.prototype = proto;
// inherit parent's statics
for (var i in this) {
if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
NewClass[i] = this[i];
}
}
// mix static properties into the class
if (props.statics) {
extend(NewClass, props.statics);
}
// mix includes into the prototype
if (props.includes) {
checkDeprecatedMixinEvents(props.includes);
extend.apply(null, [proto].concat(props.includes));
}
// mix given properties into the prototype
extend(proto, props);
delete proto.statics;
delete proto.includes;
// merge options
if (proto.options) {
proto.options = parentProto.options ? create$2(parentProto.options) : {};
extend(proto.options, props.options);
}
proto._initHooks = [];
// add method for calling all hooks
proto.callInitHooks = function () {
if (this._initHooksCalled) { return; }
if (parentProto.callInitHooks) {
parentProto.callInitHooks.call(this);
}
this._initHooksCalled = true;
for (var i = 0, len = proto._initHooks.length; i < len; i++) {
proto._initHooks[i].call(this);
}
};
return NewClass;
};
// @function include(properties: Object): this
// [Includes a mixin](#class-includes) into the current class.
Class.include = function (props) {
var parentOptions = this.prototype.options;
extend(this.prototype, props);
if (props.options) {
this.prototype.options = parentOptions;
this.mergeOptions(props.options);
}
return this;
};
// @function mergeOptions(options: Object): this
// [Merges `options`](#class-options) into the defaults of the class.
Class.mergeOptions = function (options) {
extend(this.prototype.options, options);
return this;
};
// @function addInitHook(fn: Function): this
// Adds a [constructor hook](#class-constructor-hooks) to the class.
Class.addInitHook = function (fn) { // (Function) || (String, args...)
var args = Array.prototype.slice.call(arguments, 1);
var init = typeof fn === 'function' ? fn : function () {
this[fn].apply(this, args);
};
this.prototype._initHooks = this.prototype._initHooks || [];
this.prototype._initHooks.push(init);
return this;
};
function checkDeprecatedMixinEvents(includes) {
/* global L: true */
if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
includes = isArray(includes) ? includes : [includes];
for (var i = 0; i < includes.length; i++) {
if (includes[i] === L.Mixin.Events) {
console.warn('Deprecated include of L.Mixin.Events: ' +
'this property will be removed in future releases, ' +
'please inherit from L.Evented instead.', new Error().stack);
}
}
}
/*
* @class Evented
* @aka L.Evented
* @inherits Class
*
* 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).
*
* @example
*
* ```js
* map.on('click', function(e) {
* alert(e.latlng);
* } );
* ```
*
* Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
*
* ```js
* function onClick(e) { ... }
*
* map.on('click', onClick);
* map.off('click', onClick);
* ```
*/
var Events = {
/* @method on(type: String, fn: Function, context?: Object): this
* 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'`).
*
* @alternative
* @method on(eventMap: Object): this
* Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
*/
on: function (types, fn, context) {
// types can be a map of types/handlers
if (typeof types === 'object') {
for (var type in types) {
// we don't process space-separated events here for performance;
// it's a hot path since Layer uses the on(obj) syntax
this._on(type, types[type], fn);
}
} else {
// types can be a string of space-separated words
types = splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
this._on(types[i], fn, context);
}
}
return this;
},
/* @method off(type: String, fn?: Function, context?: Object): this
* 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.
*
* @alternative
* @method off(eventMap: Object): this
* Removes a set of type/listener pairs.
*
* @alternative
* @method off: this
* Removes all listeners to all events on the object. This includes implicitly attached events.
*/
off: function (types, fn, context) {
if (!arguments.length) {
// clear all listeners if called without arguments
delete this._events;
} else if (typeof types === 'object') {
for (var type in types) {
this._off(type, types[type], fn);
}
} else {
types = splitWords(types);
var removeAll = arguments.length === 1;
for (var i = 0, len = types.length; i < len; i++) {
if (removeAll) {
this._off(types[i]);
} else {
this._off(types[i], fn, context);
}
}
}
return this;
},
// attach listener (without syntactic sugar now)
_on: function (type, fn, context, _once) {
if (typeof fn !== 'function') {
console.warn('wrong listener type: ' + typeof fn);
return;
}
// check if fn already there
if (this._listens(type, fn, context) !== false) {
return;
}
if (context === this) {
// Less memory footprint.
context = undefined;
}
var newListener = {fn: fn, ctx: context};
if (_once) {
newListener.once = true;
}
this._events = this._events || {};
this._events[type] = this._events[type] || [];
this._events[type].push(newListener);
},
_off: function (type, fn, context) {
var listeners,
i,
len;
if (!this._events) {
return;
}
listeners = this._events[type];
if (!listeners) {
return;
}
if (arguments.length === 1) { // remove all
if (this._firingCount) {
// Set all removed listeners to noop
// so they are not called if remove happens in fire
for (i = 0, len = listeners.length; i < len; i++) {
listeners[i].fn = falseFn;
}
}
// clear all listeners for a type if function isn't specified
delete this._events[type];
return;
}
if (typeof fn !== 'function') {
console.warn('wrong listener type: ' + typeof fn);
return;
}
// find fn and remove it
var index = this._listens(type, fn, context);
if (index !== false) {
var listener = listeners[index];
if (this._firingCount) {
// set the removed listener to noop so that's not called if remove happens in fire
listener.fn = falseFn;
/* copy array in case events are being fired */
this._events[type] = listeners = listeners.slice();
}
listeners.splice(index, 1);
}
},
// @method fire(type: String, data?: Object, propagate?: Boolean): this
// Fires an event of the specified type. You can optionally provide a data
// object — the first argument of the listener function will contain its
// properties. The event can optionally be propagated to event parents.
fire: function (type, data, propagate) {
if (!this.listens(type, propagate)) { return this; }
var event = extend({}, data, {
type: type,
target: this,
sourceTarget: data && data.sourceTarget || this
});
if (this._events) {
var listeners = this._events[type];
if (listeners) {
this._firingCount = (this._firingCount + 1) || 1;
for (var i = 0, len = listeners.length; i < len; i++) {
var l = listeners[i];
// off overwrites l.fn, so we need to copy fn to a var
var fn = l.fn;
if (l.once) {
this.off(type, fn, l.ctx);
}
fn.call(l.ctx || this, event);
}
this._firingCount--;
}
}
if (propagate) {
// propagate the event to parents (set with addEventParent)
this._propagateEvent(event);
}
return this;
},
// @method listens(type: String, propagate?: Boolean): Boolean
// @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
// Returns `true` if a particular event type has any listeners attached to it.
// The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
listens: function (type, fn, context, propagate) {
if (typeof type !== 'string') {
console.warn('"string" type argument expected');
}
// we don't overwrite the input `fn` value, because we need to use it for propagation
var _fn = fn;
if (typeof fn !== 'function') {
propagate = !!fn;
_fn = undefined;
context = undefined;
}
var listeners = this._events && this._events[type];
if (listeners && listeners.length) {
if (this._listens(type, _fn, context) !== false) {
return true;
}
}
if (propagate) {
// also check parents for listeners if event propagates
for (var id in this._eventParents) {
if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }
}
}
return false;
},
// returns the index (number) or false
_listens: function (type, fn, context) {
if (!this._events) {
return false;
}
var listeners = this._events[type] || [];
if (!fn) {
return !!listeners.length;
}
if (context === this) {
// Less memory footprint.
context = undefined;
}
for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i].fn === fn && listeners[i].ctx === context) {
return i;
}
}
return false;
},
// @method once(…): this
// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
once: function (types, fn, context) {
// types can be a map of types/handlers
if (typeof types === 'object') {
for (var type in types) {
// we don't process space-separated events here for performance;
// it's a hot path since Layer uses the on(obj) syntax
this._on(type, types[type], fn, true);
}
} else {
// types can be a string of space-separated words
types = splitWords(types);
for (var i = 0, len = types.length; i < len; i++) {
this._on(types[i], fn, context, true);
}
}
return this;
},
// @method addEventParent(obj: Evented): this
// Adds an event parent - an `Evented` that will receive propagated events
addEventParent: function (obj) {
this._eventParents = this._eventParents || {};
this._eventParents[stamp(obj)] = obj;
return this;
},
// @method removeEventParent(obj: Evented): this
// Removes an event parent, so it will stop receiving propagated events
removeEventParent: function (obj) {
if (this._eventParents) {
delete this._eventParents[stamp(obj)];
}
return this;
},
_propagateEvent: function (e) {
for (var id in this._eventParents) {
this._eventParents[id].fire(e.type, extend({
layer: e.target,
propagatedFrom: e.target
}, e), true);
}
}
};
// aliases; we should ditch those eventually
// @method addEventListener(…): this
// Alias to [`on(…)`](#evented-on)
Events.addEventListener = Events.on;
// @method removeEventListener(…): this
// Alias to [`off(…)`](#evented-off)
// @method clearAllEventListeners(…): this
// Alias to [`off()`](#evented-off)
Events.removeEventListener = Events.clearAllEventListeners = Events.off;
// @method addOneTimeEventListener(…): this
// Alias to [`once(…)`](#evented-once)
Events.addOneTimeEventListener = Events.once;
// @method fireEvent(…): this
// Alias to [`fire(…)`](#evented-fire)
Events.fireEvent = Events.fire;
// @method hasEventListeners(…): Boolean
// Alias to [`listens(…)`](#evented-listens)
Events.hasEventListeners = Events.listens;
var Evented = Class.extend(Events);
/*
* @class Point
* @aka L.Point
*
* Represents a point with `x` and `y` coordinates in pixels.
*
* @example
*
* ```js
* var point = L.point(200, 300);
* ```
*
* 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:
*
* ```js
* map.panBy([200, 300]);
* map.panBy(L.point(200, 300));
* ```
*
* Note that `Point` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
function Point(x, y, round) {
// @property x: Number; The `x` coordinate of the point
this.x = (round ? Math.round(x) : x);
// @property y: Number; The `y` coordinate of the point
this.y = (round ? Math.round(y) : y);
}
var trunc = Math.trunc || function (v) {
return v > 0 ? Math.floor(v) : Math.ceil(v);
};
Point.prototype = {
// @method clone(): Point
// Returns a copy of the current point.
clone: function () {
return new Point(this.x, this.y);
},
// @method add(otherPoint: Point): Point
// Returns the result of addition of the current and the given points.
add: function (point) {
// non-destructive, returns a new point
return this.clone()._add(toPoint(point));
},
_add: function (point) {
// destructive, used directly for performance in situations where it's safe to modify existing point
this.x += point.x;
this.y += point.y;
return this;
},
// @method subtract(otherPoint: Point): Point
// Returns the result of subtraction of the given point from the current.
subtract: function (point) {
return this.clone()._subtract(toPoint(point));
},
_subtract: function (point) {
this.x -= point.x;
this.y -= point.y;
return this;
},
// @method divideBy(num: Number): Point
// Returns the result of division of the current point by the given number.
divideBy: function (num) {
return this.clone()._divideBy(num);
},
_divideBy: function (num) {
this.x /= num;
this.y /= num;
return this;
},
// @method multiplyBy(num: Number): Point
// Returns the result of multiplication of the current point by the given number.
multiplyBy: function (num) {
return this.clone()._multiplyBy(num);
},
_multiplyBy: function (num) {
this.x *= num;
this.y *= num;
return this;
},
// @method scaleBy(scale: Point): Point
// Multiply each coordinate of the current point by each coordinate of
// `scale`. In linear algebra terms, multiply the point by the
// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
// defined by `scale`.
scaleBy: function (point) {
return new Point(this.x * point.x, this.y * point.y);
},
// @method unscaleBy(scale: Point): Point
// Inverse of `scaleBy`. Divide each coordinate of the current point by
// each coordinate of `scale`.
unscaleBy: function (point) {
return new Point(this.x / point.x, this.y / point.y);
},
// @method round(): Point
// Returns a copy of the current point with rounded coordinates.
round: function () {
return this.clone()._round();
},
_round: function () {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
},
// @method floor(): Point
// Returns a copy of the current point with floored coordinates (rounded down).
floor: function () {
return this.clone()._floor();
},
_floor: function () {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
},
// @method ceil(): Point
// Returns a copy of the current point with ceiled coordinates (rounded up).
ceil: function () {
return this.clone()._ceil();
},
_ceil: function () {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
},
// @method trunc(): Point
// Returns a copy of the current point with truncated coordinates (rounded towards zero).
trunc: function () {
return this.clone()._trunc();
},
_trunc: function () {
this.x = trunc(this.x);
this.y = trunc(this.y);
return this;
},
// @method distanceTo(otherPoint: Point): Number
// Returns the cartesian distance between the current and the given points.
distanceTo: function (point) {
point = toPoint(point);
var x = point.x - this.x,
y = point.y - this.y;
return Math.sqrt(x * x + y * y);
},
// @method equals(otherPoint: Point): Boolean
// Returns `true` if the given point has the same coordinates.
equals: function (point) {
point = toPoint(point);
return point.x === this.x &&
point.y === this.y;
},
// @method contains(otherPoint: Point): Boolean
// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
contains: function (point) {
point = toPoint(point);
return Math.abs(point.x) <= Math.abs(this.x) &&
Math.abs(point.y) <= Math.abs(this.y);
},
// @method toString(): String
// Returns a string representation of the point for debugging purposes.
toString: function () {
return 'Point(' +
formatNum(this.x) + ', ' +
formatNum(this.y) + ')';
}
};
// @factory L.point(x: Number, y: Number, round?: Boolean)
// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
// @alternative
// @factory L.point(coords: Number[])
// Expects an array of the form `[x, y]` instead.
// @alternative
// @factory L.point(coords: Object)
// Expects a plain object of the form `{x: Number, y: Number}` instead.
function toPoint(x, y, round) {
if (x instanceof Point) {
return x;
}
if (isArray(x)) {
return new Point(x[0], x[1]);
}
if (x === undefined || x === null) {
return x;
}
if (typeof x === 'object' && 'x' in x && 'y' in x) {
return new Point(x.x, x.y);
}
return new Point(x, y, round);
}
/*
* @class Bounds
* @aka L.Bounds
*
* Represents a rectangular area in pixel coordinates.
*
* @example
*
* ```js
* var p1 = L.point(10, 10),
* p2 = L.point(40, 60),
* bounds = L.bounds(p1, p2);
* ```
*
* 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:
*
* ```js
* otherBounds.intersects([[10, 10], [40, 60]]);
* ```
*
* Note that `Bounds` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
function Bounds(a, b) {
if (!a) { return; }
var points = b ? [a, b] : a;
for (var i = 0, len = points.length; i < len; i++) {
this.extend(points[i]);
}
}
Bounds.prototype = {
// @method extend(point: Point): this
// Extends the bounds to contain the given point.
// @alternative
// @method extend(otherBounds: Bounds): this
// Extend the bounds to contain the given bounds
extend: function (obj) {
var min2, max2;
if (!obj) { return this; }
if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {
min2 = max2 = toPoint(obj);
} else {
obj = toBounds(obj);
min2 = obj.min;
max2 = obj.max;
if (!min2 || !max2) { return this; }
}
// @property min: Point
// The top left corner of the rectangle.
// @property max: Point
// The bottom right corner of the rectangle.
if (!this.min && !this.max) {
this.min = min2.clone();
this.max = max2.clone();
} else {
this.min.x = Math.min(min2.x, this.min.x);
this.max.x = Math.max(max2.x, this.max.x);
this.min.y = Math.min(min2.y, this.min.y);
this.max.y = Math.max(max2.y, this.max.y);
}
return this;
},
// @method getCenter(round?: Boolean): Point
// Returns the center point of the bounds.
getCenter: function (round) {
return toPoint(
(this.min.x + this.max.x) / 2,
(this.min.y + this.max.y) / 2, round);
},
// @method getBottomLeft(): Point
// Returns the bottom-left point of the bounds.
getBottomLeft: function () {
return toPoint(this.min.x, this.max.y);
},
// @method getTopRight(): Point
// Returns the top-right point of the bounds.
getTopRight: function () { // -> Point
return toPoint(this.max.x, this.min.y);
},
// @method getTopLeft(): Point
// Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
getTopLeft: function () {
return this.min; // left, top
},
// @method getBottomRight(): Point
// Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
getBottomRight: function () {
return this.max; // right, bottom
},
// @method getSize(): Point
// Returns the size of the given bounds
getSize: function () {
return this.max.subtract(this.min);
},
// @method contains(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle contains the given one.
// @alternative
// @method contains(point: Point): Boolean
// Returns `true` if the rectangle contains the given point.
contains: function (obj) {
var min, max;
if (typeof obj[0] === 'number' || obj instanceof Point) {
obj = toPoint(obj);
} else {
obj = toBounds(obj);
}
if (obj instanceof Bounds) {
min = obj.min;
max = obj.max;
} else {
min = max = obj;
}
return (min.x >= this.min.x) &&
(max.x <= this.max.x) &&
(min.y >= this.min.y) &&
(max.y <= this.max.y);
},
// @method intersects(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle intersects the given bounds. Two bounds
// intersect if they have at least one point in common.
intersects: function (bounds) { // (Bounds) -> Boolean
bounds = toBounds(bounds);
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
return xIntersects && yIntersects;
},
// @method overlaps(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle overlaps the given bounds. Two bounds
// overlap if their intersection is an area.
overlaps: function (bounds) { // (Bounds) -> Boolean
bounds = toBounds(bounds);
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xOverlaps = (max2.x > min.x) && (min2.x < max.x),
yOverlaps = (max2.y > min.y) && (min2.y < max.y);
return xOverlaps && yOverlaps;
},
// @method isValid(): Boolean
// Returns `true` if the bounds are properly initialized.
isValid: function () {
return !!(this.min && this.max);
},
// @method pad(bufferRatio: Number): Bounds
// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
// Negative values will retract the bounds.
pad: function (bufferRatio) {
var min = this.min,
max = this.max,
heightBuffer = Math.abs(min.x - max.x) * bufferRatio,
widthBuffer = Math.abs(min.y - max.y) * bufferRatio;
return toBounds(
toPoint(min.x - heightBuffer, min.y - widthBuffer),
toPoint(max.x + heightBuffer, max.y + widthBuffer));
},
// @method equals(otherBounds: Bounds): Boolean
// Returns `true` if the rectangle is equivalent to the given bounds.
equals: function (bounds) {
if (!bounds) { return false; }
bounds = toBounds(bounds);
return this.min.equals(bounds.getTopLeft()) &&
this.max.equals(bounds.getBottomRight());
},
};
// @factory L.bounds(corner1: Point, corner2: Point)
// Creates a Bounds object from two corners coordinate pairs.
// @alternative
// @factory L.bounds(points: Point[])
// Creates a Bounds object from the given array of points.
function toBounds(a, b) {
if (!a || a instanceof Bounds) {
return a;
}
return new Bounds(a, b);
}
/*
* @class LatLngBounds
* @aka L.LatLngBounds
*
* Represents a rectangular geographical area on a map.
*
* @example
*
* ```js
* var corner1 = L.latLng(40.712, -74.227),
* corner2 = L.latLng(40.774, -74.125),
* bounds = L.latLngBounds(corner1, corner2);
* ```
*
* 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:
*
* ```js
* map.fitBounds([
* [40.712, -74.227],
* [40.774, -74.125]
* ]);
* ```
*
* 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.
*
* Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
if (!corner1) { return; }
var latlngs = corner2 ? [corner1, corner2] : corner1;
for (var i = 0, len = latlngs.length; i < len; i++) {
this.extend(latlngs[i]);
}
}
LatLngBounds.prototype = {
// @method extend(latlng: LatLng): this
// Extend the bounds to contain the given point
// @alternative
// @method extend(otherBounds: LatLngBounds): this
// Extend the bounds to contain the given bounds
extend: function (obj) {
var sw = this._southWest,
ne = this._northEast,
sw2, ne2;
if (obj instanceof LatLng) {
sw2 = obj;
ne2 = obj;
} else if (obj instanceof LatLngBounds) {
sw2 = obj._southWest;
ne2 = obj._northEast;
if (!sw2 || !ne2) { return this; }
} else {
return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
}
if (!sw && !ne) {
this._southWest = new LatLng(sw2.lat, sw2.lng);
this._northEast = new LatLng(ne2.lat, ne2.lng);
} else {
sw.lat = Math.min(sw2.lat, sw.lat);
sw.lng = Math.min(sw2.lng, sw.lng);
ne.lat = Math.max(ne2.lat, ne.lat);
ne.lng = Math.max(ne2.lng, ne.lng);
}
return this;
},
// @method pad(bufferRatio: Number): LatLngBounds
// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
// Negative values will retract the bounds.
pad: function (bufferRatio) {
var sw = this._southWest,
ne = this._northEast,
heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
return new LatLngBounds(
new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
},
// @method getCenter(): LatLng
// Returns the center point of the bounds.
getCenter: function () {
return new LatLng(
(this._southWest.lat + this._northEast.lat) / 2,
(this._southWest.lng + this._northEast.lng) / 2);
},
// @method getSouthWest(): LatLng
// Returns the south-west point of the bounds.
getSouthWest: function () {
return this._southWest;
},
// @method getNorthEast(): LatLng
// Returns the north-east point of the bounds.
getNorthEast: function () {
return this._northEast;
},
// @method getNorthWest(): LatLng
// Returns the north-west point of the bounds.
getNorthWest: function () {
return new LatLng(this.getNorth(), this.getWest());
},
// @method getSouthEast(): LatLng
// Returns the south-east point of the bounds.
getSouthEast: function () {
return new LatLng(this.getSouth(), this.getEast());
},
// @method getWest(): Number
// Returns the west longitude of the bounds
getWest: function () {
return this._southWest.lng;
},
// @method getSouth(): Number
// Returns the south latitude of the bounds
getSouth: function () {
return this._southWest.lat;
},
// @method getEast(): Number
// Returns the east longitude of the bounds
getEast: function () {
return this._northEast.lng;
},
// @method getNorth(): Number
// Returns the north latitude of the bounds
getNorth: function () {
return this._northEast.lat;
},
// @method contains(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle contains the given one.
// @alternative
// @method contains (latlng: LatLng): Boolean
// Returns `true` if the rectangle contains the given point.
contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
obj = toLatLng(obj);
} else {
obj = toLatLngBounds(obj);
}
var sw = this._southWest,
ne = this._northEast,
sw2, ne2;
if (obj instanceof LatLngBounds) {
sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast();
} else {
sw2 = ne2 = obj;
}
return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
(sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
},
// @method intersects(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
intersects: function (bounds) {
bounds = toLatLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
return latIntersects && lngIntersects;
},
// @method overlaps(otherBounds: LatLngBounds): Boolean
// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
overlaps: function (bounds) {
bounds = toLatLngBounds(bounds);
var sw = this._southWest,
ne = this._northEast,
sw2 = bounds.getSouthWest(),
ne2 = bounds.getNorthEast(),
latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
return latOverlaps && lngOverlaps;
},
// @method toBBoxString(): String
// 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.
toBBoxString: function () {
return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
},
// @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
// 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.
equals: function (bounds, maxMargin) {
if (!bounds) { return false; }
bounds = toLatLngBounds(bounds);
return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
this._northEast.equals(bounds.getNorthEast(), maxMargin);
},
// @method isValid(): Boolean
// Returns `true` if the bounds are properly initialized.
isValid: function () {
return !!(this._southWest && this._northEast);
}
};
// TODO International date line?
// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
// @alternative
// @factory L.latLngBounds(latlngs: LatLng[])
// 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).
function toLatLngBounds(a, b) {
if (a instanceof LatLngBounds) {
return a;
}
return new LatLngBounds(a, b);
}
/* @class LatLng
* @aka L.LatLng
*
* Represents a geographical point with a certain latitude and longitude.
*
* @example
*
* ```
* var latlng = L.latLng(50.5, 30.5);
* ```
*
* 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:
*
* ```
* map.panTo([50, 30]);
* map.panTo({lon: 30, lat: 50});
* map.panTo({lat: 50, lng: 30});
* map.panTo(L.latLng(50, 30));
* ```
*
* Note that `LatLng` does not inherit from Leaflet's `Class` object,
* which means new classes can't inherit from it, and new methods
* can't be added to it with the `include` function.
*/
function LatLng(lat, lng, alt) {
if (isNaN(lat) || isNaN(lng)) {
throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
}
// @property lat: Number
// Latitude in degrees
this.lat = +lat;
// @property lng: Number
// Longitude in degrees
this.lng = +lng;
// @property alt: Number
// Altitude in meters (optional)
if (alt !== undefined) {
this.alt = +alt;
}
}
LatLng.prototype = {
// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
// 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.
equals: function (obj, maxMargin) {
if (!obj) { return false; }
obj = toLatLng(obj);
var margin = Math.max(
Math.abs(this.lat - obj.lat),
Math.abs(this.lng - obj.lng));
return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
},
// @method toString(): String
// Returns a string representation of the point (for debugging purposes).
toString: function (precision) {
return 'LatLng(' +
formatNum(this.lat, precision) + ', ' +
formatNum(this.lng, precision) + ')';
},
// @method distanceTo(otherLatLng: LatLng): Number
// 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).
distanceTo: function (other) {
return Earth.distance(this, toLatLng(other));
},
// @method wrap(): LatLng
// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
wrap: function () {
return Earth.wrapLatLng(this);
},
// @method toBounds(sizeInMeters: Number): LatLngBounds
// Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
toBounds: function (sizeInMeters) {
var latAccuracy = 180 * sizeInMeters / 40075017,
lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
return toLatLngBounds(
[this.lat - latAccuracy, this.lng - lngAccuracy],
[this.lat + latAccuracy, this.lng + lngAccuracy]);
},
clone: function () {
return new LatLng(this.lat, this.lng, this.alt);
}
};
// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
// @alternative
// @factory L.latLng(coords: Array): LatLng
// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
// @alternative
// @factory L.latLng(coords: Object): LatLng
// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
function toLatLng(a, b, c) {
if (a instanceof LatLng) {
return a;
}
if (isArray(a) && typeof a[0] !== 'object') {
if (a.length === 3) {
return new LatLng(a[0], a[1], a[2]);
}
if (a.length === 2) {
return new LatLng(a[0], a[1]);
}
return null;
}
if (a === undefined || a === null) {
return a;
}
if (typeof a === 'object' && 'lat' in a) {
return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
}
if (b === undefined) {
return null;
}
return new LatLng(a, b, c);
}
/*
* @namespace C