// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.graphview.core.graph; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.openstreetmap.josm.plugins.graphview.core.property.GraphEdgePropertyType; import org.openstreetmap.josm.plugins.graphview.core.property.GraphEdgeSegments; import org.openstreetmap.josm.plugins.graphview.core.transition.Restriction; import org.openstreetmap.josm.plugins.graphview.core.transition.Segment; import org.openstreetmap.josm.plugins.graphview.core.transition.SegmentNode; import org.openstreetmap.josm.plugins.graphview.core.transition.TransitionStructure; import org.openstreetmap.josm.plugins.graphview.core.transition.TransitionStructureObserver; /** * WayGraph that is based on a {@link TransitionStructure} and updated when it changes. */ public class TSBasedWayGraph implements WayGraph, TransitionStructureObserver { private static final GraphEdgePropertyType<?>[] PROPERTY_TYPES = {GraphEdgeSegments.PROPERTY}; //TODO: -> parameter private static class GraphNodeImpl implements GraphNode { private final SegmentNode node; private final Segment segment; private final List<GraphEdge> incomingEdges = new ArrayList<>(); private final List<GraphEdge> outgoingEdges = new ArrayList<>(); GraphNodeImpl(SegmentNode node, Segment segment) { assert node != null && segment != null; assert segment.getNode1() == node || segment.getNode2() == node; this.node = node; this.segment = segment; } @Override public SegmentNode getSegmentNode() { return node; } @Override public Segment getSegment() { return segment; } public void addIncomingEdge(GraphEdge edge) { assert edge != null; incomingEdges.add(edge); } @Override public Collection<GraphEdge> getInboundEdges() { return incomingEdges; } public void addOutgoingEdge(GraphEdge edge) { assert edge != null; outgoingEdges.add(edge); } @Override public Collection<GraphEdge> getOutboundEdges() { return outgoingEdges; } @Override public String toString() { return "(" + node + "; " + segment + ")"; } } private static class GraphEdgeImpl implements GraphEdge { private final GraphNode startNode; private final GraphNode targetNode; private final Map<GraphEdgePropertyType<?>, Object> properties; GraphEdgeImpl(GraphNode startNode, GraphNode targetNode, Map<GraphEdgePropertyType<?>, Object> properties) { assert startNode != null && targetNode != null && properties != null; this.startNode = startNode; this.targetNode = targetNode; this.properties = properties; } @Override public GraphNode getStartNode() { return startNode; } @Override public GraphNode getTargetNode() { return targetNode; } @Override public Collection<GraphEdgePropertyType<?>> getAvailableProperties() { return properties.keySet(); } @Override public <V> V getPropertyValue(GraphEdgePropertyType<V> property) { @SuppressWarnings("unchecked") V result = (V) properties.get(property); return result; } @Override public String toString() { return "(" + startNode + "-->" + targetNode + ")"; } } private final Set<WayGraphObserver> observers = new HashSet<>(); private final TransitionStructure transitionStructure; private Collection<GraphNode> nodes; private List<GraphEdge> edges; /** * create a WayGraph based on a {@link TransitionStructure} * @param transitionStructure transition structure this graph is to be based on; != null */ public TSBasedWayGraph(TransitionStructure transitionStructure) { assert transitionStructure != null; this.transitionStructure = transitionStructure; transitionStructure.addObserver(this); createNodesAndEdges(); } @Override public Collection<GraphEdge> getEdges() { return edges; } @Override public Collection<GraphNode> getNodes() { return nodes; } private void createNodesAndEdges() { Collection<EvaluationGroup> evaluationGroups = createEvaluationGroups(transitionStructure); for (EvaluationGroup evaluationGroup : evaluationGroups) { evaluationGroup.evaluate(transitionStructure.getRestrictions()); } createNodesAndEdgesFromEvaluationGroups(evaluationGroups); evaluationGroups = null; } private static Collection<EvaluationGroup> createEvaluationGroups( TransitionStructure transitionStructure) { Map<SegmentNode, Set<SegmentNode>> nodeSetMap = new HashMap<>(); /* first step: everything that is part of the same restriction goes into the same set */ for (Restriction restriction : transitionStructure.getRestrictions()) { /* group every node in via segments (which includes the * last node of from and the first node of to) into a set */ SegmentNode firstNode = restriction.getFrom().getNode2(); createSetIfHasNone(firstNode, nodeSetMap); for (Segment segment : restriction.getVias()) { putInSameSet(segment.getNode1(), firstNode, nodeSetMap); putInSameSet(segment.getNode2(), firstNode, nodeSetMap); } for (Segment segment : restriction.getTos()) { putInSameSet(segment.getNode1(), firstNode, nodeSetMap); } } /* second step: create own sets for each junction and end point * (node connected with more than / less than two nodes). */ for (SegmentNode node : transitionStructure.getNodes()) { if (!nodeSetMap.containsKey(node) && !isConnectedWithExactly2Nodes(node)) { createSetIfHasNone(node, nodeSetMap); } } /* third step: create segment sets for all segments that are not in one of the node sets * (that is, at least one node is not part of a junction evaluation group * or the nodes are part of different junction evaluation groups) */ Map<Segment, Set<Segment>> segmentSetMap = new HashMap<>(); for (Segment segment : transitionStructure.getSegments()) { SegmentNode node1 = segment.getNode1(); SegmentNode node2 = segment.getNode2(); if (!nodeSetMap.containsKey(node1) || !nodeSetMap.containsKey(node2) || nodeSetMap.get(node1) != nodeSetMap.get(node2)) { createSetIfHasNone(segment, segmentSetMap); for (Segment subsequentSegment : segment.getNode2().getOutboundSegments()) { if (!nodeSetMap.containsKey(node2) || subsequentSegment.getNode2() == node1) { putInSameSet(subsequentSegment, segment, segmentSetMap); } } //note that segments leading to this segment will share sets anyway, //because this segment is a subsequent segment of them } } /* create EvaluationGroup objects */ Collection<EvaluationGroup> evaluationGroups = new ArrayList<>(nodeSetMap.size() + segmentSetMap.size()); Set<Set<SegmentNode>> nodeSets = new HashSet<>(nodeSetMap.values()); for (Set<SegmentNode> nodeSet : nodeSets) { evaluationGroups.add(new JunctionEvaluationGroup(nodeSet)); } HashSet<Set<Segment>> hashSets = new HashSet<>(segmentSetMap.values()); for (Set<Segment> segmentSet : hashSets) { Set<SegmentNode> borderNodes = new HashSet<>(); for (Segment segment : segmentSet) { if (nodeSetMap.containsKey(segment.getNode1())) { borderNodes.add(segment.getNode1()); } if (nodeSetMap.containsKey(segment.getNode2())) { borderNodes.add(segment.getNode2()); } } evaluationGroups.add(new ConnectorEvaluationGroup(segmentSet, borderNodes)); } return evaluationGroups; } private void createNodesAndEdgesFromEvaluationGroups( Collection<EvaluationGroup> evaluationGroups) { nodes = new LinkedList<>(); edges = new LinkedList<>(); //map from Segments to GraphNodes; //for those GraphNodes representing an "approaching node on segment" state final Map<Segment, GraphNodeImpl> segment2GNMap_approaching = new HashMap<>(); //map from Segments to GraphNodes; //for those GraphNodes representing a "leaving node on segment" state final Map<Segment, GraphNodeImpl> segment2GNMap_leaving = new HashMap<>(); //map from SegmentNodes to GraphNode collections; //for those GraphNodes representing an "approaching node on segment" state final Map<SegmentNode, Collection<GraphNodeImpl>> segNode2GNMap_approaching = new HashMap<>(); //map from SegmentNodes to GraphNodes collections; //for those GraphNodes representing a "leaving node on segment" state final Map<SegmentNode, Collection<GraphNodeImpl>> segNode2GNMap_leaving = new HashMap<>(); /* create graph nodes and edges for junction evaluation groups */ for (EvaluationGroup evaluationGroup : evaluationGroups) { if (evaluationGroup instanceof JunctionEvaluationGroup) { JunctionEvaluationGroup junctionEG = (JunctionEvaluationGroup) evaluationGroup; //create graph nodes for (Segment segment : junctionEG.getInboundSegments()) { GraphNodeImpl graphNode = new GraphNodeImpl(segment.getNode2(), segment); nodes.add(graphNode); segment2GNMap_approaching.put(segment, graphNode); addToCollectionMap(segNode2GNMap_approaching, segment.getNode2(), graphNode); } for (Segment segment : junctionEG.getOutboundSegments()) { GraphNodeImpl graphNode = new GraphNodeImpl(segment.getNode1(), segment); nodes.add(graphNode); segment2GNMap_leaving.put(segment, graphNode); addToCollectionMap(segNode2GNMap_leaving, segment.getNode1(), graphNode); } //create graph edges for all segment sequences between in- and outbound edges for (Segment inboundSegment : junctionEG.getInboundSegments()) { for (Segment outboundSegment : junctionEG.getOutboundSegments()) { List<Segment> segmentSequence = junctionEG.getSegmentSequence(inboundSegment, outboundSegment); if (segmentSequence != null) { createGraphEdge( segment2GNMap_approaching.get(inboundSegment), segment2GNMap_leaving.get(outboundSegment), segmentSequence, junctionEG); } } } } } /* create graph edges for connector evaluation groups. * Because GraphNodes are created for pairs of SegmentNodes (from connector groups) * and Segments (from junction groups), the GraphNodes already exist. */ for (EvaluationGroup evaluationGroup : evaluationGroups) { if (evaluationGroup instanceof ConnectorEvaluationGroup) { ConnectorEvaluationGroup connectorEG = (ConnectorEvaluationGroup) evaluationGroup; for (SegmentNode startNode : connectorEG.getBorderNodes()) { for (SegmentNode targetNode : connectorEG.getBorderNodes()) { if (segNode2GNMap_leaving.containsKey(startNode) && segNode2GNMap_approaching.containsKey(targetNode)) { for (GraphNodeImpl startGraphNode : segNode2GNMap_leaving.get(startNode)) { for (GraphNodeImpl targetGraphNode : segNode2GNMap_approaching.get(targetNode)) { if (connectorEG.getSegments().contains(startGraphNode.getSegment()) && connectorEG.getSegments().contains(targetGraphNode.getSegment())) { List<Segment> segmentSequence = connectorEG.getSegmentSequence(startNode, targetNode); if (segmentSequence != null) { createGraphEdge( startGraphNode, targetGraphNode, segmentSequence, connectorEG); } } } } } } } } } } private void createGraphEdge( GraphNodeImpl startNode, GraphNodeImpl targetNode, List<Segment> segments, ConnectorEvaluationGroup evaluationGroup) { Map<GraphEdgePropertyType<?>, Object> properties = new HashMap<>(); //TODO: replace HashMap with List-based solution for (GraphEdgePropertyType<?> propertyType : PROPERTY_TYPES) { Object value = propertyType.evaluate(evaluationGroup, segments, transitionStructure); properties.put(propertyType, value); } createGraphEdge(startNode, targetNode, properties); } private void createGraphEdge( GraphNodeImpl startNode, GraphNodeImpl targetNode, List<Segment> segments, JunctionEvaluationGroup evaluationGroup) { Map<GraphEdgePropertyType<?>, Object> properties = new HashMap<>(); //TODO: replace HashMap with List-based solution for (GraphEdgePropertyType<?> propertyType : PROPERTY_TYPES) { Object value = propertyType.evaluate(evaluationGroup, segments, transitionStructure); properties.put(propertyType, value); } createGraphEdge(startNode, targetNode, properties); } /** * creates a GraphEdge; * adds it to its nodes' collections and {@link #edges} collection. */ private void createGraphEdge(GraphNodeImpl startNode, GraphNodeImpl targetNode, Map<GraphEdgePropertyType<?>, Object> properties) { GraphEdge newEdge = new GraphEdgeImpl(startNode, targetNode, properties); startNode.addOutgoingEdge(newEdge); targetNode.addIncomingEdge(newEdge); edges.add(newEdge); } private static boolean isConnectedWithExactly2Nodes(SegmentNode node) { Set<SegmentNode> connectedNodes = new HashSet<>(2); for (Segment segment : node.getInboundSegments()) { connectedNodes.add(segment.getNode1()); } for (Segment segment : node.getOutboundSegments()) { connectedNodes.add(segment.getNode2()); } return connectedNodes.size() == 2; } /** * creates a set for an object if none exists in a map. * The set will contain the object and be added to the map with the object being its key. */ private static <T> void createSetIfHasNone(T object, Map<T, Set<T>> objectSetMap) { if (!objectSetMap.containsKey(object)) { Set<T> set = new HashSet<>(); set.add(object); objectSetMap.put(object, set); } } /** * puts an object in another object's set. * If both nodes have sets already, these sets are merged. * The objectSetMap is modified accordingly. * * @param object object that might or might not be in a set; != null * @param objectInSet object that is guaranteed to be in a set; != null * @param objectSetMap map from objects to the one set they are part of; != null */ private static <T> void putInSameSet(T object, T objectInSet, Map<T, Set<T>> objectSetMap) { assert object != null && objectInSet != null && objectSetMap != null; assert objectSetMap.containsKey(objectInSet); Set<T> set = objectSetMap.get(objectInSet); if (objectSetMap.containsKey(object)) { /* merge the two sets */ Set<T> oldSet = objectSetMap.get(object); for (T objectFromOldSet : oldSet) { set.add(objectFromOldSet); objectSetMap.put(objectFromOldSet, set); } } else { /* add object to objectInSet's set */ set.add(object); objectSetMap.put(object, set); } } private static <K, E> void addToCollectionMap(final Map<K, Collection<E>> map, K key, E entry) { if (!map.containsKey(key)) { Collection<E> newCollection = new ArrayList<>(); map.put(key, newCollection); } map.get(key).add(entry); } @Override public void update(TransitionStructure transitionStructure) { createNodesAndEdges(); notifyObservers(); } @Override public void addObserver(WayGraphObserver observer) { observers.add(observer); } @Override public void deleteObserver(WayGraphObserver observer) { observers.remove(observer); } private void notifyObservers() { for (WayGraphObserver observer : observers) { observer.update(this); } } }