UNPKG

geodesy-fn

Version:

Geodesic functions for GeoJSON, based on geodesy

163 lines (136 loc) 6.65 kB
import { DEFAULT_RADIUS, equals, toRadians, toDegrees } from './common.js'; import { wrap360 } from './dms.js'; /** * Returns the distance along the surface of the earth from start point to destination point. * * Uses haversine formula: a = sin²(Δφ/2) + cosφ1·cosφ2 · sin²(Δλ/2); d = 2 · atan2(√a, √(a-1)). * * @param {GeoJSON.Position} start - Longitude/latitude of start point. * @param {GeoJSON.Position} destination - Longitude/latitude of destination point. * @param {number} [radius] - Radius of earth (defaults to mean radius in metres). * @returns {number} Distance between start point and destination point, in same units as radius. * * @example * const p1 = [0.119, 52.205]; * const p2 = [2.351, 48.857]; * const d = distance(p1, p2); // 404.3×10³ m * const m = distanceTo(p1, p2, 3959); // 251.2 miles */ export function distance(start: GeoJSON.Position, destination: GeoJSON.Position, radius: number = DEFAULT_RADIUS): number { // a = sin²(Δφ/2) + cos(φ1)⋅cos(φ2)⋅sin²(Δλ/2) // δ = 2·atan2(√(a), √(1−a)) // see mathforum.org/library/drmath/view/51879.html for derivation const R = radius; const φ1 = toRadians(start[1]), λ1 = toRadians(start[0]); const φ2 = toRadians(destination[1]), λ2 = toRadians(destination[0]); const Δφ = φ2 - φ1; const Δλ = λ2 - λ1; const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos1) * Math.cos2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const d = R * c; return d; } /** * Returns the initial bearing from start point to destination point. * * @param {GeoJSON.Position} start - Longitude/latitude of start point. * @param {GeoJSON.Position} destination - Longitude/latitude of destination point. * @returns {number} Initial bearing in degrees from north (0°..360°). * * @example * const p1 = [0.119, 52.205]; * const p2 = [2.351, 48.857]; * const b1 = initialBearing(p1, p2); // 156.2° */ export function initialBearing(start: GeoJSON.Position, destination: GeoJSON.Position): number { if (equals(start, destination)) return NaN; // coincident points // tanθ = sinΔλ⋅cosφ2 / cosφ1⋅sinφ2 − sinφ1⋅cosφ2⋅cosΔλ // see mathforum.org/library/drmath/view/55417.html for derivation const φ1 = toRadians(start[1]); const φ2 = toRadians(destination[1]); const Δλ = toRadians(destination[0] - start[0]); const x = Math.cos1) * Math.sin2) - Math.sin1) * Math.cos2) * Math.cos(Δλ); const y = Math.sin(Δλ) * Math.cos2); const θ = Math.atan2(y, x); const bearing = toDegrees(θ); return wrap360(bearing); } /** * Returns final bearing arriving at destination point from ‘this’ point; the final bearing will * differ from the initial bearing by varying degrees according to distance and latitude. * * @param {GeoJSON.Position} start - Longitude/latitude of start point. * @param {GeoJSON.Position} destination - Longitude/latitude of destination point. * @returns {number} Final bearing in degrees from north (0°..360°). * * @example * const p1 = [0.119, 52.205]; * const p2 = [2.351, 48.857]; * const b2 = finalBearing(p1, p2); // 157.9° */ export function finalBearing(start: GeoJSON.Position, destination: GeoJSON.Position): number { // get initial bearing from destination point to this point & reverse it by adding 180° const bearing = initialBearing(destination, start) + 180; return wrap360(bearing); } /** * Returns the midpoint between start point and destination point. * * @param {GeoJSON.Position} start - Longitude/latitude of start point. * @param {GeoJSON.Position} destination - Longitude/latitude of destination point. * @returns {GeoJSON.Position} Midpoint between this point and destination point. * * @example * const p1 = [0.119, 52.205]; * const p2 = [2.351, 48.857]; * const pMid = midpoint(p1, p2); // [1.2746, 50.5363] */ export function midpoint(start: GeoJSON.Position, destination: GeoJSON.Position): GeoJSON.Position { // φm = atan2( sinφ1 + sinφ2, √( (cosφ1 + cosφ2⋅cosΔλ)² + cos²φ2⋅sin²Δλ ) ) // λm = λ1 + atan2(cosφ2⋅sinΔλ, cosφ1 + cosφ2⋅cosΔλ) // midpoint is sum of vectors to two points: mathforum.org/library/drmath/view/51822.html const φ1 = toRadians(start[1]); const λ1 = toRadians(start[0]) const φ2 = toRadians(destination[1]); const Δλ = toRadians(destination[0] - start[0]); // get cartesian coordinates for the two points const A = { x: Math.cos1), y: 0, z: Math.sin1) }; // place point A on prime meridian y=0 const B = { x: Math.cos2)*Math.cos(Δλ), y: Math.cos2)*Math.sin(Δλ), z: Math.sin2) }; // vector to midpoint is sum of vectors to two points (no need to normalise) const C = { x: A.x + B.x, y: A.y + B.y, z: A.z + B.z }; const φm = Math.atan2(C.z, Math.sqrt(C.x*C.x + C.y*C.y)); const λm = λ1 + Math.atan2(C.y, C.x); const lat = toDegrees(φm); const lon = toDegrees(λm); return [lon, lat]; } /** * Returns the destination point from start point having travelled the given distance on the * given initial bearing (bearing normally varies around path followed). * * @param {GeoJSON.Position} start - Longitude/latitude of start point. * @param {number} distance - Distance travelled, in same units as earth radius (default: metres). * @param {number} bearing - Initial bearing in degrees from north. * @param {number} [radius] - Radius of earth (defaults to mean radius in metres). * @returns {GeoJSON.Position} Destination point. * * @example * const p1 = [-0.00147, 51.47788]; * const p2 = destinationPoint(p1, 7794, 300.7); // [0.0983, 51.5136] */ export function destinationPoint(start: GeoJSON.Position, distance: number, bearing: number, radius: number = DEFAULT_RADIUS): GeoJSON.Position { // sinφ2 = sinφ1⋅cosδ + cosφ1⋅sinδ⋅cosθ // tanΔλ = sinθ⋅sinδ⋅cosφ1 / cosδ−sinφ1⋅sinφ2 // see mathforum.org/library/drmath/view/52049.html for derivation const δ = distance / radius; // angular distance in radians const θ = toRadians(bearing); const φ1 = toRadians(start[1]), λ1 = toRadians(start[0]); const sinφ2 = Math.sin1) * Math.cos(δ) + Math.cos1) * Math.sin(δ) * Math.cos(θ); const φ2 = Math.asin(sinφ2); const y = Math.sin(θ) * Math.sin(δ) * Math.cos1); const x = Math.cos(δ) - Math.sin1) * sinφ2; const λ2 = λ1 + Math.atan2(y, x); const lat = toDegrees2); const lon = toDegrees2); return [lon, lat]; }