// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.dialogs.relation.sort; import static org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType.Direction.BACKWARD; import static org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType.Direction.FORWARD; import static org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType.Direction.NONE; import java.util.ArrayList; import java.util.List; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.dialogs.relation.sort.WayConnectionType.Direction; import org.openstreetmap.josm.tools.JosmRuntimeException; import org.openstreetmap.josm.tools.bugreport.BugReport; public class WayConnectionTypeCalculator { private static final int UNCONNECTED = Integer.MIN_VALUE; private List<RelationMember> members; /** * refresh the cache of member WayConnectionTypes * @param members relation members * @return way connections */ public List<WayConnectionType> updateLinks(List<RelationMember> members) { this.members = members; final List<WayConnectionType> con = new ArrayList<>(); for (int i = 0; i < members.size(); ++i) { con.add(null); } firstGroupIdx = 0; lastForwardWay = UNCONNECTED; lastBackwardWay = UNCONNECTED; onewayBeginning = false; WayConnectionType lastWct = null; for (int i = 0; i < members.size(); ++i) { try { lastWct = updateLinksFor(con, lastWct, i); } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) { int index = i; throw BugReport.intercept(e).put("i", i).put("member", () -> members.get(index)).put("con", con); } } makeLoopIfNeeded(con, members.size()-1); return con; } private WayConnectionType updateLinksFor(final List<WayConnectionType> con, WayConnectionType lastWct, int i) { final RelationMember m = members.get(i); if (isNoHandleableWay(m)) { if (i > 0) { makeLoopIfNeeded(con, i-1); } con.set(i, new WayConnectionType()); firstGroupIdx = i; } else { WayConnectionType wct = computeNextWayConnection(con, lastWct, i, m); if (!wct.linkPrev) { if (i > 0) { makeLoopIfNeeded(con, i-1); } firstGroupIdx = i; } return wct; } return lastWct; } private static boolean isNoHandleableWay(final RelationMember m) { return !m.isWay() || m.getWay() == null || m.getWay().isIncomplete(); } private WayConnectionType computeNextWayConnection(final List<WayConnectionType> con, WayConnectionType lastWct, int i, final RelationMember m) { WayConnectionType wct = new WayConnectionType(false); wct.linkPrev = i > 0 && con.get(i-1) != null && con.get(i-1).isValid(); wct.direction = NONE; if (RelationSortUtils.isOneway(m)) { handleOneway(lastWct, i, wct); } if (wct.linkPrev) { if (lastBackwardWay != UNCONNECTED && lastForwardWay != UNCONNECTED) { determineOnewayConnectionType(con, m, i, wct); if (!wct.linkPrev) { firstGroupIdx = i; } } if (lastWct != null && !RelationSortUtils.isOneway(m)) { wct.direction = determineDirection(i-1, lastWct.direction, i); wct.linkPrev = wct.direction != NONE; } } if (!wct.linkPrev) { wct.direction = determineDirectionOfFirst(i, m); if (RelationSortUtils.isOneway(m)) { wct.isOnewayLoopForwardPart = true; lastForwardWay = i; } } wct.linkNext = false; if (lastWct != null) { lastWct.linkNext = wct.linkPrev; } con.set(i, wct); return wct; } private void handleOneway(WayConnectionType lastWct, int i, WayConnectionType wct) { if (lastWct != null && lastWct.isOnewayTail) { wct.isOnewayHead = true; } if (lastBackwardWay == UNCONNECTED && lastForwardWay == UNCONNECTED) { //Beginning of new oneway wct.isOnewayHead = true; lastForwardWay = i-1; lastBackwardWay = i-1; onewayBeginning = true; } } private int firstGroupIdx; private void makeLoopIfNeeded(final List<WayConnectionType> con, final int i) { boolean loop; if (i == firstGroupIdx) { //is primitive loop loop = determineDirection(i, FORWARD, i) == FORWARD; } else { loop = determineDirection(i, con.get(i).direction, firstGroupIdx) == con.get(firstGroupIdx).direction; } if (loop) { for (int j = firstGroupIdx; j <= i; ++j) { con.get(j).isLoop = true; } } } private Direction determineDirectionOfFirst(final int i, final RelationMember m) { Direction result = RelationSortUtils.roundaboutType(m); if (result != NONE) return result; if (RelationSortUtils.isOneway(m)) { if (RelationSortUtils.isBackward(m)) return BACKWARD; else return FORWARD; } else { /** guess the direction and see if it fits with the next member */ if (determineDirection(i, FORWARD, i+1) != NONE) return FORWARD; if (determineDirection(i, BACKWARD, i+1) != NONE) return BACKWARD; } return NONE; } private int lastForwardWay; private int lastBackwardWay; private boolean onewayBeginning; private void determineOnewayConnectionType(final List<WayConnectionType> con, RelationMember m, int i, final WayConnectionType wct) { Direction dirFW = determineDirection(lastForwardWay, con.get(lastForwardWay).direction, i); Direction dirBW; if (onewayBeginning) { if (lastBackwardWay < 0) { dirBW = determineDirection(firstGroupIdx, reverse(con.get(firstGroupIdx).direction), i, true); } else { dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true); } if (dirBW != NONE) { onewayBeginning = false; } } else { dirBW = determineDirection(lastBackwardWay, con.get(lastBackwardWay).direction, i, true); } if (RelationSortUtils.isOneway(m)) { if (dirBW != NONE) { wct.direction = dirBW; lastBackwardWay = i; wct.isOnewayLoopBackwardPart = true; } if (dirFW != NONE) { wct.direction = dirFW; lastForwardWay = i; wct.isOnewayLoopForwardPart = true; } // Not connected to previous if (dirFW == NONE && dirBW == NONE) { wct.linkPrev = false; if (RelationSortUtils.isOneway(m)) { wct.isOnewayHead = true; lastForwardWay = i-1; lastBackwardWay = i-1; } else { lastForwardWay = UNCONNECTED; lastBackwardWay = UNCONNECTED; } onewayBeginning = true; } if (dirFW != NONE && dirBW != NONE) { //End of oneway loop if (i+1 < members.size() && determineDirection(i, dirFW, i+1) != NONE) { wct.isOnewayLoopBackwardPart = false; wct.direction = dirFW; } else { wct.isOnewayLoopForwardPart = false; wct.direction = dirBW; } wct.isOnewayTail = true; } } else { lastForwardWay = UNCONNECTED; lastBackwardWay = UNCONNECTED; if (dirFW == NONE || dirBW == NONE) { wct.linkPrev = false; } } } private static Direction reverse(final Direction dir) { if (dir == FORWARD) return BACKWARD; if (dir == BACKWARD) return FORWARD; return dir; } private Direction determineDirection(int refI, Direction refDirection, int k) { return determineDirection(refI, refDirection, k, false); } /** * Determines the direction of way {@code k} with respect to the way {@code ref_i}. * The way {@code ref_i} is assumed to have the direction {@code ref_direction} and to be the predecessor of {@code k}. * * If both ways are not linked in any way, NONE is returned. * * Else the direction is given as follows: * Let the relation be a route of oneway streets, and someone travels them in the given order. * Direction is FORWARD if it is legal and BACKWARD if it is illegal to do so for the given way. * @param refI way key * @param refDirection direction of ref_i * @param k successor of ref_i * @param reversed if {@code true} determine reverse direction * @return direction of way {@code k} */ private Direction determineDirection(int refI, final Direction refDirection, int k, boolean reversed) { if (members == null || refI < 0 || k < 0 || refI >= members.size() || k >= members.size() || refDirection == NONE) return NONE; final RelationMember mRef = members.get(refI); final RelationMember m = members.get(k); Way wayRef = null; Way way = null; if (mRef.isWay()) { wayRef = mRef.getWay(); } if (m.isWay()) { way = m.getWay(); } if (wayRef == null || way == null) return NONE; /** the list of nodes the way k can dock to */ List<Node> refNodes = new ArrayList<>(); switch (refDirection) { case FORWARD: refNodes.add(wayRef.lastNode()); break; case BACKWARD: refNodes.add(wayRef.firstNode()); break; case ROUNDABOUT_LEFT: case ROUNDABOUT_RIGHT: refNodes = wayRef.getNodes(); break; default: // Do nothing } for (Node n : refNodes) { if (n == null) { continue; } if (RelationSortUtils.roundaboutType(members.get(k)) != NONE) { for (Node nn : way.getNodes()) { if (n == nn) return RelationSortUtils.roundaboutType(members.get(k)); } } else if (RelationSortUtils.isOneway(m)) { if (n == RelationNodeMap.firstOnewayNode(m) && !reversed) { if (RelationSortUtils.isBackward(m)) return BACKWARD; else return FORWARD; } if (reversed && n == RelationNodeMap.lastOnewayNode(m)) { if (RelationSortUtils.isBackward(m)) return FORWARD; else return BACKWARD; } } else { if (n == way.firstNode()) return FORWARD; if (n == way.lastNode()) return BACKWARD; } } return NONE; } }