diff --git a/src/main/java/com/conveyal/r5/point_to_point/PointToPointRouterServer.java b/src/main/java/com/conveyal/r5/point_to_point/PointToPointRouterServer.java index 34a39320c..8f641ce65 100644 --- a/src/main/java/com/conveyal/r5/point_to_point/PointToPointRouterServer.java +++ b/src/main/java/com/conveyal/r5/point_to_point/PointToPointRouterServer.java @@ -96,6 +96,22 @@ public static void main(String[] commandArguments) { LOG.info("Loading transit networks from: {}", dir); TransportNetwork transportNetwork = TransportNetwork.read(new File(dir, "network.dat")); transportNetwork.readOSM(new File(dir, "osm.mapdb")); + int invalidTrs = 0; + for (int i=0; i < transportNetwork.streetLayer.turnRestrictions.size(); i++) { + TurnRestriction tr = transportNetwork.streetLayer.turnRestrictions.get(i); + if (tr.viaEdges.length > 0) { + continue; + } + EdgeStore.Edge fromEdge = transportNetwork.streetLayer.edgeStore.getCursor(tr.fromEdge); + EdgeStore.Edge toEdge = transportNetwork.streetLayer.edgeStore.getCursor(tr.toEdge); + + int viaVertex = fromEdge.getToVertex(); + if(!transportNetwork.streetLayer.outgoingEdges.get(viaVertex).contains(tr.toEdge)) { + invalidTrs++; + LOG.info("TR:{} -> {}, {}", fromEdge.getOSMID(), toEdge.getOSMID(), tr); + } + } + LOG.info("Invalid trs:{}", invalidTrs); run(transportNetwork); } catch (Exception e) { LOG.error("An error occurred during the reading or decoding of transit networks", e); @@ -723,7 +739,6 @@ private static void run(TransportNetwork transportNetwork) { edgeIdx++; makeTurnEdge(transportNetwork, both, features, cursor, offsetBuilder, distance, edgeIdx); - return true; } catch (Exception e) { response.status(500); @@ -1068,6 +1083,15 @@ private static void makeTurnEdge(TransportNetwork transportNetwork, boolean both for (int i=0; i < edge_restricion_idxs.size(); i++) { int turnRestrictionIdx = edge_restricion_idxs.get(i); TurnRestriction turnRestriction = transportNetwork.streetLayer.turnRestrictions.get(turnRestrictionIdx); + if (turnRestriction.viaEdges.length > 0) { + continue; + } + EdgeStore.Edge fromEdge = transportNetwork.streetLayer.edgeStore.getCursor(turnRestriction.fromEdge); + + int viaVertex = fromEdge.getToVertex(); + if(transportNetwork.streetLayer.outgoingEdges.get(viaVertex).contains(turnRestriction.toEdge)) { + continue; + } //TurnRestriction.fromEdge isn't necessary correct //If edge on which from is is splitted then fromEdge is different but isn't updated in TurnRestriction diff --git a/src/main/java/com/conveyal/r5/point_to_point/builder/TNBuilderConfig.java b/src/main/java/com/conveyal/r5/point_to_point/builder/TNBuilderConfig.java index db47852be..9abc5f3ac 100644 --- a/src/main/java/com/conveyal/r5/point_to_point/builder/TNBuilderConfig.java +++ b/src/main/java/com/conveyal/r5/point_to_point/builder/TNBuilderConfig.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.google.common.annotations.VisibleForTesting; import java.io.File; @@ -84,6 +85,11 @@ public class TNBuilderConfig { */ public final boolean fetchElevationUS; + /** + * If non accessible islands should be removed (default true) + */ + public final boolean removeIslands; + /** If specified, download NED elevation tiles from the given AWS S3 bucket. */ //public final S3BucketConfig elevationBucket; @@ -144,6 +150,30 @@ public TNBuilderConfig() { bikeRentalFile = null; speeds = SpeedConfig.defaultConfig(); analysisFareCalculator = null; + removeIslands = true; + } + + @VisibleForTesting + public TNBuilderConfig(boolean removeIslands) { + htmlAnnotations = false; + maxHtmlAnnotationsPerFile = 1000; + transit = true; + useTransfersTxt = false; + parentStopLinking = false; + stationTransfers = false; + subwayAccessTime = DEFAULT_SUBWAY_ACCESS_TIME; + streets = true; + embedRouterConfig = true; + areaVisibility = false; + matchBusRoutesToStreets = false; + fetchElevationUS = false; + staticBikeRental = false; + staticParkAndRide = true; + staticBikeParkAndRide = false; + bikeRentalFile = null; + speeds = SpeedConfig.defaultConfig(); + analysisFareCalculator = null; + this.removeIslands = removeIslands; } public static TNBuilderConfig defaultConfig() { diff --git a/src/main/java/com/conveyal/r5/streets/EdgeStore.java b/src/main/java/com/conveyal/r5/streets/EdgeStore.java index 22feb6191..0f45d7108 100644 --- a/src/main/java/com/conveyal/r5/streets/EdgeStore.java +++ b/src/main/java/com/conveyal/r5/streets/EdgeStore.java @@ -139,12 +139,39 @@ public boolean isExtendOnlyCopy() { } - /** Turn restrictions for turning _out of_ each edge */ + /** Turn restrictions for turning _out of_ each edge + * + * FROM edges of all turn restrictions + * */ public TIntIntMultimap turnRestrictions; - /** Turn restrictions for turning _in of_ each edge */ + /** Turn restrictions for turning _in of_ each edge in reverse search + * + * TO edges of NO TURN restrictions + * and TO edges of NO TURN restrictions remapped from ONLY TURN restrictions + * + * Also used when splitting edges + * */ public TIntIntMultimap turnRestrictionsReverse; + /** + * TO edges of ONLY TURN restrictions. (They aren't in turnRestrictionsReverse since reverse search + * doesn't support ONLY TURNS) + * Used only when splitting edges + * */ + public TIntIntMultimap turnRestrictionsToEdges; + + /** + * Here are FROM edges of NO TURN restriction which are remapped ONLY turn restrictions to NO turn restrictions + * Used only when splitting edges + */ + public TIntIntMultimap turnRestrictionsFromEdges; + + /** Via edges to turn restrictions link. Used only when splitting edges + * FIXME: Can be probably transient since it's used only when building graph in getOrCreateVertexNear + * But I'm not sure what is with scenarios (materializeOne function)*/ + public TIntIntMultimap turnRestrictionsVia; + public StreetLayer layer; public static final transient EnumSet PERMISSION_FLAGS = EnumSet @@ -170,6 +197,9 @@ public EdgeStore (VertexStore vertexStore, StreetLayer layer, int initialSize) { outAngles = new TByteArrayList(initialEdgePairs); turnRestrictions = new TIntIntHashMultimap(); turnRestrictionsReverse = new TIntIntHashMultimap(); + turnRestrictionsToEdges = new TIntIntHashMultimap(); + turnRestrictionsFromEdges = new TIntIntHashMultimap(); + turnRestrictionsVia = new TIntIntHashMultimap(); } /** @@ -1065,8 +1095,15 @@ public EdgeStore extendOnlyCopy(StreetLayer copiedStreetLayer) { copy.inAngles = new TByteArrayList(inAngles); copy.outAngles = new TByteArrayList(outAngles); // We don't expect to add/change any turn restrictions. - copy.turnRestrictions = turnRestrictions; - copy.turnRestrictionsReverse = turnRestrictionsReverse; + copy.turnRestrictions = new TIntIntHashMultimap((TIntIntHashMultimap) turnRestrictions); + copy.turnRestrictionsReverse = new TIntIntHashMultimap( + (TIntIntHashMultimap) turnRestrictionsReverse); + copy.turnRestrictionsVia = new TIntIntHashMultimap( + (TIntIntHashMultimap) turnRestrictionsVia); + copy.turnRestrictionsToEdges = new TIntIntHashMultimap( + (TIntIntHashMultimap) turnRestrictionsToEdges); + copy.turnRestrictionsFromEdges = new TIntIntHashMultimap( + (TIntIntHashMultimap) turnRestrictionsFromEdges); return copy; } diff --git a/src/main/java/com/conveyal/r5/streets/StreetLayer.java b/src/main/java/com/conveyal/r5/streets/StreetLayer.java index 7b231b511..81e5f1c7e 100644 --- a/src/main/java/com/conveyal/r5/streets/StreetLayer.java +++ b/src/main/java/com/conveyal/r5/streets/StreetLayer.java @@ -29,6 +29,8 @@ import gnu.trove.map.hash.TIntObjectHashMap; import gnu.trove.map.hash.TLongIntHashMap; import gnu.trove.set.TIntSet; +import gnu.trove.set.hash.TIntHashSet; +import org.apache.commons.lang.ArrayUtils; import org.geotools.geojson.geom.GeometryJSON; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -235,7 +237,7 @@ private static boolean actuallyExistsInReality(String highway, Way way) { /** Load OSM, optionally removing floating subgraphs (recommended) */ - void loadFromOsm (OSM osm, boolean removeIslands, boolean saveVertexIndex) { + public void loadFromOsm(OSM osm, boolean removeIslands, boolean saveVertexIndex) { if (!osm.intersectionDetection) throw new IllegalArgumentException("Intersection detection not enabled on OSM source"); @@ -697,6 +699,10 @@ else if ("via".equals(member.role)) { turnRestrictions.add(out); edgeStore.turnRestrictions.put(out.fromEdge, index); addReverseTurnRestriction(out, index); + //TODO: add remapped via edges + for(int edgeId: out.viaEdges) { + edgeStore.turnRestrictionsVia.put(edgeId, index); + } } else { // via member(s) are ways, which is more tricky // do a little street search constrained to the ways in question @@ -888,6 +894,11 @@ else if ("via".equals(member.role)) { turnRestrictions.add(out); edgeStore.turnRestrictions.put(out.fromEdge, index); addReverseTurnRestriction(out, index); + //TODO: add remapped via edges + for(int edgeId: out.viaEdges) { + edgeStore.turnRestrictionsVia.put(edgeId, index); + } + // take a deep breath } @@ -908,13 +919,19 @@ else if ("via".equals(member.role)) { */ void addReverseTurnRestriction(TurnRestriction turnRestriction, int index) { if (turnRestriction.only) { + //Adds To edges of ONLY turn restriction to map of too edges. This is used in splitting + edgeStore.turnRestrictionsToEdges.put(turnRestriction.toEdge, index); //From Only turn restrictions create multiple NO TURN restrictions which means the same //Since only turn restrictions aren't supported in reverse street search List remapped = turnRestriction.remap(this); for (TurnRestriction remapped_restriction: remapped) { index = turnRestrictions.size(); turnRestrictions.add(remapped_restriction); + for(int edgeId: turnRestriction.viaEdges) { + edgeStore.turnRestrictionsVia.put(edgeId, index); + } edgeStore.turnRestrictionsReverse.put(remapped_restriction.toEdge, index); + edgeStore.turnRestrictionsFromEdges.put(remapped_restriction.fromEdge, index); } } else { edgeStore.turnRestrictionsReverse.put(turnRestriction.toEdge, index); @@ -1193,6 +1210,155 @@ public int getOrCreateVertexNear(double lat, double lon, StreetMode streetMode) EdgeStore.Edge newEdge1 = edgeStore.addStreetPair(newVertexIndex, oldToVertex, split.distance1_mm, edge.getOSMID()); // Copy the flags and speeds for both directions, making newEdge1 like the existing edge. newEdge1.copyPairFlagsAndSpeeds(edge); + + // clean up any turn restrictions that exist + // turn restrictions on the forward edge go to the new edge's forward edge. Turn restrictions on the back edge stay + // where they are + edgeStore.turnRestrictions.removeAll(split.edge).forEach(ridx -> { + TurnRestriction tr = turnRestrictions.get(ridx); + TurnRestriction newTurnRestriction; + // If edge is mutable we are splitting normal edge and can modify existing turnRestriction with new fromEdge + if (edge.isMutable()) { + newTurnRestriction = tr; + } else { + //If it isn't we are in scenario. Which means we need to replace current turnRestriction with new one + //Since TurnRestriction in original graph needs to stay the same + newTurnRestriction = new TurnRestriction(tr); + } + newTurnRestriction.fromEdge = newEdge1.edgeIndex; + + //In scenario we replace existing turnRestriction with new one + if (!edge.isMutable()) { + turnRestrictions.remove(ridx); + turnRestrictions.add(ridx, newTurnRestriction); + } + edgeStore.turnRestrictions.put(newEdge1.edgeIndex, ridx); + return true; + }); + + + edgeStore.turnRestrictionsFromEdges.removeAll(split.edge).forEach(ridx -> { + TurnRestriction tr = turnRestrictions.get(ridx); + TurnRestriction newTurnRestriction; + // If edge is mutable we are splitting normal edge and can modify existing turnRestriction with new fromEdge + if (edge.isMutable()) { + newTurnRestriction = tr; + } else { + //If it isn't we are in scenario. Which means we need to replace current turnRestriction with new one + //Since TurnRestriction in original graph needs to stay the same + newTurnRestriction = new TurnRestriction(tr); + } + newTurnRestriction.fromEdge = newEdge1.edgeIndex; + + //In scenario we replace existing turnRestriction with new one + if (!edge.isMutable()) { + turnRestrictions.remove(ridx); + turnRestrictions.add(ridx, newTurnRestriction); + } + edgeStore.turnRestrictionsFromEdges.put(newEdge1.edgeIndex, ridx); + return true; + }); + + // clean up any turn restrictions that exist on this edge as to edge + // turn restrictions on the backward edge go to the new edge's backward edge. Turn restrictions on the forward edge stay + // where they are + edgeStore.turnRestrictionsReverse.removeAll(split.edge+1).forEach(ridx -> { + TurnRestriction tr = turnRestrictions.get(ridx); + TurnRestriction newTurnRestriction; + // If edge is mutable we are splitting normal edge and can modify existing turnRestriction with new toEdge + if (edge.isMutable()) { + newTurnRestriction = tr; + } else { + //If it isn't we are in scenario. Which means we need to replace current turnRestriction with new one + //Since TurnRestriction in original graph needs to stay the same + newTurnRestriction = new TurnRestriction(tr); + } + //+1 since edgeIndex is forward edge but we need backward edge + newTurnRestriction.toEdge = newEdge1.edgeIndex+1; + + //In scenario we replace existing turnRestriction with new one + if (!edge.isMutable()) { + turnRestrictions.remove(ridx); + turnRestrictions.add(ridx, newTurnRestriction); + } + edgeStore.turnRestrictionsReverse.put(newEdge1.edgeIndex+1, ridx); + return true; + }); + + + // clean up any turn restrictions that exist on this edge as to edge + // turn restrictions on the backward edge go to the new edge's backward edge. Turn restrictions on the forward edge stay + // where they are + edgeStore.turnRestrictionsToEdges.removeAll(split.edge+1).forEach(ridx -> { + TurnRestriction tr = turnRestrictions.get(ridx); + TurnRestriction newTurnRestriction; + // If edge is mutable we are splitting normal edge and can modify existing turnRestriction with new toEdge + if (edge.isMutable()) { + newTurnRestriction = tr; + } else { + //If it isn't we are in scenario. Which means we need to replace current turnRestriction with new one + //Since TurnRestriction in original graph needs to stay the same + newTurnRestriction = new TurnRestriction(tr); + } + //+1 since edgeIndex is forward edge but we need backward edge + newTurnRestriction.toEdge = newEdge1.edgeIndex+1; + + //In scenario we replace existing turnRestriction with new one + if (!edge.isMutable()) { + turnRestrictions.remove(ridx); + turnRestrictions.add(ridx, newTurnRestriction); + } + edgeStore.turnRestrictionsToEdges.put(newEdge1.edgeIndex+1, ridx); + return true; + }); + + //Turn restrictions that are on via edge should be updated. Since we now have 2 via edges from one, because we split one + edgeStore.turnRestrictionsVia.get(split.edge).forEach(turnRestrictionIndex -> { + TurnRestriction tr = turnRestrictions.get(turnRestrictionIndex); + TurnRestriction newTurnRestriction; + int currentViaEdgeIndex = ArrayUtils.indexOf(tr.viaEdges, split.edge); + //New via edge index is added as the next edge in viaEdges list + //noinspection UnnecessaryLocalVariable + int[] newViaEdges = ArrayUtils.add(tr.viaEdges, currentViaEdgeIndex+1, newEdge1.edgeIndex); + if (edge.isMutable()) { + newTurnRestriction = tr; + } else { + newTurnRestriction = new TurnRestriction(tr); + } + newTurnRestriction.viaEdges = newViaEdges; + if (!edge.isMutable()) { + turnRestrictions.remove(turnRestrictionIndex); + turnRestrictions.add(turnRestrictionIndex, newTurnRestriction); + } + //Also updates turnRestrictionsViaList + edgeStore.turnRestrictionsVia.put(newEdge1.edgeIndex, turnRestrictionIndex); + return true; + }); + + //Turn restrictions that are on via backward edge should be updated. Since we now have 2 via edges from one, because we split one + edgeStore.turnRestrictionsVia.get(split.edge+1).forEach(turnRestrictionIndex -> { + TurnRestriction tr = turnRestrictions.get(turnRestrictionIndex); + TurnRestriction newTurnRestriction; + int currentViaEdgeIndex = ArrayUtils.indexOf(tr.viaEdges, split.edge+1); + //New via edge index is added as the prev edge in viaEdges list + //noinspection UnnecessaryLocalVariable + int[] newViaEdges = ArrayUtils.add(tr.viaEdges, currentViaEdgeIndex, newEdge1.edgeIndex+1); + if (edge.isMutable()) { + newTurnRestriction = tr; + } else { + newTurnRestriction = new TurnRestriction(tr); + } + newTurnRestriction.viaEdges = newViaEdges; + if (!edge.isMutable()) { + turnRestrictions.remove(turnRestrictionIndex); + turnRestrictions.add(turnRestrictionIndex, newTurnRestriction); + } + //Also updates turnRestrictionsViaList + edgeStore.turnRestrictionsVia.put(newEdge1.edgeIndex+1, turnRestrictionIndex); + return true; + }); + + // Insert the new edge into the spatial index if (!edgeStore.isExtendOnlyCopy()) { spatialIndex.insert(newEdge1.getEnvelope(), newEdge1.edgeIndex); @@ -1247,6 +1413,22 @@ public int splitEdge(Split split) { // where they are edgeStore.turnRestrictions.removeAll(split.edge).forEach(ridx -> edgeStore.turnRestrictions.put(newEdge.edgeIndex, ridx)); + //Turn restrictions that are on via edge should be updated. Since we now have 2 via edges from one, because we split one + if (edgeStore.turnRestrictionsVia.containsKey(split.edge)) { + + edgeStore.turnRestrictionsVia.get(split.edge).forEach(turnRestrictionIndex -> { + TurnRestriction tr = turnRestrictions.get(turnRestrictionIndex); + int currentViaEdgeIndex = ArrayUtils.indexOf(tr.viaEdges, split.edge); + //New via edge index is added as the next edge in viaEdges list + //noinspection UnnecessaryLocalVariable + int[] newViaEdges = ArrayUtils.add(tr.viaEdges, currentViaEdgeIndex+1, newEdge.edgeIndex); + tr.viaEdges = newViaEdges; + //Also updates turnRestrictionsViaList + edgeStore.turnRestrictionsVia.put(newEdge.edgeIndex, turnRestrictionIndex); + return true; + }); + } + return newVertexIndex; // TODO store street-to-stop distance in a table in TransitLayer. This also allows adjusting for subway entrances etc. } @@ -1360,6 +1542,11 @@ public StreetLayer scenarioCopy(TransportNetwork newScenarioNetwork, boolean wil // The extend-only copy of the EdgeStore also contains a new extend-only copy of the VertexStore. copy.vertexStore = copy.edgeStore.vertexStore; copy.temporaryEdgeIndex = new IntHashGrid(); + // List of turn restrictions needs to be copied because if we split streets + // (when adding transit stops for example) that are part of turn restrictions we need to + // update TurnRestriction from/to/via edges + copy.turnRestrictions = new ArrayList<>(turnRestrictions.size()); + copy.turnRestrictions.addAll(turnRestrictions); } copy.parentNetwork = newScenarioNetwork; copy.baseStreetLayer = this; diff --git a/src/main/java/com/conveyal/r5/streets/TurnRestriction.java b/src/main/java/com/conveyal/r5/streets/TurnRestriction.java index 80a553df3..171277ed8 100644 --- a/src/main/java/com/conveyal/r5/streets/TurnRestriction.java +++ b/src/main/java/com/conveyal/r5/streets/TurnRestriction.java @@ -30,6 +30,20 @@ public class TurnRestriction implements Serializable { /** the intermediate edges in this turn restriction */ public int[] viaEdges = EMPTY_INT_ARRAY; + // Copy constructor, used in scenarios, since street splitting can change turn restriction edges + public TurnRestriction(TurnRestriction tr) { + this.only = tr.only; + this.fromEdge = tr.fromEdge; + this.toEdge = tr.toEdge; + if (tr.viaEdges != EMPTY_INT_ARRAY) { + this.viaEdges = Arrays.copyOf(tr.viaEdges, tr.viaEdges.length); + } + } + + public TurnRestriction() { + + } + /** * Reverses order of viaEdges this is used in reverse streetSearch for turn restrictions * diff --git a/src/main/java/com/conveyal/r5/transit/TransportNetwork.java b/src/main/java/com/conveyal/r5/transit/TransportNetwork.java index c9d4445bf..df5ee12e3 100644 --- a/src/main/java/com/conveyal/r5/transit/TransportNetwork.java +++ b/src/main/java/com/conveyal/r5/transit/TransportNetwork.java @@ -136,12 +136,12 @@ public void rebuildTransientIndexes() { /** Create a TransportNetwork from gtfs-lib feeds */ public static TransportNetwork fromFeeds (String osmSourceFile, List feeds, TNBuilderConfig config) { - return fromFiles(osmSourceFile, null, feeds, config); + return fromFiles(osmSourceFile, null, feeds, config, false); } /** Legacy method to load from a single GTFS file */ public static TransportNetwork fromFiles (String osmSourceFile, String gtfsSourceFile, TNBuilderConfig tnBuilderConfig) throws DuplicateFeedException { - return fromFiles(osmSourceFile, Arrays.asList(gtfsSourceFile), tnBuilderConfig); + return fromFiles(osmSourceFile, Arrays.asList(gtfsSourceFile), tnBuilderConfig, false); } /** @@ -150,8 +150,8 @@ public static TransportNetwork fromFiles (String osmSourceFile, String gtfsSourc * the feeds into memory simulataneously, which shouldn't be so bad with mapdb-based feeds, but it's still not great * (due to caching etc.) */ - private static TransportNetwork fromFiles (String osmSourceFile, List gtfsSourceFiles, List feeds, - TNBuilderConfig tnBuilderConfig) throws DuplicateFeedException { + private static TransportNetwork fromFiles(String osmSourceFile, List gtfsSourceFiles, + List feeds, TNBuilderConfig tnBuilderConfig, boolean temporary) throws DuplicateFeedException { System.out.println("Summarizing builder config: " + BUILDER_CONFIG_FILENAME); System.out.println(tnBuilderConfig); @@ -160,8 +160,9 @@ private static TransportNetwork fromFiles (String osmSourceFile, List gt // Create a transport network to hold the street and transit layers TransportNetwork transportNetwork = new TransportNetwork(); + String osmPath = temporary ? null : new File(dir,"osm.mapdb").getPath(); // Load OSM data into MapDB - OSM osm = new OSM(new File(dir,"osm.mapdb").getPath()); + OSM osm = new OSM(osmPath); osm.intersectionDetection = true; osm.readFromFile(osmSourceFile); @@ -169,7 +170,7 @@ private static TransportNetwork fromFiles (String osmSourceFile, List gt StreetLayer streetLayer = new StreetLayer(tnBuilderConfig); transportNetwork.streetLayer = streetLayer; streetLayer.parentNetwork = transportNetwork; - streetLayer.loadFromOsm(osm); + streetLayer.loadFromOsm(osm, tnBuilderConfig.removeIslands, false); osm.close(); // The street index is needed for associating transit stops with the street network @@ -224,9 +225,12 @@ private static TransportNetwork fromFiles (String osmSourceFile, List gt * On the other hand, GTFS feeds each have their own namespace. Each GTFS object is for one specific feed, and this * distinction should be maintained for various reasons. However, we use the GTFS IDs only for reference, so it * doesn't really matter, particularly for analytics. + * + * @param temporary: if true osm.mapdb is deleted after use. Default is false since it is needed for way names */ - public static TransportNetwork fromFiles (String osmFile, List gtfsFiles, TNBuilderConfig config) { - return fromFiles(osmFile, gtfsFiles, null, config); + public static TransportNetwork fromFiles(String osmFile, List gtfsFiles, + TNBuilderConfig config, boolean temporary) { + return fromFiles(osmFile, gtfsFiles, null, config, temporary); } public static TransportNetwork fromDirectory (File directory) throws DuplicateFeedException { @@ -260,7 +264,7 @@ public static TransportNetwork fromDirectory (File directory) throws DuplicateFe LOG.error("An OSM PBF file is required to build a network."); return null; } else { - return fromFiles(osmFile.getAbsolutePath(), gtfsFiles, builderConfig); + return fromFiles(osmFile.getAbsolutePath(), gtfsFiles, builderConfig, false); } } diff --git a/src/main/java/com/conveyal/r5/util/TIntIntHashMultimap.java b/src/main/java/com/conveyal/r5/util/TIntIntHashMultimap.java index 212158d3c..afe470f6f 100644 --- a/src/main/java/com/conveyal/r5/util/TIntIntHashMultimap.java +++ b/src/main/java/com/conveyal/r5/util/TIntIntHashMultimap.java @@ -20,6 +20,15 @@ public class TIntIntHashMultimap implements TIntIntMultimap, Serializable { private TIntObjectMap wrapped = new TIntObjectHashMap<>(); + //Copy constructor + public TIntIntHashMultimap (TIntIntHashMultimap base) { + wrapped.putAll(base.wrapped); + } + + public TIntIntHashMultimap() { + + } + @Override public boolean put(int key, int value) { if (!wrapped.containsKey(key)) wrapped.put(key, new TIntArrayList()); diff --git a/src/test/java/com/conveyal/r5/analyst/scenario/FakeGraph.java b/src/test/java/com/conveyal/r5/analyst/scenario/FakeGraph.java index 50452cfa5..e1768c23b 100644 --- a/src/test/java/com/conveyal/r5/analyst/scenario/FakeGraph.java +++ b/src/test/java/com/conveyal/r5/analyst/scenario/FakeGraph.java @@ -64,7 +64,45 @@ public static TransportNetwork buildNetwork (TransitNetwork... networks) { gtfsFiles.add(gtfsFile.getAbsolutePath()); } - TransportNetwork net = TransportNetwork.fromFiles(osmFile.getAbsolutePath(), gtfsFiles, new TNBuilderConfig()); + TransportNetwork net = TransportNetwork.fromFiles(osmFile.getAbsolutePath(), gtfsFiles, new TNBuilderConfig(), + true); + net.transitLayer.buildDistanceTables(null); + + // clean up + filesToDelete.forEach(f -> f.delete()); + + return net; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** Build a graph from provided OSM using requested transit feeds */ + public static TransportNetwork buildNetwork (InputStream osmFileStream, String osmFilename, + TNBuilderConfig tnBuilderConfig, TransitNetwork... networks) { + try { + File osmFile = File.createTempFile(osmFilename, ".osm.pbf"); + + InputStream is = new BufferedInputStream(osmFileStream); + OutputStream os = new BufferedOutputStream(new FileOutputStream(osmFile)); + ByteStreams.copy(is, os); + is.close(); + os.close(); + + List filesToDelete = new ArrayList<>(); + filesToDelete.add(osmFile); + + List gtfsFiles = new ArrayList<>(); + + for (TransitNetwork network : networks) { + File gtfsFile = File.createTempFile(network.toString(), ".gtfs.zip"); + network.get().toFile(gtfsFile.getAbsolutePath()); + filesToDelete.add(gtfsFile); + gtfsFiles.add(gtfsFile.getAbsolutePath()); + } + + TransportNetwork net = TransportNetwork.fromFiles(osmFile.getAbsolutePath(), gtfsFiles, tnBuilderConfig, + true); net.transitLayer.buildDistanceTables(null); // clean up diff --git a/src/test/java/com/conveyal/r5/streets/ScenarioModifyTurnRestrictions.java b/src/test/java/com/conveyal/r5/streets/ScenarioModifyTurnRestrictions.java new file mode 100644 index 000000000..d439d0c25 --- /dev/null +++ b/src/test/java/com/conveyal/r5/streets/ScenarioModifyTurnRestrictions.java @@ -0,0 +1,181 @@ +package com.conveyal.r5.streets; + +import com.conveyal.gtfs.model.Route; +import com.conveyal.r5.analyst.scenario.AddTrips; +import com.conveyal.r5.analyst.scenario.FakeGraph; +import com.conveyal.r5.analyst.scenario.Scenario; +import com.conveyal.r5.analyst.scenario.StopSpec; +import com.conveyal.r5.point_to_point.builder.TNBuilderConfig; +import com.conveyal.r5.transit.TransportNetwork; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; + +import static com.conveyal.r5.analyst.scenario.FakeGraph.buildNetwork; +import static org.junit.Assert.*; + +/** + * Tests that turn restriction works after scenario splits from/to/via edges + * + * Works means that turn restriction on original network stay the same (turnRestriction field in edgeStore and streetLayer) and + * turnRestrictionsVia, turnRestrictionsReverse in Edgestore. + * + * And that in network with scenario they are updated. + * Created by mabu on 11.4.2017. + */ +public class ScenarioModifyTurnRestrictions { + private static final Logger LOG = LoggerFactory.getLogger(ScenarioModifyTurnRestrictions.class); + + private TransportNetwork network; + private long checksum; + + @Before + public void setUpGraph() throws Exception { + TNBuilderConfig tnBuilderConfig = new TNBuilderConfig(false); + network = buildNetwork(StreetLayerTest.class.getResourceAsStream("turn-restriction-split-test.pbf"), + "turn-restriction-split-test", tnBuilderConfig, + FakeGraph.TransitNetwork.SINGLE_LINE); + checksum = network.checksum(); + + } + + //Creates Scenario with trip on specified stop and applies it to network + //Returns modified network + private TransportNetwork addStop(double lon, double lat) { + AddTrips at = new AddTrips(); + at.bidirectional = true; + at.stops = Arrays.asList( + new StopSpec(lon, lat), + new StopSpec(-83.0014, 39.962), + new StopSpec(-82.9495, 39.962) + ); + at.mode = Route.BUS; + + AddTrips.PatternTimetable entry = new AddTrips.PatternTimetable(); + entry.headwaySecs = 900; + entry.monday = entry.tuesday = entry.wednesday = entry.thursday = entry.friday = true; + entry.saturday = entry.sunday = false; + entry.hopTimes = new int[] { 120, 140 }; + entry.dwellTimes = new int[] { 0, 30, 0 }; + entry.startTime = 7 * 3600; + entry.endTime = 10 * 3600; + + at.frequencies = Arrays.asList(entry); + + Scenario scenario = new Scenario(); + scenario.modifications = Arrays.asList(at); + + return scenario.applyToTransportNetwork(network); + } + + @Test + public void testStopSplitsFromEdge() throws Exception { + TurnRestriction tr = network.streetLayer.turnRestrictions.get(1); + LOG.debug("TR:{}", tr); + int turnRestrictionFrom = tr.fromEdge; + int turnRestrictionSize = network.streetLayer.turnRestrictions.size(); + + int[] restrictionsOnFrom = network.streetLayer.edgeStore.turnRestrictions + .get(turnRestrictionFrom).toArray(); + + //Stop was added that splits fromEdge on turn Restriction + TransportNetwork mod = addStop(-76.9959183, 38.8921836); + + //Size of turn restrictions needs to be the same + + int copyTurnRestrictionSize = mod.streetLayer.turnRestrictions.size(); + + //List of turn restrictions needs to be copied since if we add transitStops that split turnRestriction edges + //We need to update turnRestrictions (since edges are different) and this needs to be different than original TurnRestriction list + assertEquals(turnRestrictionSize, copyTurnRestrictionSize); + + //Original turnRestriction needs to remain the same + assertEquals(turnRestrictionFrom, network.streetLayer.turnRestrictions.get(1).fromEdge); + + assertArrayEquals(restrictionsOnFrom, network.streetLayer.edgeStore.turnRestrictions.get(turnRestrictionFrom).toArray()); + + //copied streetLayer needs to have new from edge + assertNotEquals(turnRestrictionFrom, mod.streetLayer.turnRestrictions.get(1).fromEdge); + + assertNotEquals(restrictionsOnFrom, mod.streetLayer.edgeStore.turnRestrictions.get(turnRestrictionFrom).toArray()); + } + + @Test + public void testStopSplitsToEdge() throws Exception { + TurnRestriction tr = network.streetLayer.turnRestrictions.get(1); + LOG.info("TR:{}", tr); + int turnRestrictionTo = tr.toEdge; + int turnRestrictionSize = network.streetLayer.turnRestrictions.size(); + + int[] restrictionsOnTo = network.streetLayer.edgeStore.turnRestrictionsReverse + .get(turnRestrictionTo).toArray(); + + //Stop was added that splits toEdge on turn Restriction + TransportNetwork mod = addStop(-76.996019,38.8919775); + + //Size of turn restrictions needs to be the same + + int copyTurnRestrictionSize = mod.streetLayer.turnRestrictions.size(); + + //List of turn restrictions needs to be copied since if we add transitStops that split turnRestriction edges + //We need to update turnRestrictions (since edges are different) and this needs to be different than original TurnRestriction list + assertEquals(turnRestrictionSize, copyTurnRestrictionSize); + + //Original turnRestriction needs to remain the same + assertEquals(turnRestrictionTo, network.streetLayer.turnRestrictions.get(1).toEdge); + + assertArrayEquals(restrictionsOnTo, network.streetLayer.edgeStore.turnRestrictionsReverse.get(turnRestrictionTo).toArray()); + + //copied streetLayer needs to have same to edge + assertEquals(turnRestrictionTo, mod.streetLayer.turnRestrictions.get(1).toEdge); + + assertArrayEquals(restrictionsOnTo, mod.streetLayer.edgeStore.turnRestrictionsReverse.get(turnRestrictionTo).toArray()); + } + + @Test + public void testStopSplitsViaEdge() throws Exception { + + //Changes turnRestriction from:146 to 134 to 146 via 134 to 136 + network.streetLayer.turnRestrictions.get(1).toEdge = 136; + network.streetLayer.turnRestrictions.get(1).viaEdges = new int[]{ 134 }; + network.streetLayer.edgeStore.turnRestrictionsVia.put(134, 1); + + TurnRestriction tr = network.streetLayer.turnRestrictions.get(1); + LOG.info("TR:{}", tr); + int turnRestrictionFrom = tr.fromEdge; + int[] turnRestrictionsVia = tr.viaEdges; + int turnRestrictionSize = network.streetLayer.turnRestrictions.size(); + + int[] restrictionsOnVia = network.streetLayer.edgeStore.turnRestrictionsVia + .get(turnRestrictionsVia[0]).toArray(); + + //Stop was added that splits viaEdge on turn Restriction + TransportNetwork mod = addStop(-76.996019, 38.8919775); + + //Size of turn restrictions needs to be the same + + int copyTurnRestrictionSize = mod.streetLayer.turnRestrictions.size(); + + //List of turn restrictions needs to be copied since if we add transitStops that split turnRestriction edges + //We need to update turnRestrictions (since edges are different) and this needs to be different than original TurnRestriction list + assertEquals(turnRestrictionSize, copyTurnRestrictionSize); + + //Original turnRestriction needs to remain the same + assertEquals(turnRestrictionFrom, network.streetLayer.turnRestrictions.get(1).fromEdge); + + assertArrayEquals(turnRestrictionsVia, network.streetLayer.turnRestrictions.get(1).viaEdges); + + assertArrayEquals(restrictionsOnVia, network.streetLayer.edgeStore.turnRestrictionsVia.get(turnRestrictionsVia[0]).toArray()); + assertFalse(network.streetLayer.edgeStore.turnRestrictionsVia.containsKey(154)); + + //copied streetLayer needs to have new via edges + assertEquals(turnRestrictionFrom, mod.streetLayer.turnRestrictions.get(1).fromEdge); + + assertEquals(turnRestrictionsVia.length+1, mod.streetLayer.turnRestrictions.get(1).viaEdges.length); + + assertArrayEquals(new int[]{1}, mod.streetLayer.edgeStore.turnRestrictionsVia.get(154).toArray()); + } +} diff --git a/src/test/java/com/conveyal/r5/streets/TurnRestrictionTest.java b/src/test/java/com/conveyal/r5/streets/TurnRestrictionTest.java index 0044a0ef6..8c198517e 100644 --- a/src/test/java/com/conveyal/r5/streets/TurnRestrictionTest.java +++ b/src/test/java/com/conveyal/r5/streets/TurnRestrictionTest.java @@ -12,6 +12,11 @@ import static org.junit.Assert.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertEquals; + public class TurnRestrictionTest extends TurnTest { private static final Logger LOG = LoggerFactory.getLogger(TurnRestrictionTest.class); diff --git a/src/test/java/com/conveyal/r5/streets/TurnSplitTest.java b/src/test/java/com/conveyal/r5/streets/TurnSplitTest.java new file mode 100644 index 000000000..10321c9d3 --- /dev/null +++ b/src/test/java/com/conveyal/r5/streets/TurnSplitTest.java @@ -0,0 +1,651 @@ +package com.conveyal.r5.streets; + +import com.conveyal.osmlib.OSM; +import com.conveyal.r5.point_to_point.builder.TNBuilderConfig; +import com.conveyal.r5.profile.ProfileRequest; +import com.conveyal.r5.profile.StreetMode; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Created by mabu on 29.3.2017. + */ +public class TurnSplitTest { + private static final Logger LOG = LoggerFactory.getLogger(TurnSplitTest.class); + + private static StreetLayer sl; + + @Before + public void setUpGraph() throws Exception { + OSM osm = new OSM(null); + osm.intersectionDetection = true; + osm.readFromUrl(StreetLayerTest.class.getResource("turn-restriction-split-test.pbf").toString()); + + sl = new StreetLayer(TNBuilderConfig.defaultConfig()); + // load from OSM and don't remove floating subgraphs + sl.loadFromOsm(osm, false, true); + + sl.buildEdgeLists(); + + } + + //Test if turn restriction still works if fromEdge is split + //This splitting happens in StreetLayer#getOrCreateVertexNear which is used in associateBikeSharing, buildParkAndRideNodes and associateStops + @Test + public void testTurnRestrictionWithSplitOnFrom() throws Exception { + + ProfileRequest profileRequest = new ProfileRequest(); + profileRequest.fromLat = 38.8930088; + profileRequest.fromLon = -76.998343; + profileRequest.toLat = 38.8914185; + profileRequest.toLon = -76.9962294; + + StreetRouter streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + StreetRouter.State lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + + int oldWeight = lastState.weight; + StreetRouter.State state = lastState; + EdgeStore.Edge edge = sl.edgeStore.getCursor(); + + TurnRestriction tr = sl.turnRestrictions.get(1); + + LOG.info("TR:{}->{}", tr.fromEdge, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getFlagsAsString()); + //Turn from 143 to 145 is in Turn restriction + assertFalse(state.backEdge == 134 && (state.backState.backEdge == 152 || state.backState.backEdge == 146)); + state = state.backState; + + } + + //Splits "from Turn restriction edge" + int vertex = sl.createAndLinkVertex(38.8921836,-76.9959183); + + sl.buildEdgeLists(); + + streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + state = lastState; + + int newWeight = lastState.weight; + + assertEquals(oldWeight, newWeight); + + + //LOG.info("TR:{}->{}", tr.fromEdge, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + //LOG.info("V:{} W:{} be:{}, flags:{}", state.vertex, state.weight, state.backEdge, edge.getFlagsAsString()); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getFlagsAsString()); + //Turn from 143 to 146 is in Turn restriction + assertFalse(state.backEdge == 134 && (state.backState.backEdge == 152 || state.backState.backEdge == 146)); + state = state.backState; + + } + + } + + //Test if turn restriction still works if fromEdge is split which isn't forward edge but backward edge + //This splitting happens in StreetLayer#getOrCreateVertexNear which is used in associateBikeSharing, buildParkAndRideNodes and associateStops + @Test + public void testTurnRestrictionWithSplitOnFromBackwardEdge() throws Exception { + + TurnRestriction restriction = new TurnRestriction(); + restriction.fromEdge = 45; + restriction.toEdge = 28; + restriction.only = false; + int ridx = sl.turnRestrictions.size(); + sl.turnRestrictions.add(restriction); + sl.edgeStore.turnRestrictions.put(restriction.fromEdge, ridx); + sl.addReverseTurnRestriction(restriction, ridx); + + + ProfileRequest profileRequest = new ProfileRequest(); + profileRequest.fromLat = 38.89098; + profileRequest.fromLon = -76.99478; + profileRequest.toLat = 38.891657; + profileRequest.toLon = -76.99661326; + + StreetRouter streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + StreetRouter.State lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + + int oldWeight = lastState.weight; + StreetRouter.State state = lastState; + EdgeStore.Edge edge = sl.edgeStore.getCursor(); + + TurnRestriction tr = sl.turnRestrictions.get(2); + + LOG.info("TR:{}->{}", tr.fromEdge, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getLengthMm()); + //Turn from 45 to 28 is in Turn restriction 153 is new splitted edge + assertFalse(state.backEdge == 28 && (state.backState.backEdge == 45 || state.backState.backEdge == 153)); + state = state.backState; + + } + + //Splits "from Turn restriction edge" + int vertex = sl.createAndLinkVertex(38.8909806,-76.995403); + + sl.buildEdgeLists(); + + streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + state = lastState; + + int newWeight = lastState.weight; + + //Weight isn't exactly the same because edge is split and some decimals are forgotten + // when weight is summed since it's an integer not float. + assertEquals(oldWeight, newWeight+1); + + + //LOG.info("TR:{}->{}", tr.fromEdge, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + //LOG.info("V:{} W:{} be:{}, flags:{}", state.vertex, state.weight, state.backEdge, edge.getFlagsAsString()); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getLengthMm()); + //Turn from 45 to 28 is in Turn restriction 153 is new splitted edge + assertFalse(state.backEdge == 28 && (state.backState.backEdge == 45 || state.backState.backEdge == 153)); + state = state.backState; + + } + + } + + //Test if turn restriction still works if toEdge is split + //This splitting happens in StreetLayer#getOrCreateVertexNear which is used in associateBikeSharing, buildParkAndRideNodes and associateStops + @Test + public void testTurnRestrictionWithSplitOnTo() throws Exception { + + ProfileRequest profileRequest = new ProfileRequest(); + profileRequest.fromLat = 38.8930088; + profileRequest.fromLon = -76.998343; + profileRequest.toLat = 38.8914185; + profileRequest.toLon = -76.9962294; + + StreetRouter streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + StreetRouter.State lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + + int oldWeight = lastState.weight; + StreetRouter.State state = lastState; + EdgeStore.Edge edge = sl.edgeStore.getCursor(); + + TurnRestriction tr = sl.turnRestrictions.get(1); + + LOG.info("TR:{}->{}", tr.fromEdge, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getFlagsAsString()); + //Turn from 143 to 145 is in Turn restriction + assertFalse(state.backEdge == 134 && (state.backState.backEdge == 152 || state.backState.backEdge == 146)); + state = state.backState; + + } + + //Splits "to Turn restriction edge" + int vertex = sl.createAndLinkVertex(38.8919775,-76.996019); + + sl.buildEdgeLists(); + + streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + state = lastState; + + int newWeight = lastState.weight; + + assertEquals(oldWeight, newWeight); + + + //LOG.info("TR:{}->{}", tr.fromEdge, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + LOG.info("V:{} W:{} be:{}, flags:{}", state.vertex, state.weight, state.backEdge, edge.getFlagsAsString()); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getFlagsAsString()); + //Turn from 143 to 146 is in Turn restriction + assertFalse(state.backEdge == 134 && (state.backState.backEdge == 152 || state.backState.backEdge == 146)); + state = state.backState; + + } + + } + + //Test if turn restriction still works if toEdge is split which isn't forward edge but backward edge + //This splitting happens in StreetLayer#getOrCreateVertexNear which is used in associateBikeSharing, buildParkAndRideNodes and associateStops + @Test + public void testTurnRestrictionWithSplitOnToBackwardEdge() throws Exception { + + TurnRestriction restriction = new TurnRestriction(); + restriction.fromEdge = 47; + restriction.toEdge = 45; + restriction.only = false; + int ridx = sl.turnRestrictions.size(); + sl.turnRestrictions.add(restriction); + sl.edgeStore.turnRestrictions.put(restriction.fromEdge, ridx); + sl.addReverseTurnRestriction(restriction, ridx); + + + ProfileRequest profileRequest = new ProfileRequest(); + profileRequest.fromLat = 38.89098; + profileRequest.fromLon = -76.99478; + profileRequest.toLat = 38.891657; + profileRequest.toLon = -76.99661326; + + StreetRouter streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + StreetRouter.State lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + + int oldWeight = lastState.weight; + StreetRouter.State state = lastState; + EdgeStore.Edge edge = sl.edgeStore.getCursor(); + + TurnRestriction tr = sl.turnRestrictions.get(2); + + LOG.info("TR:{}->{}", tr.fromEdge, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getLengthMm()); + //Turn from 47 to 45 is in Turn restriction 153 is new splitted edge + assertFalse(state.backEdge == 45 && (state.backState.backEdge == 47 || state.backState.backEdge == 153)); + state = state.backState; + + } + + //Splits "from Turn restriction edge" + int vertex = sl.createAndLinkVertex(38.8909806,-76.995403); + + sl.buildEdgeLists(); + + streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + state = lastState; + + int newWeight = lastState.weight; + + //Weight isn't exactly the same because edge is split and some decimals are forgotten + // when weight is summed since it's an integer not float. + assertEquals(oldWeight, newWeight); + + + LOG.info("TR:{}->{}", tr.fromEdge, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + //LOG.info("V:{} W:{} be:{}, flags:{}", state.vertex, state.weight, state.backEdge, edge.getFlagsAsString()); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getLengthMm()); + //Turn from 47 to 45 is in Turn restriction 153 is new splitted edge + assertFalse(state.backEdge == 45 && (state.backState.backEdge == 47 || state.backState.backEdge == 153)); + state = state.backState; + + } + + } + + + //Test if turn restriction still works if viaEdge is split + //This splitting happens in StreetLayer#getOrCreateVertexNear which is used in associateBikeSharing, buildParkAndRideNodes and associateStops + @Test + public void testTurnRestrictionWithSplitOnVia() throws Exception { + + ProfileRequest profileRequest = new ProfileRequest(); + profileRequest.fromLat = 38.8930088; + profileRequest.fromLon = -76.998343; + profileRequest.toLat = 38.8914185; + profileRequest.toLon = -76.99933; + + //Changes turnRestriction from:146 to 134 to 146 via 134 to 136 + sl.turnRestrictions.get(1).toEdge = 136; + sl.turnRestrictions.get(1).viaEdges = new int[]{ 134 }; + sl.edgeStore.turnRestrictionsVia.put(134, 1); + + StreetRouter streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + StreetRouter.State lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + + int oldWeight = lastState.weight; + StreetRouter.State state = lastState; + EdgeStore.Edge edge = sl.edgeStore.getCursor(); + + TurnRestriction tr = sl.turnRestrictions.get(1); + + LOG.debug("TR:{}->{}->{}", tr.fromEdge, tr.viaEdges, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getFlagsAsString()); + //Turn from 143 to 145 is in Turn restriction + assertFalse(state.backEdge == 134 && (state.backState.backEdge == 152 || state.backState.backEdge == 146)); + state = state.backState; + + } + + //Splits "via Turn restriction edge 134" + int vertex = sl.createAndLinkVertex(38.8919775,-76.996019); + + sl.buildEdgeLists(); + + streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + state = lastState; + + int newWeight = lastState.weight; + + + //LOG.info("TR:{}->{}", tr.fromEdge, tr.toEdge); + while (state != null) { + edge.seek(state.backEdge); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getFlagsAsString()); + //Turn from 143 to 146 is in Turn restriction + assertFalse(state.backEdge == 134 && (state.backState.backEdge == 152 || state.backState.backEdge == 146)); + state = state.backState; + + } + + assertEquals(oldWeight, newWeight); + + } + + //Test if turn restriction still works if viaEdge is split which isn't forward edge but backward edge + //This splitting happens in StreetLayer#getOrCreateVertexNear which is used in associateBikeSharing, buildParkAndRideNodes and associateStops + @Test + public void testTurnRestrictionWithSplitOnViaBackwardEdge() throws Exception { + + TurnRestriction restriction = new TurnRestriction(); + restriction.fromEdge = 47; + restriction.toEdge = 28; + restriction.viaEdges = new int[]{45}; + restriction.only = false; + int ridx = sl.turnRestrictions.size(); + sl.turnRestrictions.add(restriction); + sl.edgeStore.turnRestrictions.put(restriction.fromEdge, ridx); + sl.edgeStore.turnRestrictionsVia.put(restriction.viaEdges[0], ridx); + sl.addReverseTurnRestriction(restriction, ridx); + + + ProfileRequest profileRequest = new ProfileRequest(); + profileRequest.fromLat = 38.89098; + profileRequest.fromLon = -76.99478; + profileRequest.toLat = 38.891657; + profileRequest.toLon = -76.99661326; + + StreetRouter streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + StreetRouter.State lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + + int oldWeight = lastState.weight; + StreetRouter.State state = lastState; + EdgeStore.Edge edge = sl.edgeStore.getCursor(); + + TurnRestriction tr = sl.turnRestrictions.get(2); + + LOG.info("TR:{}", tr); + while (state != null) { + edge.seek(state.backEdge); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getLengthMm()); + //Turn from 47 to 45 is in Turn restriction 153 is new splitted edge + assertFalse(state.backEdge == 28 && (state.backState.backEdge == 45)); + state = state.backState; + + } + + //Splits "from Turn restriction edge" + int vertex = sl.createAndLinkVertex(38.8909806,-76.995403); + + sl.buildEdgeLists(); + + streetRouter = new StreetRouter(sl); + streetRouter.profileRequest = profileRequest; + streetRouter.streetMode = StreetMode.CAR; + + + + streetRouter.distanceLimitMeters = 5_000; + //Split for end coordinate + assertTrue("Destination must be found", streetRouter.setDestination(profileRequest.toLat, profileRequest.toLon)); + assertTrue("Origin must be found", streetRouter.setOrigin(profileRequest.fromLat, profileRequest.fromLon)); + + streetRouter.route(); + + lastState = streetRouter.getState(streetRouter.getDestinationSplit()); + //LOG.info("W:{}", lastState.weight); + state = lastState; + + int newWeight = lastState.weight; + + //Weight isn't exactly the same because edge is split and some decimals are forgotten + // when weight is summed since it's an integer not float. + assertEquals(oldWeight, newWeight+1); + + + LOG.info("TR:{}", tr); + while (state != null) { + edge.seek(state.backEdge); + //LOG.info("V:{} W:{} be:{}, flags:{}", state.vertex, state.weight, state.backEdge, edge.getFlagsAsString()); + LOG.debug("V:{} W:{} be:{}, TR:{} flags:{}", state.vertex, state.weight, state.backEdge, sl.edgeStore.turnRestrictions.containsKey(state.backEdge), edge.getLengthMm()); + //Turn from 47 to 45 is in Turn restriction 153 is new splitted edge + assertFalse(state.backEdge == 28 && (state.backState.backEdge == 45)); + state = state.backState; + + } + + } + + // Test if turn restriction is valid if from edge on ONLY turn is split + // This is different from normal from edge since ONLY turns from edges aren't saved in TurnRestriction list + @Test + public void testTurnRestrictionWithSplitOnFromOnly() throws Exception { + TurnRestriction out = new TurnRestriction(); + out.fromEdge = 134; + out.toEdge = 31; + out.only = true; + int index = sl.turnRestrictions.size(); + sl.turnRestrictions.add(out); + sl.edgeStore.turnRestrictions.put(out.fromEdge, index); + sl.addReverseTurnRestriction(out, index); + for (int i =index; i < sl.turnRestrictions.size(); i++) { + TurnRestriction tr = sl.turnRestrictions.get(i); + LOG.info("TR:{} {}", i, tr); + testTurnRestriction(tr); + } + + //Splits FROM edge + sl.createAndLinkVertex(38.8919723,-76.9959763); + sl.buildEdgeLists(); + LOG.info("Splits from edge on TR"); + + for (int i =index; i < sl.turnRestrictions.size(); i++) { + TurnRestriction tr = sl.turnRestrictions.get(i); + if (i == index) { + assertFalse("New Turn restriction should have different FROM edge", tr.fromEdge == 134); + } + testTurnRestriction(tr); + } + + } + + // Test if turn restriction is valid if to edge on ONLY turn is split + // This is different from normal to edge test since ONLY turns to edges aren't saved in TurnRestrictionReverse list + @Test + public void testTurnRestrictionWithSplitOnToOnly() throws Exception { + TurnRestriction out = new TurnRestriction(); + out.fromEdge = 134; + out.toEdge = 31; + out.only = true; + int index = sl.turnRestrictions.size(); + sl.turnRestrictions.add(out); + sl.edgeStore.turnRestrictions.put(out.fromEdge, index); + sl.addReverseTurnRestriction(out, index); + for (int i =index; i < sl.turnRestrictions.size(); i++) { + TurnRestriction tr = sl.turnRestrictions.get(i); + testTurnRestriction(tr); + } + + //Splits TO edge + sl.createAndLinkVertex(38.891768,-76.9962057); + sl.buildEdgeLists(); + LOG.info("Splits to edge on TR"); + + for (int i =index; i < sl.turnRestrictions.size(); i++) { + TurnRestriction tr = sl.turnRestrictions.get(i); + if (i == index) { + assertFalse("New Turn restriction should have different TO edge", tr.toEdge == 31); + } + testTurnRestriction(tr); + } + + + + } + + public void testTurnRestriction(TurnRestriction tr) { + EdgeStore.Edge fromEdge = sl.edgeStore.getCursor(tr.fromEdge); + EdgeStore.Edge toEdge = sl.edgeStore.getCursor(tr.toEdge); + + int viaVertex = fromEdge.getToVertex(); + LOG.info("TR:{} -> {}, {}", fromEdge.getOSMID(), toEdge.getOSMID(), tr); + //LOG.info ("Frome edge distance:{}", fromEdge.getLengthMm()); + assertTrue(sl.outgoingEdges.get(viaVertex).contains(tr.toEdge)); + + + } +} diff --git a/src/test/java/com/conveyal/r5/streets/TurnTest.java b/src/test/java/com/conveyal/r5/streets/TurnTest.java index a8d2d958e..fa51980cd 100644 --- a/src/test/java/com/conveyal/r5/streets/TurnTest.java +++ b/src/test/java/com/conveyal/r5/streets/TurnTest.java @@ -1,15 +1,13 @@ package com.conveyal.r5.streets; import com.conveyal.r5.point_to_point.builder.TNBuilderConfig; -import junit.framework.TestCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Base class for tests of turn costs and restrictions.. */ -public abstract class TurnTest { - public StreetLayer streetLayer; +public abstract class TurnTest extends TurnTestUtils { private static final Logger LOG = LoggerFactory.getLogger(TurnTest.class); @@ -58,22 +56,6 @@ public void setUp (boolean southernHemisphere) { streetLayer.buildEdgeLists(); } - /** create a turn restriction */ - public void restrictTurn (boolean onlyTurn, int from, int to, int... via) { - TurnRestriction restriction = new TurnRestriction(); - restriction.fromEdge = from; - restriction.toEdge = to; - restriction.only = onlyTurn; - restriction.viaEdges = via; - LOG.debug("{}", restriction); - int ridx = streetLayer.turnRestrictions.size(); - streetLayer.turnRestrictions.add(restriction); - streetLayer.edgeStore.turnRestrictions.put(restriction.fromEdge, ridx); - streetLayer.addReverseTurnRestriction(restriction, ridx); - - - } - /** * Creates turn restriction without adding it to turnRestriction maps * @@ -101,4 +83,5 @@ static TurnRestriction makeTurnRestriction(boolean onlyTurn, int from, int to, turnRestriction.viaEdges = new int[]{viaEdge}; return turnRestriction; } + } diff --git a/src/test/java/com/conveyal/r5/streets/TurnTestUtils.java b/src/test/java/com/conveyal/r5/streets/TurnTestUtils.java new file mode 100644 index 000000000..3e74294f5 --- /dev/null +++ b/src/test/java/com/conveyal/r5/streets/TurnTestUtils.java @@ -0,0 +1,21 @@ +package com.conveyal.r5.streets; + +/** + * Moved from TurnTest + */ +public class TurnTestUtils { + public StreetLayer streetLayer; + + /** create a turn restriction */ + public void restrictTurn (boolean onlyTurn, int from, int to, int... via) { + TurnRestriction restriction = new TurnRestriction(); + restriction.fromEdge = from; + restriction.toEdge = to; + restriction.only = onlyTurn; + restriction.viaEdges = via; + int ridx = streetLayer.turnRestrictions.size(); + streetLayer.turnRestrictions.add(restriction); + streetLayer.edgeStore.turnRestrictions.put(restriction.fromEdge, ridx); + streetLayer.addReverseTurnRestriction(restriction, ridx); + } +} diff --git a/src/test/resources/com/conveyal/r5/streets/turn-restriction-split-test.pbf b/src/test/resources/com/conveyal/r5/streets/turn-restriction-split-test.pbf new file mode 100644 index 000000000..ffe9a21bf Binary files /dev/null and b/src/test/resources/com/conveyal/r5/streets/turn-restriction-split-test.pbf differ