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; }