Modify

#24891 new enhancement

[PATCH] Parallel ways: compute a proper one-sided buffer so large offsets work

Reported by: wangi Owned by: team
Priority: normal Milestone:
Component: Core Version:
Keywords: Cc:

Description

Parallel mode produces spikes and loops when the offset exceeds the local radius of curvature (e.g. a maritime boundary 22 km off a coastline)

What steps will reproduce the problem?

  1. Load a way with many short segments and some sharp turns, e.g. a natural=coastline way with a few hundred nodes and segments of 50-300 m.
  2. Switch to Parallel mode (Shift+P) and start dragging the way sideways.
  3. Keep dragging until the offset is a few times the length of the segments (a few km for a coastline; a 12 nm / 22 km territorial water boundary is a typical use).

What is the expected result?

A parallel way at the requested distance, everywhere at least that distance from the source way, following the envelope of the source: straight parts are shifted, convex corners are rounded (or mitred where that is harmless), concave corners are clipped, and parts of the source which are "swallowed" by the offset (bays narrower than twice the offset, small notches) disappear from the result. The same shape a GIS buffer operation yields, restricted to one side of the way.

What happens instead?

As soon as the offset is larger than the length of the neighbouring segments (more precisely: the local radius of curvature at a concave corner), the result explodes into spikes and loops which cover the whole screen and beyond, see the attached screenshots (first: 250 m offset, fine; second: 12 nm offset, unusable). The resulting way is useless and has to be undone.

Please provide any additional information below. Attach a screenshot if possible.

Cause: ParallelWays.changeOffset() places every node of the copy at the intersection of the two neighbouring offset *lines*, unconditionally. Whenever the offset exceeds the local radius of curvature that intersection lies far away on the wrong side of the corner ("swallowtail"), which produces the spikes. For the same reason the method cannot produce a correct result at all, since a correct offset of a polyline in general has a different number of nodes than the source.

The attached patch (against r19621) rewrites ParallelWays as a proper one-sided buffer:

  • every segment is offset; concave corners are clipped at the intersection of the two offset segments (only when it lies on both), convex corners are joined by a circular arc approximated by chords (new preference edit.make-parallel-way-action.arc-step-degrees, default 10°),
  • the raw polyline is split at its self-intersections and against half-circle caps at both ends, every piece is kept only if it lies at (at least) the offset distance from the source, the surviving pieces are chained and the longest chain is the result,
  • arcs that survive untouched at gentle corners are collapsed back to the classic mitre point, so the behaviour for the usual dual-carriageway use (small offset) is unchanged: same node count, same positions, tags copied,
  • a uniform grid over the source segments and pruning of raw segments which lie entirely inside keep it interactive: a 1600-node path recomputes in 15-70 ms on a laptop.

Since the number of nodes of the result now depends on the offset, ParallelWayAction creates the nodes and ways on mouse release (single undoable command, result selected as before) and draws a preview of the parallel on the temporary layer while dragging (new stroke preference edit.make-parallel-way-action.stroke.preview). Several selected ways still yield one way each, sharing the boundary nodes and keeping their direction; a way completely swallowed by the offset is omitted; if nothing remains a notification is shown.

ParallelWays public API: the constructor, isClosedPath(), changeOffset(), commit() and getWays() are kept (getWays() is empty before commit()), getOffsetPoints() and isResultClosed() are added for the preview.

Tests: new ParallelWaysTest (small/large offsets of a line, a square inside/outside/swallowed, a sawtooth "coast" from 10 m to 22 km, a seeded 1600-node random coastline, multi-way commit with tags/direction/undo, a ring made of two ways) checking that every vertex is at least the offset away from the source and that the result has no self-intersections. Existing ParallelWayActionTest (incl. the #20908 regression) passes unchanged. Checkstyle is clean.

Known limitations of the patch: the preview during the drag is a plain line rather than the styled rendering of a real way (the way did not exist before release); when the inward offset of a closed way splits into several lobes only the longest lobe is kept; arcs are chords, so vertices can lie inside the true offset by up to about 2·r·(1−cos 5°) ≈ 0.8 %.

Attachments (4)

josm-parallel-way-buffer.patch (71.0 KB ) - added by wangi 44 minutes ago.
screenshot-250m.png (583.7 KB ) - added by wangi 34 minutes ago.
Good outcome at 250m
screenshot-12nm.png (1.8 MB ) - added by wangi 33 minutes ago.
Bad outcome at 12nm
screenshot-12nm-fixed.png (1.5 MB ) - added by wangi 33 minutes ago.
Good outcome at 12nm, with new code

Change History (4)

by wangi, 34 minutes ago

Attachment: screenshot-250m.png added

Good outcome at 250m

by wangi, 33 minutes ago

Attachment: screenshot-12nm.png added

Bad outcome at 12nm

by wangi, 33 minutes ago

Attachment: screenshot-12nm-fixed.png added

Good outcome at 12nm, with new code

Modify Ticket

Change Properties
Set your email in Preferences
Action
as new The owner will remain team.
as The resolution will be set. Next status will be 'closed'.
to The owner will be changed from team to the specified user.
Next status will be 'needinfo'. The owner will be changed from team to wangi.
as duplicate The resolution will be set to duplicate. Next status will be 'closed'. The specified ticket will be cross-referenced with this ticket.
The owner will be changed from team to anonymous. Next status will be 'assigned'.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.