// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.turnlanes.model; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.RelationMember; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.plugins.turnlanes.CollectionUtils; import org.openstreetmap.josm.tools.Pair; public final class ModelContainer { private static final ModelContainer EMPTY = new ModelContainer(Collections.<Node>emptySet(), Collections.<Way>emptySet(), false); public static ModelContainer create(Iterable<Node> primaryNodes, Iterable<Way> primaryWays) { return new ModelContainer(new HashSet<>(CollectionUtils.toList(primaryNodes)), new HashSet<>( CollectionUtils.toList(primaryWays)), false); } public static ModelContainer createEmpty(Iterable<Node> primaryNodes, Iterable<Way> primaryWays) { return new ModelContainer(new HashSet<>(CollectionUtils.toList(primaryNodes)), new HashSet<>( CollectionUtils.toList(primaryWays)), true); } public static ModelContainer empty() { return EMPTY; } private static void close(Set<Node> closedNodes, Set<Way> closedWays) { boolean closed = false; while (!closed) { closed = true; for (Node n : new ArrayList<>(closedNodes)) { for (Way w : Utils.filterRoads(n.getReferrers())) { if (w.isFirstLastNode(n)) { closed &= close(closedNodes, closedWays, w); } } for (Way w : new ArrayList<>(closedWays)) { closed &= close(closedNodes, closedWays, w); } } } } private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Way w) { boolean closed = true; for (Relation r : OsmPrimitive.getFilteredList(w.getReferrers(), Relation.class)) { if (!r.get("type").equals(Constants.TYPE_TURNS)) { continue; } for (RelationMember m : r.getMembers()) { if (m.getRole().equals(Constants.TURN_ROLE_VIA) && m.getMember().equals(w)) { closed &= close(closedNodes, closedWays, r); } } } return closed; } private static boolean close(Set<Node> closedNodes, Set<Way> closedWays, Relation r) { boolean closed = true; final List<Way> via = new ArrayList<>(); for (RelationMember m : Utils.getMembers(r, Constants.TURN_ROLE_VIA)) { if (m.isWay()) { closed &= !closedWays.add(m.getWay()); via.add(m.getWay()); } else if (m.isNode()) { closed &= !closedNodes.add(m.getNode()); } } if (!via.isEmpty()) { final Way from = Utils.getMemberWay(r, Constants.TURN_ROLE_FROM); final Way to = Utils.getMemberWay(r, Constants.TURN_ROLE_TO); closed &= !closedNodes.add(Utils.lineUp(from, via.get(0))); closed &= !closedNodes.add(Utils.lineUp(via.get(via.size() - 1), to)); } return closed; } private static <E extends OsmPrimitive, C extends Collection<E>> C filterUsables(C collection) { final Iterator<E> it = collection.iterator(); while (it.hasNext()) { final E e = it.next(); if (e.getDataSet() == null || !e.isUsable()) { it.remove(); } } return collection; } private final Map<Node, Junction> junctions = new HashMap<>(); private final Map<Way, Road> roads = new HashMap<>(); private final Set<Node> primaryNodes; private final Set<Way> primaryWays; private final boolean empty; private ModelContainer(Set<Node> primaryNodes, Set<Way> primaryWays, boolean empty) { if (empty) { this.primaryNodes = Collections.unmodifiableSet(new HashSet<>(primaryNodes)); this.primaryWays = Collections.unmodifiableSet(new HashSet<>(primaryWays)); this.empty = true; } else { final Set<Node> closedNodes = filterUsables(new HashSet<>(primaryNodes)); final Set<Way> closedWays = filterUsables(new HashSet<>(primaryWays)); close(closedNodes, closedWays); this.primaryNodes = Collections.unmodifiableSet(closedNodes); this.primaryWays = Collections.unmodifiableSet(closedWays); for (Pair<Way, Junction> w : createPrimaryJunctions()) { if (!this.primaryWays.contains(w.a)) { addRoad(new Road(this, w.a, w.b)); } } for (Route r : Utils.orderWays(this.primaryWays, this.primaryNodes)) { addRoad(new Road(this, r)); } for (Road r : roads.values()) { r.initialize(); } this.empty = junctions.isEmpty(); } } private Set<Pair<Way, Junction>> createPrimaryJunctions() { final Set<Pair<Way, Junction>> roads = new HashSet<>(); for (Node n : primaryNodes) { final List<Way> ws = new ArrayList<>(); for (Way w : Utils.filterRoads(n.getReferrers())) { if (w.isFirstLastNode(n)) { ws.add(w); } } if (ws.size() > 1) { final Junction j = register(new Junction(this, n)); for (Way w : ws) { roads.add(new Pair<>(w, j)); } } } return roads; } Junction getOrCreateJunction(Node n) { final Junction existing = junctions.get(n); if (existing != null) { return existing; } return register(new Junction(this, n)); } public Junction getJunction(Node n) { Junction j = junctions.get(n); if (j == null) { throw new IllegalArgumentException(); } return j; } Road getRoad(Way w) { final Road r = roads.get(w); if (r == null) { throw new IllegalArgumentException("There is no road containing the given way."); } return r; } private void addRoad(Road newRoad, Road mergedA, Road mergedB) { assert (mergedA == null) == (mergedB == null); for (Route.Segment s : newRoad.getRoute().getSegments()) { final Road oldRoad = roads.put(s.getWay(), newRoad); if (oldRoad != null) { if (mergedA == null) { addRoad(mergeRoads(oldRoad, newRoad), oldRoad, newRoad); } else if (!oldRoad.equals(mergedA) && !oldRoad.equals(mergedB)) { throw new RuntimeException("A road can't be connected to more than two junctions."); } } } } private void addRoad(Road newRoad) { addRoad(newRoad, null, null); } private Road mergeRoads(Road a, Road b) { final String ERR_ILLEGAL_ARGS = "The given roads can not be merged into one."; final List<Way> ws = new ArrayList<>(CollectionUtils.toList(CollectionUtils.reverse(a.getRoute().getWays()))); final List<Way> bws = b.getRoute().getWays(); int i = -1; for (Way w : ws) { if (w.equals(bws.get(i + 1))) { ++i; } else if (i >= 0) { throw new IllegalArgumentException(ERR_ILLEGAL_ARGS); } } if (i < 0) { throw new IllegalArgumentException(ERR_ILLEGAL_ARGS); } ws.addAll(bws.subList(i + 1, bws.size())); final Route mergedRoute = Route.create(ws, a.getRoute().getLastSegment().getEnd()); return new Road(this, mergedRoute); } private Junction register(Junction j) { if (junctions.put(j.getNode(), j) != null) { throw new IllegalStateException(); } return j; } public Set<Junction> getPrimaryJunctions() { if (empty) { return Collections.emptySet(); } final Set<Junction> pjs = new HashSet<>(); for (Node n : primaryNodes) { pjs.add(getJunction(n)); } return pjs; } public Set<Road> getPrimaryRoads() { if (empty) { return Collections.emptySet(); } final Set<Road> prs = new HashSet<>(); for (Way w : primaryWays) { prs.add(roads.get(w)); } return prs; } public ModelContainer recalculate() { return new ModelContainer(primaryNodes, primaryWays, false); } public boolean isPrimary(Junction j) { return primaryNodes.contains(j.getNode()); } public boolean isPrimary(Road r) { return primaryWays.contains(r.getRoute().getFirstSegment().getWay()); } public boolean isEmpty() { return empty; } public boolean hasRoad(Way w) { return roads.containsKey(w); } public boolean hasJunction(Node n) { return junctions.containsKey(n); } }