feat(journey): draw the trips' GPX tracks on the journey map (#1911)

* feat(journey): serve the GPX tracks behind a journey

A journey has no trip of its own, but every entry records the trip it came from,
and a trip's routed geometries live on its places. So the tracks are derivable
without any new storage: GET /journeys/:id/tracks walks that link and returns the
geometries, dropping elevation nothing on the map reads and skipping anything
that is not a usable line.

* feat(journey): draw those tracks on the journey map

Both renderers, Leaflet and GL, plus the mobile map-timeline that shares them.
Solid and in the track's own colour with a white casing, so it reads as a
recorded route rather than as the dashed line that merely connects entries in
time order, and stays legible on satellite tiles.

* feat(journey): load the tracks alongside the journey

Fetched separately from the journey itself: most journeys have no tracks, and a
failed lookup should cost the map its lines rather than the whole page.

* docs(journey): document the GPX tracks on the map

* fix(journey): label tracks with the house map tooltip

bindTooltip's default box ignores the appearance tokens, so it sat there in
Leaflet grey next to markers already using .map-tooltip. Same class now, and an
unnamed track gets no tooltip at all instead of an empty one.
This commit is contained in:
Maurice
2026-08-12 15:14:50 +02:00
committed by GitHub
parent 767dc662b7
commit 870bd6131f
15 changed files with 334 additions and 11 deletions
+21
View File
@@ -130,3 +130,24 @@ export const journeyShareLinkRequestSchema = z.looseObject({
share_map: z.unknown().optional(),
});
export type JourneyShareLinkRequest = z.infer<typeof journeyShareLinkRequestSchema>;
/**
* GPX tracks a journey can draw (#1260). A journey has no trip of its own, but its
* entries record the trip and place they came from, so the tracks are the routed
* geometries of the places in those trips. Points are [lat, lng] pairs, matching the
* order the GPX importer stored them in; elevation, where the import kept it, is
* dropped here because nothing on the map reads it.
*/
export const journeyTrackSchema = z.object({
place_id: z.number(),
trip_id: z.number(),
name: z.string(),
color: z.string().nullable(),
points: z.array(z.tuple([z.number(), z.number()])),
});
export type JourneyTrack = z.infer<typeof journeyTrackSchema>;
export const journeyTracksResponseSchema = z.object({
tracks: z.array(journeyTrackSchema),
});
export type JourneyTracksResponse = z.infer<typeof journeyTracksResponseSchema>;