export type LngLatTuple = [number, number]; const EARTH_RADIUS_METERS = 6371008.8; const SAME_COORDINATE_EPSILON = 0.0000001; export function getDistanceMeters(from: LngLatTuple, to: LngLatTuple) { const fromLatitude = toRadians(from[1]); const toLatitude = toRadians(to[1]); const deltaLatitude = toRadians(to[1] - from[1]); const deltaLongitude = toRadians(to[0] - from[0]); const a = Math.sin(deltaLatitude / 2) ** 2 + Math.cos(fromLatitude) * Math.cos(toLatitude) * Math.sin(deltaLongitude / 2) ** 2; return 2 * EARTH_RADIUS_METERS * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); } export function getLineDistanceMeters(coordinates: LngLatTuple[]) { return coordinates.reduce((total, coordinate, index) => { if (index === 0) { return total; } return total + getDistanceMeters(coordinates[index - 1], coordinate); }, 0); } export function getLastSegmentDistanceMeters(coordinates: LngLatTuple[]) { if (coordinates.length < 2) { return 0; } return getDistanceMeters(coordinates[coordinates.length - 2], coordinates[coordinates.length - 1]); } export function getPolygonAreaSquareMeters(coordinates: LngLatTuple[]) { if (coordinates.length < 3) { return 0; } const closedCoordinates = [...coordinates, coordinates[0]]; const area = closedCoordinates.slice(0, -1).reduce((total, coordinate, index) => { const nextCoordinate = closedCoordinates[index + 1]; return ( total + toRadians(nextCoordinate[0] - coordinate[0]) * (2 + Math.sin(toRadians(coordinate[1])) + Math.sin(toRadians(nextCoordinate[1]))) ); }, 0); return Math.abs((area * EARTH_RADIUS_METERS * EARTH_RADIUS_METERS) / 2); } export function createCircleCoordinates(center: LngLatTuple, radiusMeters: number, steps = 48) { const angularDistance = radiusMeters / EARTH_RADIUS_METERS; const centerLongitude = toRadians(center[0]); const centerLatitude = toRadians(center[1]); const coordinates: LngLatTuple[] = []; for (let index = 0; index <= steps; index += 1) { const bearing = (2 * Math.PI * index) / steps; const latitude = Math.asin( Math.sin(centerLatitude) * Math.cos(angularDistance) + Math.cos(centerLatitude) * Math.sin(angularDistance) * Math.cos(bearing) ); const longitude = centerLongitude + Math.atan2( Math.sin(bearing) * Math.sin(angularDistance) * Math.cos(centerLatitude), Math.cos(angularDistance) - Math.sin(centerLatitude) * Math.sin(latitude) ); coordinates.push([toDegrees(longitude), toDegrees(latitude)]); } return coordinates; } export function isSameCoordinate(previous: LngLatTuple | undefined, next: LngLatTuple) { if (!previous) { return false; } return ( Math.abs(previous[0] - next[0]) < SAME_COORDINATE_EPSILON && Math.abs(previous[1] - next[1]) < SAME_COORDINATE_EPSILON ); } export function toRadians(value: number) { return (value * Math.PI) / 180; } function toDegrees(value: number) { return (value * 180) / Math.PI; }