feat(editable-layers) Snap to edges#623
Conversation
charlieforward9
left a comment
There was a problem hiding this comment.
Thanks for pushing this forward. The feature direction is good, and the PR shows that different modes need different snapping behavior.
The main design question is whether this should remain a generic SnappableMode enhancement or become a broader mode-owned snapping API.
In the current implementation, modes expose getSnappingBehavior(), but SnappableMode still owns most of the behavior matrix: FromSnapSources / WhenDragging / Freehand, event rewriting, target exclusion, and guide rendering. That leaves the design partly centralized in SnappableMode and partly delegated to individual modes.
That split has a few implications:
- If
SnappableModeremains the public compatibility surface, it likely needs cleaner mode-level hooks so it can stay generic: e.g. how a mode defines snap sources, snap targets, event eligibility, and snap application. - If snapping becomes fully mode-owned, that is a larger public API shift. Consumers with custom modes may need to reimplement or migrate snapping behavior.
- The second path likely needs a longer migration timeline, v9 deprecation warnings, and clearer v10 documentation.
There is also at least one concrete follow-up to check: TransformMode now wraps TranslateMode in SnappableMode, but existing priority logic checks for TranslateMode directly. That may change scale/translate interaction behavior unless the wrapper is accounted for.
Could you pick which direction you want to take here?
- Keep
SnappableModegeneric and refactor this PR so modes provide snapping policy through hooks/strategy. - Treat this as the start of a mode-owned snapping API, with
SnappableModedeprecation/migration planned separately.
|
@charlieforward9 I have updated the PR. I think the best direction to take is:
I have adapted the first version into a distinct SnappingStrategy interface to provide a simple hook that could be overriden by consumers if required. This means the behaviour is now divided as follows:
|
|
I have done some profiling of the utlity functions to try to get a sense of how fast or slow the snapping is. The results are fine for small geometries but do scale up as shapes get more complex since there isn't a lot of optimisation in the code at the moment e.g. indexing. (All values are mean values in ms) getSnapTargetHandles (vertex snapping)
getSnapTargetHandles (edge snapping)
getClosestSnapTargetHandle
On the order of magnitude of 1000 vertices this seems reasonable and is probably workable around 10,000. But if we need to go up to 100,000 then it's going to take about a second to update snapping guides. @charlieforward9 would be curious for your take on what the non-functional requirements for latency etc. should be for this feature since I imagine you have more of an insight on the different use cases for this library. To improve the latency the natural path seems to be to bring in the rbush library for a spatial index and the challenge will be building and maintaining the spatial index if the features change. |
#586
This PR comprises two main changes - firstly it adds support for snapping mode to a much wider variety of modes and secondly adds support for snapping to edges.
It turned out different modes had different requirements for snapping:
I added support for three different
SnappingStrategys that modes can utilise (plus not supported):Each strategy defines how to handle click events, how to handle movement events and how to draw guides which provides enough diversity of behaviour to meet the requirements above. Behaviour shared between different strategies lives in
snapping-utils.ts.I also added edge snapping which can be enabled by setting the
edgeSnappingmode config totrue.Testing:
Videos:
TranslateMode -
Translate.Mode.mp4
DrawLineStringMode -
DrawLineString.Mode.mp4
ModifyMode -
Modify.Mode.mp4
Alternative designs:
There are definitely cons to this design of specifying the behaviour on the
GeoJsonEditModebecause this adds extra complexity and it is also frustrating if people come up with their own custom modes which don't fit with one of these built-in behaviours.It's also not ideal that to enable snapping you both need to wrap your mode in a
SnappableModeand turn onenableSnapping. It should be sufficient to just make one change to turn on snapping.An alternative design that we should consider is deprecating the pre-existing
SnappableModeand starting from scratch. Instead of trying to apply snapping generically in a composite mode we could dictate that each mode is responsible of it's own snapping behaviour and then use gracefully constructed utility function to handle the duplicated logic where appropriate. This would give mode authors more control to specify how snapping should work specifically for that mode.Considerations
Breaking Changes
Custom modes which used the
SnappableMode(without extending one of the existing modes) will need to declare:to get working snapping again.