The routeData Y.Map's ~15 string keys (geojson, coordinates, segmentBoundaries, road metadata, profile, colorMode, baseLayer, overlays, poiCategories) were read and written raw at ~30 call sites, each with its own JSON parsing and casts; parseJsonArray existed twice and waypoint extraction four times. GPX assembly was duplicated between SaveToJournalButton and ExportButton, so the saved plan and the exported file could silently diverge. - new lib/route-data.ts owns the routeData (+ noGoAreas) schema: typed read/write, JSON encoding internal, ColorMode moves here (re-exported from ColoredRoute for existing importers) - new lib/gpx-export.ts owns GPX assembly: buildRouteGpx / buildPlanGpx / buildDayGpxFiles / hasDayBreaks; multi-day splitting becomes a pure, tested function - waypoint-ymap.ts gains extractWaypoints / extractWaypointData; the four hand-rolled copies (use-routing, use-waypoint-manager, WaypointSidebar, use-days) now share it, and WaypointSidebar's moveWaypoint reuses the round-trip helpers instead of re-listing every waypoint field - all hooks/components consume the seam; no raw routeData key strings remain outside route-data.ts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
86 lines
2.2 KiB
TypeScript
86 lines
2.2 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import * as Y from "yjs";
|
|
import type { ElevationPoint } from "~/lib/elevation-chart-draw";
|
|
import {
|
|
getColorMode,
|
|
getGeojson,
|
|
readRoadMetadata,
|
|
type ColorMode,
|
|
type RoadMetadata,
|
|
} from "~/lib/route-data";
|
|
|
|
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));
|
|
}
|
|
|
|
function extractElevation(geojsonStr: string): ElevationPoint[] {
|
|
try {
|
|
const geojson = JSON.parse(geojsonStr);
|
|
const coords: number[][] = geojson.features?.[0]?.geometry?.coordinates ?? [];
|
|
if (coords.length === 0) return [];
|
|
|
|
const points: ElevationPoint[] = [];
|
|
let totalDist = 0;
|
|
|
|
for (let i = 0; i < coords.length; i++) {
|
|
if (i > 0) {
|
|
const prev = coords[i - 1]!;
|
|
const curr = coords[i]!;
|
|
totalDist += haversine(prev[1]!, prev[0]!, curr[1]!, curr[0]!);
|
|
}
|
|
if (coords[i]![2] !== undefined) {
|
|
points.push({
|
|
distance: totalDist,
|
|
elevation: coords[i]![2]!,
|
|
lat: coords[i]![1]!,
|
|
lon: coords[i]![0]!,
|
|
});
|
|
}
|
|
}
|
|
return points;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export interface ElevationData extends RoadMetadata {
|
|
points: ElevationPoint[];
|
|
colorMode: ColorMode;
|
|
}
|
|
|
|
export function useElevationData(routeData: Y.Map<unknown>): ElevationData {
|
|
const [data, setData] = useState<ElevationData>({
|
|
points: [],
|
|
colorMode: "plain",
|
|
surfaces: [],
|
|
highways: [],
|
|
maxspeeds: [],
|
|
smoothnesses: [],
|
|
tracktypes: [],
|
|
cycleways: [],
|
|
bikeroutes: [],
|
|
});
|
|
|
|
useEffect(() => {
|
|
const update = () => {
|
|
const geojson = getGeojson(routeData);
|
|
setData({
|
|
points: geojson ? extractElevation(geojson) : [],
|
|
colorMode: getColorMode(routeData),
|
|
...readRoadMetadata(routeData),
|
|
});
|
|
};
|
|
routeData.observe(update);
|
|
update();
|
|
return () => routeData.unobserve(update);
|
|
}, [routeData]);
|
|
|
|
return data;
|
|
}
|