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:
1 parent
5caf865fc8
commit
aa2b96ac70
11 files changed
+437
-2
No files matched your search
@@ -128,6 +128,32 @@ module Maptool
|
||||
File.read(cache)
|
||||
end
|
||||
|
||||
# Appends one GeoJSON Feature (a hand-drawn point/note, see notes.js) to
|
||||
# the FeatureCollection at `path` — or, if `path` is nil, to a new or
|
||||
# existing file named `name` inside `folder`. Deliberately overwrites in
|
||||
# place rather than disambiguating like save() does: repeat calls with
|
||||
# the same path/name are meant to grow one running "layer" file, not
|
||||
# spawn "notes-1.geojson", "notes-2.geojson", ... siblings. Returns the
|
||||
# store-relative path written to, so a caller that didn't already know
|
||||
# it (the new-file case) can show/select it afterwards.
|
||||
def append_feature(feature:, path: nil, folder: "", name: "")
|
||||
if path && !path.to_s.strip.empty?
|
||||
file = resolve_file(path)
|
||||
raise Errno::ENOENT, file unless File.file?(file)
|
||||
else
|
||||
dir = resolve_dir(folder.to_s)
|
||||
FileUtils.mkdir_p(dir)
|
||||
filename = safe_geojson_filename(name)
|
||||
file = File.join(dir, filename)
|
||||
path = folder.to_s.empty? ? filename : "#{folder}/#{filename}"
|
||||
end
|
||||
|
||||
collection = File.file?(file) ? JSON.parse(File.read(file)) : { "type" => "FeatureCollection", "features" => [] }
|
||||
(collection["features"] ||= []) << feature
|
||||
File.write(file, JSON.generate(collection))
|
||||
path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resolve_dir(folder)
|
||||
@@ -163,6 +189,17 @@ module Maptool
|
||||
base
|
||||
end
|
||||
|
||||
# Same path-safety idea as safe_filename above, but for a brand-new
|
||||
# notes layer: always .geojson (never falls back to .gpx), and named
|
||||
# after the user's own "Parking spots"-style label rather than an
|
||||
# uploaded file's original name.
|
||||
def safe_geojson_filename(name)
|
||||
base = File.basename(name.to_s.strip)
|
||||
base = "notes" if base.empty? || base == "." || base == ".."
|
||||
base = base.sub(/\.geojson\z/i, "")
|
||||
"#{base}.geojson"
|
||||
end
|
||||
|
||||
def unique_filename(dir, name)
|
||||
return name unless File.exist?(File.join(dir, name))
|
||||
|
||||
|
||||
Reference in new issue
Block a user