97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
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;
|
|
}
|