// Distance units. // // Everything is COMPUTED in kilometres — the great-circle maths, the backend's // distance_km on a spot, the map's path lengths — and converted once, here, at // display time. Storing miles anywhere would mean two sources of truth for the // same number and a rounding error that grows with every hop. // // The preference is portable (see lib/uiPref): an operator who works in miles // works in miles on every machine they copy their folder to. import { writeUiPref } from '@/lib/uiPref'; export const KEY_MILES = 'opslog.distanceMiles'; const KM_PER_MILE = 1.609344; // statute miles, the ones a US licence is used in export function useMiles(): boolean { try { return localStorage.getItem(KEY_MILES) === '1'; } catch { return false; } } export function setUseMiles(on: boolean): void { writeUiPref(KEY_MILES, on ? '1' : '0'); listeners.forEach((l) => l()); } // subscribeDistanceUnit notifies on a change. The grids capture the unit inside // their column definitions (header text and formatter both), so without this a // toggle would only show up on the next language change or restart. const listeners = new Set<() => void>(); export function subscribeDistanceUnit(fn: () => void): () => void { listeners.add(fn); return () => { listeners.delete(fn); }; } // distanceValue converts a distance in km to the operator's unit, rounded to a // whole unit — the precision the inputs actually justify (a 4-character grid is // a square tens of kilometres wide). export function distanceValue(km: number): number { if (!isFinite(km)) return 0; return Math.round(useMiles() ? km / KM_PER_MILE : km); } // distanceUnit is the short label: "km" or "mi". export function distanceUnit(): string { return useMiles() ? 'mi' : 'km'; } // formatDistance is value + unit, thousands-separated: "12 345 km". export function formatDistance(km: number): string { return `${distanceValue(km).toLocaleString()} ${distanceUnit()}`; }