Skip to content

feat: add snap to polylines#3342

Open
int-72h wants to merge 3 commits into
umap-project:masterfrom
int-72h:snap
Open

feat: add snap to polylines#3342
int-72h wants to merge 3 commits into
umap-project:masterfrom
int-72h:snap

Conversation

@int-72h

@int-72h int-72h commented May 21, 2026

Copy link
Copy Markdown

This PR adds snap functionality when drawing and editing maps. It closes #2159.
I've used Claude to write most of it, as a result I'm marking the PR as a draft until I'm confident it hasn't pumped out complete slop. I'll let it explain what it's done:

Snap to existing features while editing

Adds snap-to-feature when drawing and editing maps (à la Google My Maps), using the makinacorpus/Leaflet.Snap library adapted to uMap's Leaflet.Editable stack.

Behaviour

  • Drawing a line/polygon/marker: the cursor and placed vertices snap onto nearby features, with a visual indicator (incl. the first vertex).
  • Dragging an existing marker/vertex: snaps via Leaflet.Snap's MarkerSnap.
  • New snapDistance setting (default 15px, 0 disables; per-map and ?snapDistance= override).

Changes

  • Vendored leaflet-snap + leaflet-geometryutil (package.json, vendorsjs.sh, js.html).
  • Snap adapter in umap.controls.js; snapDistance in schema.js/help.js.
  • Fix: setPropertiesFromQueryString asNumber wrote to an undefined var and lacked a presence guard.
  • Integration tests in test_snap.py.

Notes

  • Snapping is on by default (15px) — flip the schema default to 0 for opt-in.
  • Not run against full CI / real browser; unit + targeted integration tests pass.

🤖 Generated with Claude Code

int-72h and others added 3 commits May 21, 2026 02:31
Add snap-to-point support on the frontend using Leaflet.Snap
(makinacorpus). uMap uses Leaflet.Editable rather than Leaflet.Draw, so
the library's Draw hooks are unused; instead a MarkerSnap handler is
attached to the markers Editable exposes (dragged vertices, dragged
point features, and markers being placed).

- Vendor leaflet-geometryutil + leaflet-snap into vendors/snap/ and load
  them as classic scripts after Leaflet.Editable.
- New `snapDistance` setting (default 15px, 0 disables), overridable per
  map and via the `?snapDistance=` querystring.
- Construct MarkerSnap without the marker arg to avoid the removed
  L.Handler.MarkerDrag, and re-apply the snapped latlng on commit since
  Editable overwrites a placed marker with the raw click position.
- Exclude the edited feature from guide layers (Leaflet.Snap's own
  self-exclusion relies on Leaflet.Draw internals Editable lacks).
- Fix asNumber() in setPropertiesFromQueryString: it wrote to an
  undefined `name` and had no presence guard, so Number querystring
  overrides never applied.
- Add integration tests covering snapping and the disabled case.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The initial snapping only attached MarkerSnap to markers/vertices the
user drags, so drawing a new line or polygon never snapped — the main
behaviour people expect (à la Google My Maps) was missing, making it
feel weak and intermittent.

Snap the raw map pointer events instead: registered at editor setup,
the handler runs before Leaflet.Editable's own mousemove/mouseup
listeners and, since Leaflet shares one event object across a fire,
rewriting `latlng` makes both the live rubber-band and the committed
vertex/marker snap onto existing features. Marker placement now goes
through this path too (dropping the previous commit-time reapply hack);
MarkerSnap is kept only for dragging existing geometry, where the
position comes from the icon rather than the event.

Guide layers are cached per drawing session. Paths currently being
edited are excluded: Leaflet.Editable stamps a circular `__vertex`
back-reference on their latlngs and the snap library's closest()
JSON-serializes the geometry, which would otherwise throw.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The placed coordinate already snaps for every drawn vertex, but only
points after the first get visual feedback: the rubber-band line bends
onto the target as you hover. The first vertex has no rubber-band, so
within the snap radius it snapped silently with no cue — making it feel
like the starting point doesn't snap at all.

Draw a small circle at the snapped position whenever the pointer is
within snapDistance of a feature during drawing (mousemove included), so
the first vertex gets the same feedback as the rest, à la Google My Maps.
The indicator is hidden when the pointer leaves snap range and when
drawing ends.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@int-72h

int-72h commented May 21, 2026

Copy link
Copy Markdown
Author

It all seems to be reasonably sane; though one may want to trim back the comments?
The biggest code hog is the wrapper required to get leaflet-snap working with Editables - the rest seems to be reasonable. Somebody more familiar with the codebase should look at it though.

@int-72h int-72h marked this pull request as ready for review May 21, 2026 14:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Snap Interaction

1 participant