import { haversineMeters } from "./statistics.ts"; /** Forward/backward-fills gaps from the nearest defined neighbor — assumes at least one defined value exists. */ function fillGaps(values: readonly (number | undefined)[]): number[] { const result = [...values]; for (let i = 1; i < result.length; i++) if (result[i] === undefined) result[i] = result[i - 1]; for (let i = result.length - 2; i >= 0; i--) if (result[i] === undefined) result[i] = result[i + 1]; return result as number[]; } /** Normalizes a filled series to [0, 1] by its own min/max (a flat series maps entirely to 0). */ function normalize(filled: readonly number[]): number[] { const min = Math.min(...filled); const max = Math.max(...filled); const range = max - min || 1; return filled.map((v) => (v - min) / range); } /** * Per-point elevation normalized to [0, 1] by this track's own min/max, for * `line.colorBy: "elevation"`. `undefined` if fewer than 2 points have * elevation data — not enough to establish a meaningful range. */ export function normalizeElevations( elevations: readonly (number | undefined)[], ): number[] | undefined { const defined = elevations.filter((e): e is number => e !== undefined); if (defined.length < 2) return undefined; return normalize(fillGaps(elevations)); } /** * Per-point instantaneous speed (m/s, from real-world distance / elapsed * time between consecutive points) normalized to [0, 1] by this track's own * min/max, for `line.colorBy: "speed"`. The first point copies the second's * speed (nothing precedes it to compute a delta from). `undefined` if fewer * than 2 points have a usable consecutive `