// 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.NONE;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
/**
* Auxiliary class for relation sorting.
*
* Constructs two mappings: One that maps each way to its nodes and the inverse mapping that
* maps each node to all ways that have this node.
* After construction both maps are consistent, but later on objects that are no longer needed
* are removed from the value sets.
* However the corresponding keys are not deleted even if they map to an empty set.
* Note that normal ways have 2 nodes (beginning and end) but roundabouts can have less or more
* (that are shared by other members).
*
* @author Christiaan Welvaart <cjw@time4t.net>
* @since 1785
*/
public class RelationNodeMap {
private static class NodesWays {
public final Map<Node, Set<Integer>> nodes = new TreeMap<>();
public final Map<Integer, Set<Node>> ways = new TreeMap<>();
public final boolean oneWay;
NodesWays(boolean oneWay) {
this.oneWay = oneWay;
}
}
/*
* the maps. (Need TreeMap for efficiency.)
*/
private final NodesWays map = new NodesWays(false);
/*
* Maps for oneways (forward/backward roles)
*/
private final NodesWays onewayMap = new NodesWays(true);
private final NodesWays onewayReverseMap = new NodesWays(true);
/*
* Used to keep track of what members are done.
*/
private final Set<Integer> remaining = new TreeSet<>();
private final Map<Integer, Set<Node>> remainingOneway = new TreeMap<>();
/**
* All members that are incomplete or not a way
*/
private final List<Integer> notSortable = new ArrayList<>();
public static Node firstOnewayNode(RelationMember m) {
if (!m.isWay()) return null;
if ("backward".equals(m.getRole())) {
return m.getWay().lastNode();
}
return m.getWay().firstNode();
}
public static Node lastOnewayNode(RelationMember m) {
if (!m.isWay()) return null;
if ("backward".equals(m.getRole())) {
return m.getWay().firstNode();
}
return m.getWay().lastNode();
}
RelationNodeMap(List<RelationMember> members) {
for (int i = 0; i < members.size(); ++i) {
RelationMember m = members.get(i);
if (m.getMember().isIncomplete() || !m.isWay() || m.getWay().getNodesCount() < 2) {
notSortable.add(i);
continue;
}
Way w = m.getWay();
if (RelationSortUtils.roundaboutType(w) != NONE) {
for (Node nd : w.getNodes()) {
addPair(nd, i);
}
} else if (RelationSortUtils.isOneway(m)) {
addNodeWayMap(firstOnewayNode(m), i);
addWayNodeMap(lastOnewayNode(m), i);
addNodeWayMapReverse(lastOnewayNode(m), i);
addWayNodeMapReverse(firstOnewayNode(m), i);
addRemainingForward(firstOnewayNode(m), i);
addRemainingForward(lastOnewayNode(m), i);
} else {
addPair(w.firstNode(), i);
addPair(w.lastNode(), i);
}
}
remaining.addAll(map.ways.keySet());
}
private void addPair(Node n, int i) {
Set<Integer> ts = map.nodes.get(n);
if (ts == null) {
ts = new TreeSet<>();
map.nodes.put(n, ts);
}
ts.add(i);
Set<Node> ts2 = map.ways.get(i);
if (ts2 == null) {
ts2 = new TreeSet<>();
map.ways.put(i, ts2);
}
ts2.add(n);
}
private void addNodeWayMap(Node n, int i) {
Set<Integer> ts = onewayMap.nodes.get(n);
if (ts == null) {
ts = new TreeSet<>();
onewayMap.nodes.put(n, ts);
}
ts.add(i);
}
private void addWayNodeMap(Node n, int i) {
Set<Node> ts2 = onewayMap.ways.get(i);
if (ts2 == null) {
ts2 = new TreeSet<>();
onewayMap.ways.put(i, ts2);
}
ts2.add(n);
}
private void addNodeWayMapReverse(Node n, int i) {
Set<Integer> ts = onewayReverseMap.nodes.get(n);
if (ts == null) {
ts = new TreeSet<>();
onewayReverseMap.nodes.put(n, ts);
}
ts.add(i);
}
private void addWayNodeMapReverse(Node n, int i) {
Set<Node> ts2 = onewayReverseMap.ways.get(i);
if (ts2 == null) {
ts2 = new TreeSet<>();
onewayReverseMap.ways.put(i, ts2);
}
ts2.add(n);
}
private void addRemainingForward(Node n, int i) {
Set<Node> ts2 = remainingOneway.get(i);
if (ts2 == null) {
ts2 = new TreeSet<>();
remainingOneway.put(i, ts2);
}
ts2.add(n);
}
private Integer firstOneway;
private Node lastOnewayNode;
private Node firstCircular;
/**
* Return a relation member that is linked to the member 'i', but has not been popped yet.
* Return null if there is no such member left.
* @param way way key
* @return a relation member that is linked to the member 'i', but has not been popped yet
*/
public Integer popAdjacent(Integer way) {
if (lastOnewayNode != null) return popBackwardOnewayPart(way);
if (firstOneway != null) return popForwardOnewayPart(way);
if (map.ways.containsKey(way)) {
for (Node n : map.ways.get(way)) {
Integer i = deleteAndGetAdjacentNode(map, n);
if (i != null) return i;
Integer j = deleteAndGetAdjacentNode(onewayMap, n);
if (j != null) {
firstOneway = j;
return j;
}
}
}
firstOneway = way;
return popForwardOnewayPart(way);
}
private Integer popForwardOnewayPart(Integer way) {
if (onewayMap.ways.containsKey(way)) {
for (Node n : onewayMap.ways.get(way)) {
Integer i = findAdjacentWay(onewayMap, n);
if (i == null) {
continue;
}
lastOnewayNode = processBackwardIfEndOfLoopReached(i);
if (lastOnewayNode != null)
return popBackwardOnewayPart(firstOneway);
deleteWayNode(onewayMap, i, n);
return i;
}
}
firstOneway = null;
return null;
}
private Node processBackwardIfEndOfLoopReached(Integer way) { //find if we didn't reach end of the loop (and process backward part)
if (onewayReverseMap.ways.containsKey(way)) {
for (Node n : onewayReverseMap.ways.get(way)) {
if ((map.nodes.containsKey(n))
|| (onewayMap.nodes.containsKey(n) && onewayMap.nodes.get(n).size() > 1))
return n;
if (firstCircular != null && firstCircular == n)
return firstCircular;
}
}
return null;
}
private Integer popBackwardOnewayPart(int way) {
if (lastOnewayNode != null) {
Set<Node> nodes = new TreeSet<>();
if (onewayReverseMap.ways.containsKey(way)) {
nodes.addAll(onewayReverseMap.ways.get(way));
}
if (map.ways.containsKey(way)) {
nodes.addAll(map.ways.get(way));
}
for (Node n : nodes) {
if (n == lastOnewayNode) { //if oneway part ends
firstOneway = null;
lastOnewayNode = null;
Integer j = deleteAndGetAdjacentNode(map, n);
if (j != null) return j;
Integer k = deleteAndGetAdjacentNode(onewayMap, n);
if (k != null) {
firstOneway = k;
return k;
}
}
Integer j = deleteAndGetAdjacentNode(onewayReverseMap, n);
if (j != null) return j;
}
}
firstOneway = null;
lastOnewayNode = null;
return null;
}
/**
* find next node in nw NodeWays structure, if the node is found delete and return it
* @param nw nodes and ways
* @param n node
* @return node next to n
*/
private Integer deleteAndGetAdjacentNode(NodesWays nw, Node n) {
Integer j = findAdjacentWay(nw, n);
if (j == null) return null;
deleteWayNode(nw, j, n);
return j;
}
private static Integer findAdjacentWay(NodesWays nw, Node n) {
Set<Integer> adj = nw.nodes.get(n);
if (adj == null || adj.isEmpty()) return null;
return adj.iterator().next();
}
private void deleteWayNode(NodesWays nw, Integer way, Node n) {
if (nw.oneWay) {
doneOneway(way);
} else {
done(way);
}
nw.ways.get(way).remove(n);
}
/**
* Returns some remaining member or null if every sortable member has been processed.
* @return member key
*/
public Integer pop() {
if (!remaining.isEmpty()) {
Integer i = remaining.iterator().next();
done(i);
return i;
}
if (remainingOneway.isEmpty()) return null;
for (Integer i : remainingOneway.keySet()) { //find oneway, which is connected to more than one way (is between two oneway loops)
for (Node n : onewayReverseMap.ways.get(i)) {
if (onewayReverseMap.nodes.containsKey(n) && onewayReverseMap.nodes.get(n).size() > 1) {
doneOneway(i);
firstCircular = n;
return i;
}
}
}
Integer i = remainingOneway.keySet().iterator().next();
doneOneway(i);
return i;
}
/**
* This relation member has been processed.
* Remove references in the map.nodes.
* @param i member key
*/
private void doneOneway(Integer i) {
Set<Node> nodesForward = remainingOneway.get(i);
for (Node n : nodesForward) {
if (onewayMap.nodes.containsKey(n)) {
onewayMap.nodes.get(n).remove(i);
}
if (onewayReverseMap.nodes.containsKey(n)) {
onewayReverseMap.nodes.get(n).remove(i);
}
}
remainingOneway.remove(i);
}
private void done(Integer i) {
remaining.remove(i);
Set<Node> nodes = map.ways.get(i);
for (Node n : nodes) {
boolean result = map.nodes.get(n).remove(i);
if (!result) throw new AssertionError();
}
}
public List<Integer> getNotSortableMembers() {
return notSortable;
}
}