When placing or dragging a waypoint within 50m of a visible POI, the waypoint snaps to the POI's exact coordinates and adopts its name. Snapping only applies to visible POIs (enabled categories, current viewport). Explicit name (from "Add as waypoint" button) is preserved without snapping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
51 lines
1.3 KiB
TypeScript
51 lines
1.3 KiB
TypeScript
import type { Poi } from "./overpass.ts";
|
|
|
|
const SNAP_DISTANCE_METERS = 50;
|
|
|
|
interface SnapResult {
|
|
lat: number;
|
|
lon: number;
|
|
name?: string;
|
|
snapped: boolean;
|
|
}
|
|
|
|
/**
|
|
* Snap a coordinate to the nearest visible POI if within threshold.
|
|
* Returns the snapped position and POI name, or the original position.
|
|
*/
|
|
export function snapToPoi(lat: number, lon: number, pois: Poi[]): SnapResult {
|
|
if (pois.length === 0) return { lat, lon, snapped: false };
|
|
|
|
let bestPoi: Poi | null = null;
|
|
let bestDist = Infinity;
|
|
|
|
for (const poi of pois) {
|
|
const dist = haversine(lat, lon, poi.lat, poi.lon);
|
|
if (dist < bestDist) {
|
|
bestDist = dist;
|
|
bestPoi = poi;
|
|
}
|
|
}
|
|
|
|
if (bestPoi && bestDist <= SNAP_DISTANCE_METERS) {
|
|
return {
|
|
lat: bestPoi.lat,
|
|
lon: bestPoi.lon,
|
|
name: bestPoi.name,
|
|
snapped: true,
|
|
};
|
|
}
|
|
|
|
return { lat, lon, snapped: false };
|
|
}
|
|
|
|
function haversine(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
|
const R = 6371000;
|
|
const toRad = (d: number) => (d * Math.PI) / 180;
|
|
const dLat = toRad(lat2 - lat1);
|
|
const dLon = toRad(lon2 - lon1);
|
|
const a =
|
|
Math.sin(dLat / 2) ** 2 +
|
|
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
|
|
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
}
|