Add a "user overlay" tool: click the map, save a note

New map-corner "Add note" control (public/js/notes.js), mirroring
measure.js's click-to-place UX. Click the map, write a short note,
pick an existing .geojson layer file or name a new one — saved via a
new GpxStore#append_feature (grows the target file in place, unlike
save()'s always-disambiguate-the-filename behavior) through a new
POST /api/gpx/notes route.

No new rendering path: a note is just another point in a .geojson
file the Tracks panel's tree already lists, checkboxes, and pops up —
visibility toggling ("layers") comes for free from that existing
machinery, per-file, with no changes needed there.

Points only for now — a polygon mode would need gpx.js's point/line
split taught a third Polygon bucket first, or a saved polygon
wouldn't render back on reload (see TODO.adoc).

Covered by new tests: GpxStore#append_feature (create/append/missing-
path/filename-safety) and the /api/gpx/notes route (auth gate, create,
append, malformed/missing-geometry rejection, 404 on a missing
target). Full suite: 97 runs, 0 failures.
This commit is contained in:
randy committed 2026-09-09 10:18:50 +02:00
1 parent 5caf865fc8
commit aa2b96ac70
11 files changed
+437 -2

No files matched your search

+28
View File
@@ -226,6 +226,34 @@ module Maptool
json paths: paths, tree: GPX_STORE.tree
end
# A hand-drawn point/note (see notes.js) — unlike the upload above, this
# grows one existing/new .geojson "layer" file in place (GpxStore#
# append_feature) rather than always landing as its own new file.
post "/api/gpx/notes" do
require_login!
begin
feature = JSON.parse(params[:feature].to_s)
rescue JSON::ParserError
halt 400, json(error: "invalid feature")
end
halt 400, json(error: "invalid feature") unless feature.is_a?(Hash) && feature["geometry"]
begin
path = GPX_STORE.append_feature(
feature: feature,
path: params[:path],
folder: params[:folder].to_s,
name: params[:name].to_s
)
rescue GpxStore::InvalidPath
halt 400, json(error: "invalid path")
rescue Errno::ENOENT
halt 404, json(error: "file not found")
end
json path: path, tree: GPX_STORE.tree
end
# ?download=geojson exports the file's GeoJSON as an attachment instead
# of rendering it on the map — the only way to get a .gpx/.fit's
# converted form out of the app, since GpxStore#geojson's cache is