feat(hikes): multi-day stages (separate GPX tracks, stage nav, builder)
Represent a multi-day hike as separate named GPX <trk> elements, one per stage, while still treating the whole thing as one route on the overview. GPX & build: - gpx.ts: parseGpxStages (one stage per <trk>) + multi-track buildGpx. - build-hikes.ts: per-stage stats with totals summed across stages so the overnight gaps (distance, time) and the altitude jump between stages are excluded; previewBreaks recorded where stages sit >1 km apart. - types: HikeStage, manifest `stages?` and `previewBreaks?` (both optional — single-stage hikes are unchanged). Detail page: - HikeStageNav: a light itinerary-stepper switcher (numbered nodes, active glows in the accent) writing a shared stageStore. - Selecting a stage scopes the metrics, elevation profile (x-window), map (highlight + zoom, dim the rest) and photo strip/markers; "Alle Etappen" shows the whole route. Overview: live map and the prerendered static composite both break the preview line across >1 km inter-stage transfers (previewBreaks). Route builder: - Mark any placed waypoint as a stage start (named) from the waypoint list or the detail panel; export assembles each stage independently into its own <trk>; import re-marks stage boundaries from a multi-track GPX.
This commit is contained in:
@@ -361,6 +361,9 @@ export async function renderStaticMap(opts: RenderStaticMapOpts): Promise<boolea
|
||||
export interface RenderOverviewPolyline {
|
||||
points: Array<[number, number]>;
|
||||
color: string;
|
||||
/** Indices where a new disconnected sub-path begins (multi-day stage gaps
|
||||
* >1 km), so the line isn't drawn across an overnight transfer. */
|
||||
breaks?: number[];
|
||||
}
|
||||
|
||||
export interface RenderOverviewMapOpts {
|
||||
@@ -388,13 +391,16 @@ export async function renderOverviewMap(opts: RenderOverviewMapOpts): Promise<bo
|
||||
// zoomed-out, so even ≤150-point preview polylines stay compact.
|
||||
const paths = drawable
|
||||
.map((line) => {
|
||||
const breakSet = new Set(line.breaks ?? []);
|
||||
const parts: string[] = [];
|
||||
for (let i = 0; i < line.points.length; i++) {
|
||||
const [lat, lng] = line.points[i];
|
||||
const p = lngLatToPx(lng, lat, zoom);
|
||||
const px = p.x - originX;
|
||||
const py = p.y - originY;
|
||||
parts.push((i === 0 ? 'M' : 'L') + escapeSvgNumber(px) + ',' + escapeSvgNumber(py));
|
||||
// Start a fresh sub-path at index 0 and at every stage break.
|
||||
const cmd = i === 0 || breakSet.has(i) ? 'M' : 'L';
|
||||
parts.push(cmd + escapeSvgNumber(px) + ',' + escapeSvgNumber(py));
|
||||
}
|
||||
return (
|
||||
`<path d="${parts.join(' ')}" fill="none" stroke="${line.color}" ` +
|
||||
|
||||
Reference in New Issue
Block a user