UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

227 lines (205 loc) • 6.53 kB
import * as Util from '../core/Util.js'; import {LatLngBounds} from './LatLngBounds.js'; /** @typedef {[number, number, number?]} LatLngTuple */ /** @typedef {{lat: number, lng: number, alt?: number}} LatLngLiteral */ /** * A `LatLng`, or its array (`[lat, lng]`, `[lat, lng, alt]`) or object (`{lat, lng}`, `{lat, lon}`) form. * @typedef {LatLng | LatLngTuple | LatLngLiteral | {lat: number, lon: number, alt?: number}} LatLngExpression */ /** * Represents a geographical point with a certain latitude and longitude. * * @example * ```js * const latlng = new 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: * * ```js * map.panTo([50, 30]); * map.panTo({lat: 50, lng: 30}); * map.panTo({lat: 50, lon: 30}); * map.panTo(new 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. * * @categoryDescription Functions * There are several static functions which can be called without instantiating LatLng: */ export class LatLng { /** * Creates an object representing a geographical point with the given latitude and longitude (and optionally * altitude). * @overload * @param {number} lat * @param {number} lng * @param {number} [alt] */ /** * Expects an array of the form `[Number, Number]` or `[Number, Number, Number]`, or a plain object of the form * `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. You can also use `lon` in * place of `lng` in the object form. * @overload * @param {LatLngExpression} coords */ /** * @param {any} lat * @param {number} [lng] * @param {number} [alt] */ constructor(lat, lng, alt) { const valid = LatLng.validate(lat, lng, alt); if (!valid) { throw new Error(`Invalid LatLng object: (${lat}, ${lng})`); } let _lat, _lng, _alt; if (lat instanceof LatLng) { // We can use the same object, no need to clone it // eslint-disable-next-line no-constructor-return return lat; } else if (Array.isArray(lat) && typeof lat[0] !== 'object') { if (lat.length === 3) { _lat = lat[0]; _lng = lat[1]; _alt = lat[2]; } else if (lat.length === 2) { _lat = lat[0]; _lng = lat[1]; } } else if (typeof lat === 'object' && 'lat' in lat) { _lat = lat.lat; _lng = 'lng' in lat ? lat.lng : lat.lon; _alt = lat.alt; } else { _lat = lat; _lng = lng; _alt = alt; } /** * Latitude in degrees * @type {number} */ this.lat = +_lat; /** * Longitude in degrees * @type {number} */ this.lng = +_lng; if (_alt !== undefined) { /** * Altitude in meters (optional) * @type {number | undefined} */ this.alt = +_alt; } } /** * Returns `true` if the LatLng object can be properly initialized. * @category Functions * @overload * @param {number} lat * @param {number} [lng] * @param {number} [alt] * @returns {boolean} */ /** * Expects an array of the form `[Number, Number]` or `[Number, Number, Number]`, or a plain object of the form * `{lat: Number, lng: Number}`. Returns `true` if the LatLng object can be properly initialized. * @overload * @param {LatLngExpression} coords * @returns {boolean} */ /** * @param {unknown} lat * @param {unknown} [lng] * @param {unknown} [alt] */ static validate(lat, lng, alt) { if (lat instanceof LatLng || (typeof lat === 'object' && lat !== null && 'lat' in lat)) { return true; } else if (lat && Array.isArray(lat) && typeof lat[0] !== 'object') { if (lat.length === 3 || lat.length === 2) { return true; } return false; } else if ((lat || lat === 0) && (lng || lng === 0)) { return true; } return false; } /** * 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. * @param {LatLngExpression} [obj] * @param {number} [maxMargin] */ equals(obj, maxMargin) { if (!obj) { return false; } obj = new LatLng(obj); const margin = Math.max( Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng)); return margin <= (maxMargin ?? 1.0E-9); } /** * Returns a string representation of the point (for debugging purposes). * @param {number} [precision] */ toString(precision) { return `LatLng(${Util.formatNum(this.lat, precision)}, ${Util.formatNum(this.lng, precision)})`; } /** * Returns the distance (in meters) to the given `LatLng` calculated using the * [Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula). * @param {LatLngExpression} other */ distanceTo(other) { return haversineDistance(this, new LatLng(other), 6371000); } /** Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. */ wrap() { return new LatLng(this.lat, Util.wrapNum(this.lng, [-180, 180], true), this.alt); } /** * Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. * @param {number} sizeInMeters */ toBounds(sizeInMeters) { const latAccuracy = 180 * sizeInMeters / 40075017, lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); return new LatLngBounds( [this.lat - latAccuracy, this.lng - lngAccuracy], [this.lat + latAccuracy, this.lng + lngAccuracy]); } /** Returns a copy of the current LatLng. */ clone() { // to skip the validation in the constructor we need to initialize with 0 and then set the values later const latlng = new LatLng(0, 0); latlng.lat = this.lat; latlng.lng = this.lng; if (this.alt !== undefined) { latlng.alt = this.alt; } return latlng; } }; /** * Great-circle distance on a sphere of radius R, shared with `EarthCRS.distance`. * @param {LatLng} latlng1 * @param {LatLng} latlng2 * @param {number} R */ export function haversineDistance(latlng1, latlng2, R) { const rad = Math.PI / 180, lat1 = latlng1.lat * rad, lat2 = latlng2.lat * rad, sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; }