// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.graphview.core.transition; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; 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.access.AccessEvaluator; import org.openstreetmap.josm.plugins.graphview.core.access.AccessParameters; import org.openstreetmap.josm.plugins.graphview.core.access.AccessRuleset; import org.openstreetmap.josm.plugins.graphview.core.access.RulesetAccessEvaluator; import org.openstreetmap.josm.plugins.graphview.core.data.DataSource; import org.openstreetmap.josm.plugins.graphview.core.data.DataSourceObserver; import org.openstreetmap.josm.plugins.graphview.core.data.TagGroup; import org.openstreetmap.josm.plugins.graphview.core.property.RoadPropertyType; /** * generic TransitionStructure implementation using a {@link DataSource} to access OSM data * * @param <N> node type * @param <W> way type * @param <R> relation type */ public class GenericTransitionStructure<N, W, R, M> implements TransitionStructure, DataSourceObserver { private static final Collection<Segment> EMPTY_SEGMENT_LIST = Collections.unmodifiableList(new ArrayList<Segment>(0)); private static final Collection<Restriction> EMPTY_RESTRICTION_COLLECTION = new ArrayList<>(0); private static class SegmentNodeImpl implements SegmentNode { private final double lat; private final double lon; private final List<Segment> inboundSegments = new LinkedList<>(); private final List<Segment> outboundSegments = new LinkedList<>(); private final Map<RoadPropertyType<?>, Object> properties; SegmentNodeImpl(double lat, double lon, Map<RoadPropertyType<?>, Object> properties) { assert properties != null; this.lat = lat; this.lon = lon; this.properties = properties; } @Override public double getLat() { return lat; } @Override public double getLon() { return lon; } public void addInboundSegment(Segment segment) { inboundSegments.add(segment); } public void addOutboundSegment(Segment segment) { outboundSegments.add(segment); } @Override public Collection<Segment> getOutboundSegments() { return outboundSegments; } @Override public Collection<Segment> getInboundSegments() { return inboundSegments; } /*public <P> void setProperty(RoadPropertyType<P> property, P value) { properties.put(property, value); }*/ @Override public Collection<RoadPropertyType<?>> getAvailableProperties() { return properties.keySet(); } @Override public <P> P getPropertyValue(RoadPropertyType<P> property) { @SuppressWarnings("unchecked") //cast is safe due to type parameter of setProperty P result = (P) properties.get(property); return result; } public Map<RoadPropertyType<?>, Object> getProperties() { return properties; } @Override public String toString() { return "(" + lat + ", " + lon + ")"; } } private static class SegmentImpl implements Segment { private final SegmentNode node1; private final SegmentNode node2; private final Map<RoadPropertyType<?>, Object> properties; SegmentImpl(SegmentNode node1, SegmentNode node2, Map<RoadPropertyType<?>, Object> properties) { this.node1 = node1; this.node2 = node2; this.properties = properties; } @Override public SegmentNode getNode1() { return node1; } @Override public SegmentNode getNode2() { return node2; } /*public <P> void setProperty(RoadPropertyType<P> property, P value) { properties.put(property, value); }*/ @Override public Collection<RoadPropertyType<?>> getAvailableProperties() { return properties.keySet(); } @Override public <P> P getPropertyValue(RoadPropertyType<P> property) { @SuppressWarnings("unchecked") //cast is safe due to type parameter of setProperty P result = (P) properties.get(property); return result; } @Override public String toString() { return "(" + node1 + "->" + node2 + ")"; } } private static class RestrictionImpl implements Restriction { private final Segment from; private final Collection<Segment> vias; private final Collection<Segment> tos; /** constructor, will directly use collection references, collections must not be changed after usage as constructor param */ RestrictionImpl(Segment from, Collection<Segment> vias, Collection<Segment> tos) { this.from = from; this.vias = Collections.unmodifiableCollection(vias); this.tos = Collections.unmodifiableCollection(tos); } @Override public Segment getFrom() { return from; } @Override public Collection<Segment> getVias() { return vias; } @Override public Collection<Segment> getTos() { return tos; } @Override public String toString() { return from + " -> " + vias + " -> " + tos; } } private final Set<TransitionStructureObserver> observers = new HashSet<>(); private final Collection<RoadPropertyType<?>> properties; private final DataSource<N, W, R, M> dataSource; private AccessParameters accessParameters; private AccessRuleset ruleset; private AccessEvaluator<N, W> accessEvaluator; private Collection<SegmentNode> nodes = null; private Collection<Segment> segments = new LinkedList<>(); private Collection<Restriction> restrictions = new LinkedList<>(); public GenericTransitionStructure( AccessParameters accessParameters, AccessRuleset ruleset, DataSource<N, W, R, M> dataSource, Collection<RoadPropertyType<?>> properties) { assert accessParameters != null && ruleset != null; assert dataSource != null; assert properties != null; this.dataSource = dataSource; this.properties = properties; setAccessParametersAndRuleset(accessParameters, ruleset); dataSource.addObserver(this); } /** * sets new access parameters and/or a new ruleset. * Causes a data update if at least one is actually changed. * * @param accessParameters new access parameters, null indicates no change * @param ruleset new ruleset, null indicates no change */ public void setAccessParametersAndRuleset(AccessParameters accessParameters, AccessRuleset ruleset) { if (accessParameters != null) { this.accessParameters = accessParameters; } if (ruleset != null) { this.ruleset = ruleset; } if (accessParameters != null || ruleset != null) { assert dataSource != null; accessEvaluator = new RulesetAccessEvaluator<>( dataSource, this.ruleset, this.accessParameters); updateData(); notifyObservers(); } } @Override public Collection<SegmentNode> getNodes() { return nodes; } @Override public Collection<Segment> getSegments() { return segments; } @Override public Collection<Restriction> getRestrictions() { return restrictions; } /** * creates nodes, segments and restrictions based on the data source */ protected void updateData() { ArrayList<SegmentNode> nodes = new ArrayList<>(); ArrayList<Segment> segments = new ArrayList<>(); Map<N, SegmentNodeImpl> nodeCreationMap = new HashMap<>(); Map<W, List<Segment>> waySegmentMap = new HashMap<>(); /* create segments (nodes are created only when included in a segment) */ for (W way : dataSource.getWays()) { createSegmentsAndSegmentNodes(way, accessEvaluator, nodes, segments, nodeCreationMap, waySegmentMap); } nodes.trimToSize(); segments.trimToSize(); /* create restrictions */ Collection<Restriction> restrictions = createRestrictionsFromTurnRestrictions(dataSource.getRelations(), nodeCreationMap, waySegmentMap); restrictions.addAll(createRestrictionsFromBarrierNodes(nodeCreationMap, waySegmentMap)); /* keep data and inform observers */ this.nodes = nodes; this.segments = segments; this.restrictions = restrictions; notifyObservers(); } /** * creates all Segments and SegmentNodes for a way * * @param way way to create Segments and SegmentNodes from; != null * @param wayAccessEvaluator evaluator object that decides whether way is usable; != null * @param nodes collection of SegmentNodes, new SegmentNodes will be added here; != null * @param segments collection of Segments, new Segments will be added here; != null * @param nodeCreationMap map providing the SegmentNode that has been created from a Node, * if new SegmentNodes are created, they will be added appropriately; != null * @param waySegmentMap map providing the Segments that have been created from a Way, * if new Segments are created, they will be added appropriately; != null */ private void createSegmentsAndSegmentNodes(W way, AccessEvaluator<N, W> wayAccessEvaluator, Collection<SegmentNode> nodes, Collection<Segment> segments, Map<N, SegmentNodeImpl> nodeCreationMap, Map<W, List<Segment>> waySegmentMap) { assert way != null && wayAccessEvaluator != null && nodes != null && segments != null && nodeCreationMap != null && waySegmentMap != null; /* calculate property values */ Map<RoadPropertyType<?>, Object> forwardPropertyValues = getWayPropertyMap(way, true); Map<RoadPropertyType<?>, Object> backwardPropertyValues = getWayPropertyMap(way, false); /* create segments from the way if it can be accessed and isn't incomplete or deleted */ boolean forwardAccess = wayAccessEvaluator.wayUsable(way, true, forwardPropertyValues); boolean backwardAccess = wayAccessEvaluator.wayUsable(way, false, backwardPropertyValues); if (forwardAccess || backwardAccess) { if (!waySegmentMap.containsKey(way)) { waySegmentMap.put(way, new LinkedList<Segment>()); } /* create segments from all pairs of subsequent nodes */ N previousNode = null; for (N node : dataSource.getNodes(way)) { if (previousNode != null) { SegmentNodeImpl node1 = getOrCreateSegmentNodeForNode(previousNode, nodes, nodeCreationMap); SegmentNodeImpl node2 = getOrCreateSegmentNodeForNode(node, nodes, nodeCreationMap); if (forwardAccess) { SegmentImpl segment = new SegmentImpl(node1, node2, forwardPropertyValues); segments.add(segment); waySegmentMap.get(way).add(segment); node1.addOutboundSegment(segment); node2.addInboundSegment(segment); } if (backwardAccess) { //no "else if" because both can be valid SegmentImpl segment = new SegmentImpl(node2, node1, backwardPropertyValues); segments.add(segment); waySegmentMap.get(way).add(segment); node1.addInboundSegment(segment); node2.addOutboundSegment(segment); } } previousNode = node; } } } /** * if no segment node for a node exists in the nodeCreationMap, * creates a segment node for it and adds it to the nodeCreationMap and the nodes collection * and returns it; otherwise returns the existing segment node. */ private SegmentNodeImpl getOrCreateSegmentNodeForNode(N node, Collection<SegmentNode> nodes, Map<N, SegmentNodeImpl> nodeCreationMap) { SegmentNodeImpl segmentNode = nodeCreationMap.get(node); if (segmentNode == null) { Map<RoadPropertyType<?>, Object> nodePropertyValues = getNodePropertyMap(node); segmentNode = new SegmentNodeImpl(dataSource.getLat(node), dataSource.getLon(node), nodePropertyValues); nodeCreationMap.put(node, segmentNode); nodes.add(segmentNode); } return segmentNode; } /** * creates all Restrictions from a collection of Relations. * Only "type=restriction" relations are relevant for restrictions. * * @param relations Relations to create Restrictions from. * They can have any type key, as filtering is done inside this method. * @param nodeCreationMap map providing the SegmentNode that has been created from a Node, * will not be modified; != null * @param waySegmentMap map providing the Segments that have been created from a Way, * will not be modified; != null * @return Restrictions created from the Relations; != null, but may be empty */ private Collection<Restriction> createRestrictionsFromTurnRestrictions( Iterable<R> relations, Map<N, SegmentNodeImpl> nodeCreationMap, Map<W, List<Segment>> waySegmentMap) { assert relations != null && nodeCreationMap != null && waySegmentMap != null; Collection<Restriction> results = new LinkedList<>(); for (R relation : relations) { TagGroup tags = dataSource.getTagsR(relation); if ("restriction".equals(tags.getValue("type")) && tags.getValue("restriction") != null) { //evaluate relation if (tags.getValue("restriction").startsWith("no_")) { results.addAll(createRestrictionsFromRestrictionRelation(relation, true, nodeCreationMap, waySegmentMap)); } else if (tags.getValue("restriction").startsWith("only_")) { results.addAll(createRestrictionsFromRestrictionRelation(relation, false, nodeCreationMap, waySegmentMap)); } } } return results; } @SuppressWarnings("unchecked") //several generic casts that are checked with isInstance private Collection<Restriction> createRestrictionsFromRestrictionRelation( R relation, boolean restrictive, Map<N, SegmentNodeImpl> nodeCreationMap, Map<W, List<Segment>> waySegmentMap) { /* collect information about the relation */ W fromWay = null; Collection<N> viaNodes = new LinkedList<>(); Collection<W> viaWays = new LinkedList<>(); Collection<W> toWays = new LinkedList<>(); for (M member : dataSource.getMembers(relation)) { if ("from".equals(dataSource.getRole(member))) { if (fromWay != null || !dataSource.isWMember(member)) { //broken restriction return EMPTY_RESTRICTION_COLLECTION; } else { fromWay = (W) dataSource.getMember(member); } } else if ("to".equals(dataSource.getRole(member))) { if (!dataSource.isWMember(member)) { //broken restriction return EMPTY_RESTRICTION_COLLECTION; } else { toWays.add((W) dataSource.getMember(member)); } } else if ("via".equals(dataSource.getRole(member))) { if (dataSource.isWMember(member)) { viaWays.add((W) dataSource.getMember(member)); } else if (dataSource.isNMember(member)) { viaNodes.add((N) dataSource.getMember(member)); } } } if (fromWay != null && toWays.size() > 0 && (viaNodes.size() > 0 || viaWays.size() > 0)) { return createRestrictionsFromRestrictionRelationMembers( restrictive, nodeCreationMap, waySegmentMap, fromWay, viaNodes, viaWays, toWays); } else { return new ArrayList<>(0); } } private Collection<Restriction> createRestrictionsFromRestrictionRelationMembers( boolean restrictive, Map<N, SegmentNodeImpl> nodeCreationMap, Map<W, List<Segment>> waySegmentMap, W fromWay, Collection<N> viaNodes, Collection<W> viaWays, Collection<W> toWays) { Collection<SegmentNode> nodesCreatedFromViaNodes = new ArrayList<>(viaNodes.size()); for (N viaNode : viaNodes) { if (nodeCreationMap.containsKey(viaNode)) { nodesCreatedFromViaNodes.add(nodeCreationMap.get(viaNode)); } } /* check completeness of restriction to avoid dealing with incomplete restriction info */ if (!waySegmentMap.containsKey(fromWay)) { //broken restriction return EMPTY_RESTRICTION_COLLECTION; } for (W viaWay : viaWays) { if (!waySegmentMap.containsKey(viaWay)) { //broken restriction return EMPTY_RESTRICTION_COLLECTION; } } for (W toWay : toWays) { if (!waySegmentMap.containsKey(toWay)) { //broken restriction return EMPTY_RESTRICTION_COLLECTION; } } /* find all via segments: * via segments are segments created from via ways * or segments starting and ending with nodes created from via nodes */ ArrayList<Segment> viaSegments = new ArrayList<>(); for (W viaWay : viaWays) { viaSegments.addAll(waySegmentMap.get(viaWay)); } for (SegmentNode nodeCreatedFromViaNode : nodesCreatedFromViaNodes) { for (Segment segment : nodeCreatedFromViaNode.getOutboundSegments()) { if (nodesCreatedFromViaNodes.contains(segment.getNode2())) { viaSegments.add(segment); } } } viaSegments.trimToSize(); /* create a set with all nodes that are based on via members */ Set<SegmentNode> nodesCreatedFromViaMembers = new HashSet<>(nodesCreatedFromViaNodes); for (W viaWay : viaWays) { for (N viaWayNode : dataSource.getNodes(viaWay)) { nodesCreatedFromViaMembers.add(nodeCreationMap.get(viaWayNode)); } } /* * find from segment and to segments: * Such a segment contains a node based on a via member. * Each way should contain only one possible segment * connecting to via members (due to splitting). */ Segment fromSegment = null; Collection<Segment> toSegments = new ArrayList<>(); for (Segment possibleFromSegment : waySegmentMap.get(fromWay)) { if (nodesCreatedFromViaMembers.contains(possibleFromSegment.getNode2())) { if (fromSegment == null) { fromSegment = possibleFromSegment; } else { //broken restriction return EMPTY_RESTRICTION_COLLECTION; } } } if (fromSegment == null) { //broken restriction return EMPTY_RESTRICTION_COLLECTION; } if (restrictive) { for (W toWay : toWays) { if (waySegmentMap.containsKey(toWay)) { Segment toSegment = null; for (Segment possibleToSegment : waySegmentMap.get(toWay)) { if (nodesCreatedFromViaMembers.contains(possibleToSegment.getNode1())) { if (toSegment == null) { toSegment = possibleToSegment; } else { //broken restriction return EMPTY_RESTRICTION_COLLECTION; } } } if (toSegment == null) { //broken restriction return EMPTY_RESTRICTION_COLLECTION; } else { toSegments.add(toSegment); } } } } else { //!restrictive /* forbidden "to" segments are all segments that start at a "via" node * and are neither a via segment nor created from an allowed "to" way */ for (SegmentNode toStartingNode : nodesCreatedFromViaMembers) { for (Segment outboundSegment : toStartingNode.getOutboundSegments()) { if (!viaSegments.contains(outboundSegment)) { boolean isAllowed = false; for (W toWay : toWays) { if (waySegmentMap.get(toWay).contains(outboundSegment)) { isAllowed = true; break; } } if (!isAllowed) { toSegments.add(outboundSegment); } } } } } /* create restriction */ Collection<Restriction> results = new ArrayList<>(1); results.add(new RestrictionImpl(fromSegment, viaSegments, toSegments)); return results; } /** * creates Restrictions from barrier nodes (nodes that are considered impassable by the * {@link #accessEvaluator}). These restrictions prevent moving from a segment before the * barrier node to a segment after the barrier node. * * @param nodeCreationMap map providing the SegmentNode that has been created from a node, * will not be modified; != null * @param waySegmentMap map providing the Segments that have been created from a way, * will not be modified; != null * @return Restrictions created from barrier nodes; != null, but may be empty */ private Collection<Restriction> createRestrictionsFromBarrierNodes( Map<N, SegmentNodeImpl> nodeCreationMap, Map<W, List<Segment>> waySegmentMap) { assert nodeCreationMap != null; assert waySegmentMap != null; Collection<Restriction> results = new LinkedList<>(); for (N node : nodeCreationMap.keySet()) { if (!accessEvaluator.nodeUsable(node, nodeCreationMap.get(node).getProperties())) { SegmentNode barrierNode = nodeCreationMap.get(node); for (Segment inboundSegment : barrierNode.getInboundSegments()) { for (Segment outboundSegment : barrierNode.getOutboundSegments()) { results.add(new RestrictionImpl(inboundSegment, EMPTY_SEGMENT_LIST, Arrays.asList(outboundSegment))); } } } } return results; } /** * determines the values of all RoadPropertyTypes from {@link #properties} for a way and * creates a map with the types that have non-null values as keys, property values as content */ private Map<RoadPropertyType<?>, Object> getWayPropertyMap(W way, boolean forward) { Map<RoadPropertyType<?>, Object> propertyValues; propertyValues = new HashMap<>(); for (RoadPropertyType<?> property : properties) { Object value = property.evaluateW(way, forward, accessParameters, dataSource); if (value != null) { propertyValues.put(property, value); } } return propertyValues; } /** * determines the values of all RoadPropertyTypes from {@link #properties} for a node and * creates a map with the types that have non-null values as keys, property values as content */ private Map<RoadPropertyType<?>, Object> getNodePropertyMap(N node) { Map<RoadPropertyType<?>, Object> propertyValues; propertyValues = new HashMap<>(); for (RoadPropertyType<?> property : properties) { Object value = property.evaluateN(node, accessParameters, dataSource); if (value != null) { propertyValues.put(property, value); } } return propertyValues; } @Override public void update(DataSource<?, ?, ?, ?> dataSource) { assert this.dataSource == dataSource; updateData(); } @Override public void addObserver(TransitionStructureObserver observer) { observers.add(observer); } @Override public void deleteObserver(TransitionStructureObserver observer) { observers.remove(observer); } protected void notifyObservers() { for (TransitionStructureObserver observer : observers) { observer.update(this); } } }