/* * geo.ts — spherical geo math for the route model (D-07). * -------------------------------------------------------- * haversineM: great-circle distance in meters on the mean-earth sphere * (radius 6 371 000 m — at KL-metro scale the model error is far below the * fixture corpus's 200 m origin tolerance). Finite for finite inputs. * * deriveCumulativeDistanceM: the snapped-progress support field (D-07 — the * GUID-05/06 upstream obligation). Produces [0, d(p0→p1), d(p0→p1)+d(p1→p2), * …] — the same length as the input, [0] === 0, non-decreasing. Phase 6's * guidance snaps a live fix's progress along the line using this array * instead of re-deriving it per fix; the fixture source derives it once at * decode/load and memoizes (RESEARCH Finding 1). * * Pure: no I/O, no logging, no storage (the D-15 analog — nothing about a * request outlives the caller's frame). */ import type { RoutePoint } from "./types.ts"; const EARTH_RADIUS_M = 6_371_000; const DEGREES_TO_RADIANS = Math.PI / 180; /** Great-circle distance between two WGS84 points, in meters. */ export function haversineM(a: RoutePoint, b: RoutePoint): number { const lat1 = a.lat * DEGREES_TO_RADIANS; const lat2 = b.lat * DEGREES_TO_RADIANS; const dLat = (b.lat - a.lat) * DEGREES_TO_RADIANS; const dLng = (b.lng - a.lng) * DEGREES_TO_RADIANS; const sinLat = Math.sin(dLat / 2); const sinLng = Math.sin(dLng / 2); const h = sinLat * sinLat + Math.cos(lat1) * Math.cos(lat2) * sinLng * sinLng; return 2 * EARTH_RADIUS_M * Math.asin(Math.min(1, Math.sqrt(h))); } /** Meters walked along the polyline: same length as points, [0] === 0, * non-decreasing. Empty -> []; single point -> [0]. */ export function deriveCumulativeDistanceM(points: readonly RoutePoint[]): number[] { if (points.length === 0) return []; const cumulative: number[] = [0]; for (let i = 1; i < points.length; i++) { const prev = points[i - 1]; const curr = points[i]; const prevMeters = cumulative[i - 1]; if (prev === undefined || curr === undefined || prevMeters === undefined) break; cumulative.push(prevMeters + haversineM(prev, curr)); } return cumulative; }