177 lines
6.2 KiB
TypeScript
177 lines
6.2 KiB
TypeScript
import {wrap} from '../util/util';
|
|
|
|
/*
|
|
* Approximate radius of the earth in meters.
|
|
* Uses the WGS-84 approximation. The radius at the equator is ~6378137 and at the poles is ~6356752. https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84
|
|
* 6371008.8 is one published "average radius" see https://en.wikipedia.org/wiki/Earth_radius#Mean_radius, or ftp://athena.fsv.cvut.cz/ZFG/grs80-Moritz.pdf p.4
|
|
*/
|
|
export const earthRadius = 6371008.8;
|
|
|
|
/**
|
|
* A {@link LngLat} object, an array of two numbers representing longitude and latitude,
|
|
* or an object with `lng` and `lat` or `lon` and `lat` properties.
|
|
*
|
|
* @group Geography and Geometry
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* let v1 = new LngLat(-122.420679, 37.772537);
|
|
* let v2 = [-122.420679, 37.772537];
|
|
* let v3 = {lon: -122.420679, lat: 37.772537};
|
|
* ```
|
|
*/
|
|
export type LngLatLike = LngLat | {
|
|
lng: number;
|
|
lat: number;
|
|
} | {
|
|
lon: number;
|
|
lat: number;
|
|
} | [number, number];
|
|
|
|
/**
|
|
* A `LngLat` object represents a given longitude and latitude coordinate, measured in degrees.
|
|
* These coordinates are based on the [WGS84 (EPSG:4326) standard](https://en.wikipedia.org/wiki/World_Geodetic_System#WGS84).
|
|
*
|
|
* MapLibre GL JS uses longitude, latitude coordinate order (as opposed to latitude, longitude) to match the
|
|
* [GeoJSON specification](https://tools.ietf.org/html/rfc7946).
|
|
*
|
|
* Note that any MapLibre GL JS method that accepts a `LngLat` object as an argument or option
|
|
* can also accept an `Array` of two numbers and will perform an implicit conversion.
|
|
* This flexible type is documented as {@link LngLatLike}.
|
|
*
|
|
* @group Geography and Geometry
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* let ll = new LngLat(-123.9749, 40.7736);
|
|
* ll.lng; // = -123.9749
|
|
* ```
|
|
* @see [Get coordinates of the mouse pointer](https://maplibre.org/maplibre-gl-js/docs/examples/mouse-position/)
|
|
* @see [Display a popup](https://maplibre.org/maplibre-gl-js/docs/examples/popup/)
|
|
* @see [Create a timeline animation](https://maplibre.org/maplibre-gl-js/docs/examples/timeline-animation/)
|
|
*/
|
|
export class LngLat {
|
|
/**
|
|
* Longitude, measured in degrees.
|
|
*/
|
|
lng: number;
|
|
|
|
/**
|
|
* Latitude, measured in degrees.
|
|
*/
|
|
lat: number;
|
|
|
|
/**
|
|
* @param lng - Longitude, measured in degrees.
|
|
* @param lat - Latitude, measured in degrees.
|
|
*/
|
|
constructor(lng: number, lat: number) {
|
|
if (isNaN(lng) || isNaN(lat)) {
|
|
throw new Error(`Invalid LngLat object: (${lng}, ${lat})`);
|
|
}
|
|
this.lng = +lng;
|
|
this.lat = +lat;
|
|
if (this.lat > 90 || this.lat < -90) {
|
|
throw new Error('Invalid LngLat latitude value: must be between -90 and 90');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a new `LngLat` object whose longitude is wrapped to the range (-180, 180).
|
|
*
|
|
* @returns The wrapped `LngLat` object.
|
|
* @example
|
|
* ```ts
|
|
* let ll = new LngLat(286.0251, 40.7736);
|
|
* let wrapped = ll.wrap();
|
|
* wrapped.lng; // = -73.9749
|
|
* ```
|
|
*/
|
|
wrap() {
|
|
return new LngLat(wrap(this.lng, -180, 180), this.lat);
|
|
}
|
|
|
|
/**
|
|
* Returns the coordinates represented as an array of two numbers.
|
|
*
|
|
* @returns The coordinates represented as an array of longitude and latitude.
|
|
* @example
|
|
* ```ts
|
|
* let ll = new LngLat(-73.9749, 40.7736);
|
|
* ll.toArray(); // = [-73.9749, 40.7736]
|
|
* ```
|
|
*/
|
|
toArray(): [number, number] {
|
|
return [this.lng, this.lat];
|
|
}
|
|
|
|
/**
|
|
* Returns the coordinates represent as a string.
|
|
*
|
|
* @returns The coordinates represented as a string of the format `'LngLat(lng, lat)'`.
|
|
* @example
|
|
* ```ts
|
|
* let ll = new LngLat(-73.9749, 40.7736);
|
|
* ll.toString(); // = "LngLat(-73.9749, 40.7736)"
|
|
* ```
|
|
*/
|
|
toString(): string {
|
|
return `LngLat(${this.lng}, ${this.lat})`;
|
|
}
|
|
|
|
/**
|
|
* Returns the approximate distance between a pair of coordinates in meters
|
|
* Uses the Haversine Formula (from R.W. Sinnott, "Virtues of the Haversine", Sky and Telescope, vol. 68, no. 2, 1984, p. 159)
|
|
*
|
|
* @param lngLat - coordinates to compute the distance to
|
|
* @returns Distance in meters between the two coordinates.
|
|
* @example
|
|
* ```ts
|
|
* let new_york = new LngLat(-74.0060, 40.7128);
|
|
* let los_angeles = new LngLat(-118.2437, 34.0522);
|
|
* new_york.distanceTo(los_angeles); // = 3935751.690893987, "true distance" using a non-spherical approximation is ~3966km
|
|
* ```
|
|
*/
|
|
distanceTo(lngLat: LngLat): number {
|
|
const rad = Math.PI / 180;
|
|
const lat1 = this.lat * rad;
|
|
const lat2 = lngLat.lat * rad;
|
|
const a = Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos((lngLat.lng - this.lng) * rad);
|
|
|
|
const maxMeters = earthRadius * Math.acos(Math.min(a, 1));
|
|
return maxMeters;
|
|
}
|
|
|
|
/**
|
|
* Converts an array of two numbers or an object with `lng` and `lat` or `lon` and `lat` properties
|
|
* to a `LngLat` object.
|
|
*
|
|
* If a `LngLat` object is passed in, the function returns it unchanged.
|
|
*
|
|
* @param input - An array of two numbers or object to convert, or a `LngLat` object to return.
|
|
* @returns A new `LngLat` object, if a conversion occurred, or the original `LngLat` object.
|
|
* @example
|
|
* ```ts
|
|
* let arr = [-73.9749, 40.7736];
|
|
* let ll = LngLat.convert(arr);
|
|
* ll; // = LngLat {lng: -73.9749, lat: 40.7736}
|
|
* ```
|
|
*/
|
|
static convert(input: LngLatLike): LngLat {
|
|
if (input instanceof LngLat) {
|
|
return input;
|
|
}
|
|
if (Array.isArray(input) && (input.length === 2 || input.length === 3)) {
|
|
return new LngLat(Number(input[0]), Number(input[1]));
|
|
}
|
|
if (!Array.isArray(input) && typeof input === 'object' && input !== null) {
|
|
return new LngLat(
|
|
// flow can't refine this to have one of lng or lat, so we have to cast to any
|
|
Number('lng' in input ? (input as any).lng : (input as any).lon),
|
|
Number(input.lat)
|
|
);
|
|
}
|
|
throw new Error('`LngLatLike` argument must be specified as a LngLat instance, an object {lng: <lng>, lat: <lat>}, an object {lon: <lng>, lat: <lat>}, or an array of [<lng>, <lat>]');
|
|
}
|
|
}
|