diff --git a/TODO.adoc b/TODO.adoc
index 3c98ea3..bd4736d 100644
--- a/TODO.adoc
+++ b/TODO.adoc
@@ -40,8 +40,8 @@ independence from connectivity.
== Custom layers & data
* [x] GPX/GeoJSON drag-and-drop import (Wikiloc, Komoot, own GPX tracks — also FIT), auto-styled by category with a generated elevation profile — KML not supported (only GPX/FIT/GeoJSON/.zip)
-* [x] User overlay: offline drawing of custom points with notes (e.g. "turning space for a long vehicle", "narrow gate", "good swimming spot") — click the map, pick or name a `.geojson` layer, saved into the same GpxStore the Tracks panel already reads (so visibility/popup come for free)
-** [ ] Polygon notes (not just points) — needs gpx.js's point/line split (`_addLayers`) taught a third, Polygon-shaped bucket first, or a saved polygon wouldn't render back on reload
+* [x] User overlay: offline drawing of custom points *and areas* with notes (e.g. "turning space for a long vehicle", "narrow gate", "good swimming spot") — Point/Area mode toggle with a live drawing preview, saved into the same GpxStore the Tracks panel already reads (so visibility/popup come for free); gpx.js's `_addLayers` now renders Polygon features (fill + outline) alongside points/lines, not just points
+** [ ] The Unigrid Matrix category filter only re-slices the *points* source (`_applyCategoryFilter`) — toggling the "Note" category off hides point notes but not polygon ones sharing that category
* [ ] Offline satellite tile caching (e.g. ESRI) for a selected route area — key for judging surface quality and obstacles when wild camping
== Nice to have (later)
diff --git a/public/css/style.css b/public/css/style.css
index 7bd78d6..6f09f2e 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -774,17 +774,63 @@ html, body {
display: none;
}
+.notes-panel__modes {
+ display: flex;
+ gap: 4px;
+ margin-bottom: 6px;
+}
+
+.notes-panel__mode {
+ flex: 1;
+ width: auto;
+ height: auto;
+ padding: 4px 8px;
+ border: none;
+ border-radius: 4px;
+ background: #f0f0f0;
+ font: inherit;
+ font-size: 12px;
+ cursor: pointer;
+}
+
+.notes-panel__mode.active {
+ background: #dbe9ff;
+ font-weight: 600;
+}
+
.notes-panel__hint {
- margin: 0;
+ margin: 0 0 6px;
color: #888;
}
+.notes-panel__finish {
+ width: 100%;
+ box-sizing: border-box;
+ margin-bottom: 6px;
+ padding: 4px 8px;
+ border: none;
+ border-radius: 4px;
+ background: #1a5fb4;
+ color: #fff;
+ font: inherit;
+ font-size: 12px;
+ cursor: pointer;
+}
+
.notes-panel__form {
display: flex;
flex-direction: column;
gap: 6px;
}
+/* Same "author display wins over the UA [hidden] rule" gotcha as
+ .gpx-panel__action[hidden] above — both this and .notes-panel__modes set
+ their own "display", so hiding them needs an explicit override too. */
+.notes-panel__form[hidden],
+.notes-panel__modes[hidden] {
+ display: none;
+}
+
.notes-panel__text {
width: 100%;
box-sizing: border-box;
diff --git a/public/js/gpx.js b/public/js/gpx.js
index f741d0b..473483a 100644
--- a/public/js/gpx.js
+++ b/public/js/gpx.js
@@ -540,6 +540,29 @@ export class GpxPanelControl {
});
this._registerLineInteractivity(`${linesId}-line`);
+ // Polygon notes (see notes.js) — the same "lines" source already only
+ // renders LineString geometry through the line layer above (MapLibre's
+ // own "line" layer type silently ignores Polygon features), so this is
+ // just two more layers reading the same source, not a new bucket.
+ // Popup-on-click like a waypoint (_registerPointInteractivity), not the
+ // track line's hover-summary/profile-panel behavior above — a note
+ // polygon has no elevation profile to open.
+ this._map.addLayer({
+ id: `${linesId}-polygon-fill`,
+ type: "fill",
+ source: linesId,
+ filter: ["==", ["geometry-type"], "Polygon"],
+ paint: { "fill-color": TRACK_COLOR, "fill-opacity": 0.2 },
+ });
+ this._map.addLayer({
+ id: `${linesId}-polygon-outline`,
+ type: "line",
+ source: linesId,
+ filter: ["==", ["geometry-type"], "Polygon"],
+ paint: { "line-color": TRACK_COLOR, "line-width": 2 },
+ });
+ this._registerPointInteractivity(`${linesId}-polygon-fill`);
+
// clusterRadius/clusterMaxZoom are harmless when there's nothing dense
// enough to cluster (a handful of waypoints just render individually).
this._map.addSource(pointsId, { type: "geojson", data: points, cluster: true, clusterRadius: 50, clusterMaxZoom: 14 });
@@ -704,6 +727,8 @@ export class GpxPanelControl {
const pointsId = pointsSourceId(path);
[
`${linesId}-line`,
+ `${linesId}-polygon-fill`,
+ `${linesId}-polygon-outline`,
`${pointsId}-clusters`,
`${pointsId}-cluster-count`,
`${pointsId}-unclustered`,
diff --git a/public/js/notes.js b/public/js/notes.js
index 4c2e356..c440a2f 100644
--- a/public/js/notes.js
+++ b/public/js/notes.js
@@ -2,20 +2,26 @@ import { NOTE_PIN_ICON } from "/js/icons.js";
import { notifyGpxStoreChanged } from "/js/panels.js";
const NEW_LAYER_VALUE = "__new__";
+const PREVIEW_SOURCE_ID = "notes-preview";
+const PREVIEW_FILL_LAYER_ID = "notes-preview-fill";
+const PREVIEW_LINE_LAYER_ID = "notes-preview-line";
+const PREVIEW_POINT_LAYER_ID = "notes-preview-points";
+const PREVIEW_COLOR = "#c0392b";
-// A quick "pin it with a note" tool — click the map, write a short note,
-// pick which .geojson "layer" file it joins (or start a new one). Saved
-// server-side (POST /api/gpx/notes) into the same GpxStore everything else
-// in the Tracks panel already reads from, so a note shows up there with no
+// A quick "pin it with a note" tool — click the map (Point mode) or click
+// out a few corners and Finish (Area mode), write a short note, pick which
+// .geojson "layer" file it joins (or start a new one). Saved server-side
+// (POST /api/gpx/notes) into the same GpxStore everything else in the
+// Tracks panel already reads from, so a note shows up there with no
// separate rendering path of its own: same tree, same checkbox visibility,
-// same popup. Points only for now — a polygon mode would need gpx.js's
-// point/line split (public/js/gpx.js's _addLayers) taught a third,
-// Polygon-shaped bucket first, so it's left for a follow-up rather than
-// saving areas that wouldn't render back once reloaded.
+// same popup — public/js/gpx.js's _addLayers renders a Polygon feature the
+// same way it already renders points/lines, no separate code path here.
export class NotesControl {
constructor() {
this._active = false;
- this._pendingLngLat = null;
+ this._mode = "point"; // "point" | "area"
+ this._areaPoints = []; // vertices while drawing an area, before Finish
+ this._pendingGeometry = null; // the finished Point/Polygon geometry, once placed
}
onAdd(map) {
@@ -26,7 +32,12 @@ export class NotesControl {
this._container.innerHTML = `