Class ParallelWays
- java.lang.Object
-
- org.openstreetmap.josm.actions.mapmode.ParallelWays
-
public class ParallelWays extends java.lang.Object
Helper forParallelWayAction.Computes a one-sided offset ("parallel") of a branchless path made of one or more ways.
The algorithm is a proper one-sided buffer, which also works when the offset is much larger than the length of the segments of the source path (e.g. a maritime boundary 22 km off a coastline):
- Each segment is offset by the requested distance.
- At every vertex the two neighbouring offset segments are joined: on the inner side of a turn they are clipped at their intersection, on the outer side a mitre is used for gentle turns and a circular arc (approximated by chords) for sharp turns or when the mitre would overshoot too far.
- The resulting raw polyline is split at its self-intersections. Every piece is kept only if it lies at (at least) the offset distance from the source path. Pieces which are closer belong to inverted loops ("swallowtails") and are dropped. The remaining pieces are chained; the longest chain is the result.
Contrary to earlier versions the nodes and ways are only created (and added to the data set) by
commit(), since the number of nodes of the result depends on the offset. UsegetOffsetPoints()to draw a preview while the offset is changed.
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description private static classParallelWays.ChainA chain of connected valid piecesstatic classParallelWays.ClosestPointThe point of the source path closest to a given point, seeclosestPoint(EastNorth).private static classParallelWays.PiecesGrowable list of pieces (sub segments of the raw polyline)private static classParallelWays.PointsPoint table used while trimming: raw points first, then intersection and transition pointsprivate static classParallelWays.RawPolylineThe raw (untrimmed) offset polyline.
-
Field Summary
Fields Modifier and Type Field Description private doublearcStepprivate static intCAPmarker for raw segments of the end caps, which are only used to trim the result and are never part of itprivate booleanclosedprivate booleancopyTagsstatic doubleDEFAULT_ARC_STEP_DEGREESDefault angular step used to approximate circular arcs, in degreesprivate double[]dirXunit direction of segment iprivate double[]dirYprivate doublegridCellprivate int[][]gridCellsprivate intgridColsprivate doublegridMinXprivate doublegridMinYprivate intgridQueryprivate intgridRowsprivate int[]gridStampprivate static intNO_NODEprivate intnodeCountprivate double[]pxprivate double[]pyprivate booleanresultClosedprivate int[]resultPieceSegprivate int[]resultPointNodeprivate java.util.List<EastNorth>resultPtsprivate double[]segLenprivate double[]segMaxXprivate double[]segMaxYprivate double[]segMinXbounding boxes of the source segments, used to speed up distance computationsprivate double[]segMinYprivate int[]segWaysource way index for each source segmentprivate java.util.List<Node>sortedNodesthe source nodes, in path order (duplicates by coordinate removed, oriented like the reference way)private java.util.List<Way>sourceWaysprivate boolean[]wayForwardwhether the source way runs in the same direction as sortedNodes (aligned with sourceWays)private java.util.List<Way>ways
-
Constructor Summary
Constructors Constructor Description ParallelWays(java.util.Collection<Way> sourceWays, boolean copyTags, int refWayIndex)Constructs a newParallelWays.ParallelWays(java.util.Collection<Way> sourceWays, boolean copyTags, int refWayIndex, double arcStepDegrees)Constructs a newParallelWays.
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description private voidaddCap(ParallelWays.RawPolyline raw, int k, double startAngle, int direction, double d, boolean leading)Adds the chords of a half circle around nodek, starting at the given angle.private voidaddJoin(ParallelWays.RawPolyline raw, int k, int prev, int next, double d)Adds the offset points at vertexk, where segmentprevends and segmentnextstarts.private voidbuildGrid(double r)Builds a uniform grid over the source segments, with a cell size of (at least) r, so that all segments within distance r of a point are found in the 3x3 cells around it.private ParallelWays.RawPolylinebuildRawOffset(double d)private java.util.List<Way>buildWays()Builds the (not yet added) ways from the last computed offset path.voidchangeOffset(double d)Offsets the way(s) d units.private voidclassify(ParallelWays.RawPolyline raw, double r, ParallelWays.Points points, ParallelWays.Pieces pieces, int i, int sId, int eId)Determines the validity of the piece of raw segmentibetween two points, and adds the resulting piece(s).ParallelWays.ClosestPointclosestPoint(EastNorth p)Finds the point of the source path closest to the given point.voidcommit()Creates the nodes and ways of the offset path (as computed by the last call tochangeOffset(double)), and adds them to the edit data set by adding a new sequence command to the undo/redo queue.private doubledistanceToSource(double x, double y, double cutoff)Distance from a point to the source path.private static ParallelWays.ChainfindChainEndingAt(java.util.List<ParallelWays.Chain> chains, int id, ParallelWays.Points points, double tolerance)Finds the chain ending at (or within the tolerance of) the given point, preferring an exact match.private voidfindSelfIntersections(ParallelWays.RawPolyline raw, int rawSegCount, boolean[] skip, ParallelWays.Points points, java.util.List<java.util.List<double[]>> breaks)Finds all intersections between non adjacent segments of the raw polyline (sweep on x).private voidforEachCell(double minX, double minY, double maxX, double maxY, java.util.function.IntConsumer consumer)java.util.List<EastNorth>getOffsetPoints()Returns the points of the offset path computed by the last call tochangeOffset(double).java.util.List<Way>getWays()Returns the resulting parallel ways, available aftercommit().booleanisClosedPath()Determines if the nodes graph form a closed pathprivate static booleanisForward(java.util.List<Node> path, Way way, boolean cyclic)Checks whether a way runs in the same direction as a node list.booleanisResultClosed()Determines if the result of the lastchangeOffset(double)call is a closed ring.private booleanisValid(double x, double y, double r, double tolerance, int arc)Checks whether a point of the raw polyline lies at (at least) distance r from the source path.private doublelocateTransition(double[] s, double[] e, double a, double b, double r, double tolerance, int arc)Locates (by bisection) the parameter betweenaandbwhere the validity changes.private java.util.List<Command>makeAddWayAndNodesCommandList()private NodemakeNode(int idx)private static voidreplaceIntactArcsByMitres(ParallelWays.RawPolyline raw, ParallelWays.Points points, java.util.List<java.lang.Integer> ids, java.util.List<java.lang.Integer> segs)Replaces arcs which are completely part of the result by a mitre (where the corner is gentle enough).private static booleansegmentIntersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double[] uv)Segment/segment intersection.private voidsetEmptyResult()private voidtrim(ParallelWays.RawPolyline raw, double r)
-
-
-
Field Detail
-
DEFAULT_ARC_STEP_DEGREES
public static final double DEFAULT_ARC_STEP_DEGREES
Default angular step used to approximate circular arcs, in degrees- See Also:
- Constant Field Values
-
NO_NODE
private static final int NO_NODE
- See Also:
- Constant Field Values
-
CAP
private static final int CAP
marker for raw segments of the end caps, which are only used to trim the result and are never part of it- See Also:
- Constant Field Values
-
sourceWays
private final java.util.List<Way> sourceWays
-
copyTags
private final boolean copyTags
-
arcStep
private final double arcStep
-
sortedNodes
private final java.util.List<Node> sortedNodes
the source nodes, in path order (duplicates by coordinate removed, oriented like the reference way)
-
closed
private final boolean closed
-
nodeCount
private final int nodeCount
-
px
private final double[] px
-
py
private final double[] py
-
dirX
private final double[] dirX
unit direction of segment i
-
dirY
private final double[] dirY
-
segLen
private final double[] segLen
-
segMinX
private final double[] segMinX
bounding boxes of the source segments, used to speed up distance computations
-
segMinY
private final double[] segMinY
-
segMaxX
private final double[] segMaxX
-
segMaxY
private final double[] segMaxY
-
wayForward
private final boolean[] wayForward
whether the source way runs in the same direction as sortedNodes (aligned with sourceWays)
-
segWay
private final int[] segWay
source way index for each source segment
-
gridCell
private double gridCell
-
gridMinX
private double gridMinX
-
gridMinY
private double gridMinY
-
gridCols
private int gridCols
-
gridRows
private int gridRows
-
gridCells
private int[][] gridCells
-
gridStamp
private int[] gridStamp
-
gridQuery
private int gridQuery
-
resultPieceSeg
private int[] resultPieceSeg
-
resultPointNode
private int[] resultPointNode
-
resultClosed
private boolean resultClosed
-
-
Constructor Detail
-
ParallelWays
public ParallelWays(java.util.Collection<Way> sourceWays, boolean copyTags, int refWayIndex)
Constructs a newParallelWays.- Parameters:
sourceWays- source wayscopyTags- whether tags should be copiedrefWayIndex- Need a reference way to determine the direction of the offset when we manage multiple ways- Throws:
java.lang.IllegalArgumentException- if the ways do not form a branchless path
-
ParallelWays
public ParallelWays(java.util.Collection<Way> sourceWays, boolean copyTags, int refWayIndex, double arcStepDegrees)
Constructs a newParallelWays.- Parameters:
sourceWays- source wayscopyTags- whether tags should be copiedrefWayIndex- Need a reference way to determine the direction of the offset when we manage multiple waysarcStepDegrees- angular step (in degrees) of the chords approximating arcs at convex corners- Throws:
java.lang.IllegalArgumentException- if the ways do not form a branchless path- Since:
- 19624
-
-
Method Detail
-
isForward
private static boolean isForward(java.util.List<Node> path, Way way, boolean cyclic)
Checks whether a way runs in the same direction as a node list.- Parameters:
path- the node listway- the waycyclic- whether the node list is a ring (without repeated closing node)- Returns:
trueif the first segment of the way, found in the path, has the same orientation
-
isClosedPath
public boolean isClosedPath()
Determines if the nodes graph form a closed path- Returns:
trueif the nodes graph form a closed path
-
closestPoint
public ParallelWays.ClosestPoint closestPoint(EastNorth p)
Finds the point of the source path closest to the given point. Used to relate the offset to the part of the path the mouse is currently next to (rather than to the segment where the drag started).- Parameters:
p- the point (projected coordinates)- Returns:
- the closest point, the segment it lies on and the signed distance
- Since:
- 19624
-
changeOffset
public void changeOffset(double d)
Offsets the way(s) d units. Positive d means to the left (relative to the reference way)- Parameters:
d- offset
-
buildGrid
private void buildGrid(double r)
Builds a uniform grid over the source segments, with a cell size of (at least) r, so that all segments within distance r of a point are found in the 3x3 cells around it.- Parameters:
r- the (absolute) offset
-
forEachCell
private void forEachCell(double minX, double minY, double maxX, double maxY, java.util.function.IntConsumer consumer)
-
buildRawOffset
private ParallelWays.RawPolyline buildRawOffset(double d)
-
addCap
private void addCap(ParallelWays.RawPolyline raw, int k, double startAngle, int direction, double d, boolean leading)
Adds the chords of a half circle around nodek, starting at the given angle. The chords are marked asCAP: they are only used to trim other pieces.- Parameters:
raw- the raw polylinek- source node indexstartAngle- angle of the first point of the half circledirection- rotation direction (+1 counter clockwise)d- the offsetleading-trueif the cap precedes the offset path (the end point of the half circle is then added by the caller),falseif it follows it (the start point has been added by the caller)
-
addJoin
private void addJoin(ParallelWays.RawPolyline raw, int k, int prev, int next, double d)
Adds the offset points at vertexk, where segmentprevends and segmentnextstarts.- Parameters:
raw- the raw polylinek- source node indexprev- index of the segment ending at knext- index of the segment starting at kd- the offset
-
trim
private void trim(ParallelWays.RawPolyline raw, double r)
-
setEmptyResult
private void setEmptyResult()
-
classify
private void classify(ParallelWays.RawPolyline raw, double r, ParallelWays.Points points, ParallelWays.Pieces pieces, int i, int sId, int eId)
Determines the validity of the piece of raw segmentibetween two points, and adds the resulting piece(s). The validity is sampled at both ends and in the middle; if it changes, the transition point is located and the piece is split there.
-
locateTransition
private double locateTransition(double[] s, double[] e, double a, double b, double r, double tolerance, int arc)
Locates (by bisection) the parameter betweenaandbwhere the validity changes.- Returns:
- the parameter of the transition
-
isValid
private boolean isValid(double x, double y, double r, double tolerance, int arc)
Checks whether a point of the raw polyline lies at (at least) distance r from the source path.- Parameters:
x- pointy- pointr- offsettolerance- allowed deficitarc- if the point lies on an arc chord: the center node of the arc; the point is then projected onto the arc before testing, elseNO_NODE- Returns:
trueif the point is valid
-
replaceIntactArcsByMitres
private static void replaceIntactArcsByMitres(ParallelWays.RawPolyline raw, ParallelWays.Points points, java.util.List<java.lang.Integer> ids, java.util.List<java.lang.Integer> segs)
Replaces arcs which are completely part of the result by a mitre (where the corner is gentle enough).
-
findChainEndingAt
private static ParallelWays.Chain findChainEndingAt(java.util.List<ParallelWays.Chain> chains, int id, ParallelWays.Points points, double tolerance)
Finds the chain ending at (or within the tolerance of) the given point, preferring an exact match.- Returns:
- the chain, or
null
-
findSelfIntersections
private void findSelfIntersections(ParallelWays.RawPolyline raw, int rawSegCount, boolean[] skip, ParallelWays.Points points, java.util.List<java.util.List<double[]>> breaks)
Finds all intersections between non adjacent segments of the raw polyline (sweep on x).- Parameters:
raw- the raw polylinerawSegCount- number of raw segmentsskip- raw segments to ignorepoints- point table, intersection points are appendedbreaks- per raw segment list of (param, pointId), filled
-
segmentIntersection
private static boolean segmentIntersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double[] uv)
Segment/segment intersection.- Parameters:
uv- output: parameters along the first and the second segment- Returns:
- true if the segments intersect (touching end points count as intersection)
-
distanceToSource
private double distanceToSource(double x, double y, double cutoff)
Distance from a point to the source path.- Parameters:
x- pointy- pointcutoff- the search can stop as soon as a distance below this value is found- Returns:
- the distance (or any value below cutoff if such a distance exists)
-
getOffsetPoints
public java.util.List<EastNorth> getOffsetPoints()
Returns the points of the offset path computed by the last call tochangeOffset(double). For a closed result the first point is not repeated at the end, seeisResultClosed().- Returns:
- the offset points (projected coordinates), empty if nothing has been computed or nothing remains
- Since:
- 19624
-
isResultClosed
public boolean isResultClosed()
Determines if the result of the lastchangeOffset(double)call is a closed ring.- Returns:
trueif the offset path is a closed ring- Since:
- 19624
-
commit
public void commit()
Creates the nodes and ways of the offset path (as computed by the last call tochangeOffset(double)), and adds them to the edit data set by adding a new sequence command to the undo/redo queue.Does nothing if there is no offset path.
-
makeAddWayAndNodesCommandList
private java.util.List<Command> makeAddWayAndNodesCommandList()
-
buildWays
private java.util.List<Way> buildWays()
Builds the (not yet added) ways from the last computed offset path.- Returns:
- the ways, in the order of the source ways; ways swallowed by the offset are omitted
-
-