// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.gui.dialogs.relation;
import static org.openstreetmap.josm.gui.dialogs.relation.WayConnectionType.Direction.NONE;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
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>
*
*/
public class RelationNodeMap {
/*
* the maps. (Need TreeMap for efficiency.)
*/
private TreeMap<Node, TreeSet<Integer>> nodesMap;
private TreeMap<Integer, TreeSet<Node>> waysMap;
/*
* Used to keep track of what members are done.
*/
private TreeSet<Integer> remaining;
/**
* All members that are incomplete or not a way
*/
private List<Integer> notSortable = new ArrayList<Integer>();
RelationNodeMap(List<RelationMember> members) {
nodesMap = new TreeMap<Node, TreeSet<Integer>>();
waysMap = new TreeMap<Integer, TreeSet<Node>>();
for (int i = 0; i < members.size(); ++i) {
RelationMember m = members.get(i);
if (m.getMember().isIncomplete() || !m.isWay())
{
notSortable.add(i);
}
else {
Way w = m.getWay();
if (MemberTableModel.roundaboutType(w) != NONE) {
for (Node nd : w.getNodes()) {
addPair(nd, i);
}
} else {
addPair(w.firstNode(), i);
addPair(w.lastNode(), i);
}
}
}
remaining = new TreeSet<Integer>();
for (Integer k : waysMap.keySet()) {
remaining.add(k);
}
/*
* Clean up the maps, i.e. remove nodes from roundabouts and dead ends that
* cannot be used in future. (only for performance)
*/
Iterator<Map.Entry<Node,TreeSet<Integer>>> it = nodesMap.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Node,TreeSet<Integer>> nodeLinks = it.next();
if (nodeLinks.getValue().size() < 2) {
if (nodeLinks.getValue().size() != 1) throw new AssertionError();
Integer d_way = nodeLinks.getValue().iterator().next();
TreeSet<Node> d_way_nodes = waysMap.get(d_way);
d_way_nodes.remove(nodeLinks.getKey());
it.remove();
continue;
}
}
}
private void addPair(Node n, int i) {
TreeSet<Integer> ts = nodesMap.get(n);
if (ts == null) {
ts = new TreeSet<Integer>();
nodesMap.put(n, ts);
}
ts.add(i);
TreeSet<Node> ts2 = waysMap.get(i);
if (ts2 == null) {
ts2 = new TreeSet<Node>();
waysMap.put(i, ts2);
}
ts2.add(n);
}
/**
* Return a relation member that is linked to the
* member 'i', but has not been popped jet.
* Return null if there is no such member left.
*/
public Integer popAdjacent(Integer i) {
TreeSet<Node> nodes = waysMap.get(i);
for (Node n : nodes) {
TreeSet<Integer> adj = nodesMap.get(n);
if (!adj.isEmpty()) {
Integer j = adj.iterator().next();
done(j);
waysMap.get(j).remove(n);
return j;
}
}
return null;
}
/**
* Returns some remaining member or null if
* every sortable member has been processed.
*/
public Integer pop() {
if (remaining.isEmpty()) return null;
Integer i = remaining.iterator().next();
done(i);
return i;
}
/**
* This relation member has been processed.
* Remove references in the nodesMap.
*/
private void done(Integer i) {
remaining.remove(i);
TreeSet<Node> nodes = waysMap.get(i);
for (Node n : nodes) {
boolean result = nodesMap.get(n).remove(i);
if (!result) throw new AssertionError();
}
}
public List<Integer> getNotSortableMembers() {
return notSortable;
}
}