gc-wing/apps/web/src/widgets/map3d/lib/geometry.ts

63 lines
1.9 KiB
TypeScript

import { DEG2RAD, RAD2DEG, EARTH_RADIUS_M } from '../constants';
export const clampNumber = (value: number, minValue: number, maxValue: number) =>
Math.max(minValue, Math.min(maxValue, value));
export function wrapLonDeg(lon: number) {
const v = ((lon + 180) % 360 + 360) % 360;
return v - 180;
}
export function destinationPointLngLat(
from: [number, number],
bearingDeg: number,
distanceMeters: number,
): [number, number] {
const [lonDeg, latDeg] = from;
const lat1 = latDeg * DEG2RAD;
const lon1 = lonDeg * DEG2RAD;
const brng = bearingDeg * DEG2RAD;
const dr = Math.max(0, distanceMeters) / EARTH_RADIUS_M;
if (!Number.isFinite(dr) || dr === 0) return [lonDeg, latDeg];
const sinLat1 = Math.sin(lat1);
const cosLat1 = Math.cos(lat1);
const sinDr = Math.sin(dr);
const cosDr = Math.cos(dr);
const lat2 = Math.asin(sinLat1 * cosDr + cosLat1 * sinDr * Math.cos(brng));
const lon2 =
lon1 +
Math.atan2(
Math.sin(brng) * sinDr * cosLat1,
cosDr - sinLat1 * Math.sin(lat2),
);
const outLon = wrapLonDeg(lon2 * RAD2DEG);
const outLat = clampNumber(lat2 * RAD2DEG, -85.0, 85.0);
return [outLon, outLat];
}
export function circleRingLngLat(center: [number, number], radiusMeters: number, steps = 72): [number, number][] {
const [lon0, lat0] = center;
const latRad = lat0 * DEG2RAD;
const cosLat = Math.max(1e-6, Math.cos(latRad));
const r = Math.max(0, radiusMeters);
const ring: [number, number][] = [];
for (let i = 0; i <= steps; i++) {
const a = (i / steps) * Math.PI * 2;
const dy = r * Math.sin(a);
const dx = r * Math.cos(a);
const dLat = (dy / EARTH_RADIUS_M) / DEG2RAD;
const dLon = (dx / (EARTH_RADIUS_M * cosLat)) / DEG2RAD;
ring.push([lon0 + dLon, lat0 + dLat]);
}
return ring;
}
export function normalizeAngleDeg(value: number, offset = 0): number {
const v = value + offset;
return ((v % 360) + 360) % 360;
}