From cc90c32a0ee208c6bd4ec0df5d3d7f187510fb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Thu, 2 Apr 2026 20:47:17 +0100 Subject: [PATCH] Extract shared extractWaypoints into @trails-cool/gpx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both new.tsx and api.sessions.ts had duplicated track-segment waypoint extraction logic. Moved to packages/gpx as extractWaypoints(gpxData) — uses elements when present, falls back to start of each track segment + end of last. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/planner/app/routes/api.sessions.ts | 27 +++----------------- apps/planner/app/routes/new.tsx | 28 ++------------------- packages/gpx/src/index.ts | 1 + packages/gpx/src/waypoints.ts | 33 +++++++++++++++++++++++++ 4 files changed, 39 insertions(+), 50 deletions(-) create mode 100644 packages/gpx/src/waypoints.ts diff --git a/apps/planner/app/routes/api.sessions.ts b/apps/planner/app/routes/api.sessions.ts index 26ad9c5..3364385 100644 --- a/apps/planner/app/routes/api.sessions.ts +++ b/apps/planner/app/routes/api.sessions.ts @@ -1,7 +1,7 @@ import { data } from "react-router"; import type { Route } from "./+types/api.sessions"; import { createSession, listSessions } from "~/lib/sessions"; -import { parseGpxAsync } from "@trails-cool/gpx"; +import { parseGpxAsync, extractWaypoints } from "@trails-cool/gpx"; import { withDb } from "@trails-cool/db"; export async function action({ request }: Route.ActionArgs) { @@ -23,29 +23,8 @@ export async function action({ request }: Route.ActionArgs) { if (gpx) { try { const gpxData = await parseGpxAsync(gpx); - // Use explicit waypoints if present, otherwise extract from track segments - if (gpxData.waypoints.length > 0) { - initialWaypoints = gpxData.waypoints; - } else if (gpxData.tracks.length > 0) { - const extracted: Array<{ lat: number; lon: number }> = []; - for (const seg of gpxData.tracks) { - if (seg.length === 0) continue; - const first = seg[0]!; - const prev = extracted[extracted.length - 1]; - if (!prev || prev.lat !== first.lat || prev.lon !== first.lon) { - extracted.push({ lat: first.lat, lon: first.lon }); - } - } - const lastSeg = gpxData.tracks[gpxData.tracks.length - 1]!; - if (lastSeg.length > 0) { - const last = lastSeg[lastSeg.length - 1]!; - const prev = extracted[extracted.length - 1]; - if (!prev || prev.lat !== last.lat || prev.lon !== last.lon) { - extracted.push({ lat: last.lat, lon: last.lon }); - } - } - initialWaypoints = extracted; - } + const wps = extractWaypoints(gpxData); + if (wps.length > 0) initialWaypoints = wps; } catch { // Continue with empty session if GPX is invalid } diff --git a/apps/planner/app/routes/new.tsx b/apps/planner/app/routes/new.tsx index aa01380..4150c29 100644 --- a/apps/planner/app/routes/new.tsx +++ b/apps/planner/app/routes/new.tsx @@ -1,7 +1,7 @@ import { redirect, data } from "react-router"; import type { Route } from "./+types/new"; import { createSession, initializeSessionWithWaypoints } from "~/lib/sessions"; -import { parseGpxAsync } from "@trails-cool/gpx"; +import { parseGpxAsync, extractWaypoints } from "@trails-cool/gpx"; import { checkRateLimit } from "~/lib/rate-limit"; export async function loader({ request }: Route.LoaderArgs) { @@ -36,31 +36,7 @@ export async function loader({ request }: Route.LoaderArgs) { try { const gpx = decodeURIComponent(gpxEncoded); const gpxData = await parseGpxAsync(gpx); - // Use explicit waypoints if present, otherwise extract from track segments - let waypoints = gpxData.waypoints; - if (waypoints.length === 0 && gpxData.tracks.length > 0) { - const extracted: Array<{ lat: number; lon: number }> = []; - for (const seg of gpxData.tracks) { - if (seg.length === 0) continue; - const first = seg[0]!; - // Deduplicate: skip if same as previous waypoint - const prev = extracted[extracted.length - 1]; - if (!prev || prev.lat !== first.lat || prev.lon !== first.lon) { - extracted.push({ lat: first.lat, lon: first.lon }); - } - } - // Add the end of the last segment - const lastSeg = gpxData.tracks[gpxData.tracks.length - 1]!; - if (lastSeg.length > 0) { - const last = lastSeg[lastSeg.length - 1]!; - const prev = extracted[extracted.length - 1]; - if (!prev || prev.lat !== last.lat || prev.lon !== last.lon) { - extracted.push({ lat: last.lat, lon: last.lon }); - } - } - waypoints = extracted; - } - initializeSessionWithWaypoints(session.id, waypoints); + initializeSessionWithWaypoints(session.id, extractWaypoints(gpxData)); } catch { // Continue with empty session if GPX is invalid } diff --git a/packages/gpx/src/index.ts b/packages/gpx/src/index.ts index e84a560..f8887f2 100644 --- a/packages/gpx/src/index.ts +++ b/packages/gpx/src/index.ts @@ -1,3 +1,4 @@ export { parseGpxAsync } from "./parse.ts"; export { generateGpx } from "./generate.ts"; +export { extractWaypoints } from "./waypoints.ts"; export type { GpxData, TrackPoint, ElevationProfile } from "./types.ts"; diff --git a/packages/gpx/src/waypoints.ts b/packages/gpx/src/waypoints.ts new file mode 100644 index 0000000..eca8da4 --- /dev/null +++ b/packages/gpx/src/waypoints.ts @@ -0,0 +1,33 @@ +import type { GpxData } from "./types.ts"; + +/** + * Extract waypoints from parsed GPX data. + * Uses explicit elements if present, otherwise extracts the start + * of each track segment + end of the last segment. + */ +export function extractWaypoints(gpxData: GpxData): Array<{ lat: number; lon: number; name?: string }> { + if (gpxData.waypoints.length > 0) return gpxData.waypoints; + if (gpxData.tracks.length === 0) return []; + + const result: Array<{ lat: number; lon: number }> = []; + for (const seg of gpxData.tracks) { + if (seg.length === 0) continue; + const first = seg[0]!; + const prev = result[result.length - 1]; + if (!prev || prev.lat !== first.lat || prev.lon !== first.lon) { + result.push({ lat: first.lat, lon: first.lon }); + } + } + + // Add end of last segment + const lastSeg = gpxData.tracks[gpxData.tracks.length - 1]!; + if (lastSeg.length > 0) { + const last = lastSeg[lastSeg.length - 1]!; + const prev = result[result.length - 1]; + if (!prev || prev.lat !== last.lat || prev.lon !== last.lon) { + result.push({ lat: last.lat, lon: last.lon }); + } + } + + return result; +}