Support uploading .geojson files directly
CI / check (push) Successful in 45s
CI / deploy (push) Successful in 15s

A .geojson file needs no GPX/FIT conversion — it's already the shape
gpx.js expects — so GpxStore#geojson now just reads and returns it
as-is, no cache sidecar. save/tree/the upload dropzone and its zip
handling all pick it up through the existing TRACK_EXTENSIONS list.

Also fixes a bug the new "cached" tree icon would have shipped with:
a GPX/FIT's own .geojson cache sidecar (e.g. "ride.gpx.geojson") also
matches the new .geojson branch in list_children, which would have
listed it as a second, phantom tree entry — excluded explicitly, with
a regression test.
This commit is contained in:
randy committed 2026-09-07 22:48:58 +02:00
1 parent 44d235bbcf
commit b4f205667b
8 files changed
+110 -19

No files matched your search

+20 -11
View File
@@ -221,11 +221,11 @@ This is a live, online-only overlay — it always needs internet access
and is unrelated to the offline PMTiles basemap above. Per RainViewer's
terms, the API is free for personal/non-commercial use.
== GPX/FIT tracks and waypoints
== GPX/FIT/GeoJSON tracks and waypoints
The "Tracks" map control (`public/js/gpx.js`) opens a side panel to
upload GPX or FIT files by dragging them in, organize them into
folders (also by dragging), and toggle each one on the map
upload GPX, FIT or GeoJSON files by dragging them in, organize them
into folders (also by dragging), and toggle each one on the map
individually.
Storage:: Files are stored as-is under `data/gpx/`, in a
@@ -243,13 +243,18 @@ which the panel renders recursively (folders as `<details>`, files as
a checkbox, both with a delete button).
Drag & drop:: The dropzone at the top of the panel accepts one or more
`.gpx`/`.fit` files, or a `.zip` (`POST /api/gpx`, multipart) into the
top level, or click it to browse instead. A `.zip` — Garmin's bulk/FIT
export format — isn't stored itself: `save_zip_entries` (`app.rb`)
expands it with `rubyzip` and saves each `.gpx`/`.fit` entry it
contains individually (flattened — any folder structure inside the
zip is discarded), skipping anything else (readme files, etc.) it
might hold. An already-uploaded file (a
`.gpx`/`.fit`/`.geojson` files, or a `.zip` (`POST /api/gpx`,
multipart) into the top level, or click it to browse instead. A
`.geojson` file needs no format conversion — it's already the shape
`gpx.js` expects — so `GpxStore#geojson` just serves it straight from
disk (see "Rendering" below); useful for a file already in this app's
own schema, e.g. one exported elsewhere in that shape, or a previous
`GET /api/gpx/file` response saved and re-uploaded. A `.zip` —
Garmin's bulk/FIT export format — isn't stored itself:
`save_zip_entries` (`app.rb`) expands it with `rubyzip` and saves each
`.gpx`/`.fit`/`.geojson` entry it contains individually (flattened —
any folder structure inside the zip is discarded), skipping anything
else (readme files, etc.) it might hold. An already-uploaded file (a
`.gpx-tree__file` row, `draggable`) can be dragged onto a folder to
move it there (`POST /api/gpx/move`), or dropped anywhere else in the
tree to move it back to the top level; an OS file can also be dropped
@@ -276,7 +281,11 @@ status) instead of on whichever toggle happens to be first — a silent
multi-second hang on a checkbox click reads as broken in a way the
same wait during an upload doesn't. A file that fails to parse still
saves fine either way; the error just surfaces on first view instead.
`Gpx` parses the GPX with REXML: `<wpt>`
A `.geojson` upload skips this path entirely — `geojson` reads and
returns it directly, no cache file, no `Gpx`/`Fit` involved; the file
tree's cached-file icon (`GEOJSON_ICON`, `public/js/icons.js`) always
shows for one, since there's no conversion step it could ever be
waiting on. `Gpx` parses the GPX with REXML: `<wpt>`
waypoints become `Point` features (with `name`, `desc` and `time`
carried through as properties), each `<trk>` becomes one
`MultiLineString` feature (one line per `<trkseg>`), and each `<rte>`