// 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; public class WayConnectionTypeCalculator { private static final int UNCONNECTED = Integer.MIN_VALUE; private List members; /** * refresh the cache of member WayConnectionTypes */ public List updateLinks(List members) { this.members = members; final List 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) { final RelationMember m = members.get(i); if (!m.isWay() || m.getWay() == null || m.getWay().isIncomplete()) { if (i > 0) { makeLoopIfNeeded(con, i-1); } con.set(i, new WayConnectionType()); firstGroupIdx = i; continue; } 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)) { 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; } } if (wct.linkPrev) { if (lastBackwardWay != UNCONNECTED && lastForwardWay != UNCONNECTED) { determineOnewayConnectionType(con, m, i, wct); if (!wct.linkPrev) { firstGroupIdx = i; } } if (!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); lastWct = wct; if (!wct.linkPrev) { if (i > 0) { makeLoopIfNeeded(con, i-1); } firstGroupIdx = i; } } makeLoopIfNeeded(con, members.size()-1); return con; } private int firstGroupIdx; private void makeLoopIfNeeded(final List 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 con, RelationMember m, int i, final WayConnectionType wct) { Direction dirFW = determineDirection(lastForwardWay, con.get(lastForwardWay).direction, i); Direction dirBW = NONE; 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 ref_i, Direction ref_direction, int k) { return determineDirection(ref_i, ref_direction, k, false); } /** * Determines the direction of way k with respect to the way ref_i. * The way ref_i is assumed to have the direction ref_direction and * to be the predecessor of 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. * **/ private Direction determineDirection(int ref_i, final Direction ref_direction, int k, boolean reversed) { if (ref_i < 0 || k < 0 || ref_i >= members.size() || k >= members.size()) return NONE; if (ref_direction == NONE) return NONE; final RelationMember m_ref = members.get(ref_i); final RelationMember m = members.get(k); Way way_ref = null; Way way = null; if (m_ref.isWay()) { way_ref = m_ref.getWay(); } if (m.isWay()) { way = m.getWay(); } if (way_ref == null || way == null) return NONE; /** the list of nodes the way k can dock to */ List refNodes = new ArrayList<>(); switch (ref_direction) { case FORWARD: refNodes.add(way_ref.lastNode()); break; case BACKWARD: refNodes.add(way_ref.firstNode()); break; case ROUNDABOUT_LEFT: case ROUNDABOUT_RIGHT: refNodes = way_ref.getNodes(); break; } 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 (n == RelationNodeMap.lastOnewayNode(m) && reversed) { if (RelationSortUtils.isBackward(m)) return FORWARD; else return BACKWARD; } } else { if (n == way.firstNode()) return FORWARD; if (n == way.lastNode()) return BACKWARD; } } return NONE; } }