leaflet
Version:
JavaScript library for mobile-friendly interactive maps
290 lines (255 loc) • 6.96 kB
JavaScript
import {formatNum} from '../core/Util.js';
/** @typedef {[number, number]} PointTuple */
/**
* A `Point`, or its array (`[x, y]`) or object (`{x, y}`) form.
* @typedef {Point | PointTuple | {x: number, y: number}} PointExpression
*/
/**
* Represents a point with `x` and `y` coordinates in pixels.
*
* @example
* ```js
* const point = new 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(new 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.
*
* @categoryDescription Functions
* There are several static functions which can be called without instantiating Point:
*/
export class Point {
/**
* Creates a Point object with the given `x` and `y` coordinates. If
* optional `round` is set to true, rounds the `x` and `y` values.
* @overload
* @param {number} x
* @param {number} y
* @param {boolean} [round]
*/
/**
* Expects an array of the form `[x, y]` or a plain object of the form `{x: Number, y: Number}` instead.
* @overload
* @param {PointExpression} coords
*/
/**
* @param {number | PointExpression} x
* @param {number} [y]
* @param {boolean} [round]
*/
constructor(x, y, round) {
const valid = Point.validate(/** @type {any} */ (x), y);
if (!valid) {
throw new Error(`Invalid Point object: (${x}, ${y})`);
}
let _x, _y;
if (x instanceof Point) {
// We can use the same object, no need to clone it
// eslint-disable-next-line no-constructor-return
return x;
} else if (Array.isArray(x)) {
_x = x[0];
_y = x[1];
} else if (typeof x === 'object' && 'x' in x && 'y' in x) {
_x = x.x;
_y = x.y;
} else {
_x = x;
_y = /** @type {number} */ (y);
}
/**
* The `x` coordinate of the point
* @type {number}
*/
this.x = (round ? Math.round(_x) : _x);
/**
* The `y` coordinate of the point
* @type {number}
*/
this.y = (round ? Math.round(_y) : _y);
}
/**
* Returns `true` if the Point object can be properly initialized.
* @category Functions
* @overload
* @param {number} x
* @param {number} [y]
* @returns {boolean}
*/
/**
* Expects an array of the form `[x, y]` or a plain object of the form `{x: Number, y: Number}`. Returns `true` if
* the Point object can be properly initialized.
* @overload
* @param {unknown} coords
* @returns {coords is PointExpression}
*/
/**
* @param {unknown} x
* @param {unknown} [y]
*/
static validate(x, y) {
if (x instanceof Point || Array.isArray(x)) {
return true;
} else if (x && typeof x === 'object' && 'x' in x && 'y' in x) {
return true;
} else if ((x || x === 0) && (y || y === 0)) {
return true;
}
return false;
}
/** Returns a copy of the current point. */
clone() {
// to skip the validation in the constructor we need to initialize with 0 and then set the values later
const p = new Point(0, 0);
p.x = this.x;
p.y = this.y;
return p;
}
/**
* Returns the result of addition of the current and the given points.
* @param {PointExpression} point
*/
add(point) {
// non-destructive, returns a new point
return this.clone()._add(new Point(point));
}
/** @param {Point} point */
_add(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;
}
/**
* Returns the result of subtraction of the given point from the current.
* @param {PointExpression} point
*/
subtract(point) {
return this.clone()._subtract(new Point(point));
}
/** @param {Point} point */
_subtract(point) {
this.x -= point.x;
this.y -= point.y;
return this;
}
/**
* Returns the result of division of the current point by the given number.
* @param {number} num
*/
divideBy(num) {
return this.clone()._divideBy(num);
}
/** @param {number} num */
_divideBy(num) {
this.x /= num;
this.y /= num;
return this;
}
/**
* Returns the result of multiplication of the current point by the given number.
* @param {number} num
*/
multiplyBy(num) {
return this.clone()._multiplyBy(num);
}
/** @param {number} num */
_multiplyBy(num) {
this.x *= num;
this.y *= num;
return this;
}
/**
* 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`.
* @param {Point} scale
*/
scaleBy(scale) {
return new Point(this.x * scale.x, this.y * scale.y);
}
/**
* Inverse of `scaleBy`. Divide each coordinate of the current point by each coordinate of `scale`.
* @param {Point} scale
*/
unscaleBy(scale) {
return new Point(this.x / scale.x, this.y / scale.y);
}
/** Returns a copy of the current point with rounded coordinates. */
round() {
return this.clone()._round();
}
_round() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
}
/** Returns a copy of the current point with floored coordinates (rounded down). */
floor() {
return this.clone()._floor();
}
_floor() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
}
/** Returns a copy of the current point with ceiled coordinates (rounded up). */
ceil() {
return this.clone()._ceil();
}
_ceil() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
}
/** Returns a copy of the current point with truncated coordinates (rounded towards zero). */
trunc() {
return this.clone()._trunc();
}
_trunc() {
this.x = Math.trunc(this.x);
this.y = Math.trunc(this.y);
return this;
}
/**
* Returns the cartesian distance between the current and the given points.
* @param {PointExpression} point
*/
distanceTo(point) {
point = new Point(point);
const x = point.x - this.x,
y = point.y - this.y;
return Math.sqrt(x * x + y * y);
}
/**
* Returns `true` if the given point has the same coordinates.
* @param {PointExpression} point
*/
equals(point) {
point = new Point(point);
return point.x === this.x &&
point.y === this.y;
}
/**
* Returns `true` if both coordinates of the given point are less than
* the corresponding current point coordinates (in absolute values).
* @param {PointExpression} point
*/
contains(point) {
point = new Point(point);
return Math.abs(point.x) <= Math.abs(this.x) &&
Math.abs(point.y) <= Math.abs(this.y);
}
/** Returns a string representation of the point for debugging purposes. */
toString() {
return `Point(${formatNum(this.x)}, ${formatNum(this.y)})`;
}
}