From 2cadaea16746bcf29cfb52426278ee38425b2aeb Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 20:22:37 +0200 Subject: [PATCH 01/28] TEST: NAV-49 - Add helper methods for checking routes. --- .../java/ch/naviqore/raptor/RaptorTest.java | 84 +++++++++---------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index 52a1dd84..9341ff62 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -39,34 +39,9 @@ void shouldFindConnectionsBetweenIntersectingRoutes(RaptorTestBuilder builder) { // check if 2 connections were found assertEquals(2, connections.size()); - - // check if the first connection is correct - Connection connection1 = connections.getFirst(); - assertEquals(sourceStop, connection1.getFromStopId()); - assertEquals(targetStop, connection1.getToStopId()); - assertTrue(connection1.getDepartureTime() >= departureTime, - "Departure time should be greater equal than searched for departure time"); - // check that transfers make sense - assertEquals(1, connection1.getWalkTransfers().size()); - assertEquals(1, connection1.getNumberOfTotalTransfers()); - assertEquals(0, connection1.getNumberOfSameStationTransfers()); - - // check second connection - Connection connection2 = connections.get(1); - assertEquals(sourceStop, connection2.getFromStopId()); - assertEquals(targetStop, connection2.getToStopId()); - assertTrue(connection2.getDepartureTime() >= departureTime, - "Departure time should be greater equal than searched for departure time"); - // check that transfers make sense - assertEquals(0, connection2.getWalkTransfers().size()); - assertEquals(2, connection2.getNumberOfTotalTransfers()); - assertEquals(2, connection2.getNumberOfSameStationTransfers()); - - // compare two connections (make sure they are pareto optimal) - assertTrue(connection1.getDuration() > connection2.getDuration(), - "First connection should be slower than second connection"); - assertTrue(connection1.getRouteLegs().size() < connection2.getRouteLegs().size(), - "First connection should have fewer route legs than second connection"); + Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 1, 2); + Helpers.assertConnection(connections.get(1), sourceStop, targetStop, departureTime, 2, 0, 3); + Helpers.checkIfConnectionsAreParetoOptimal(connections); } @Test @@ -78,14 +53,7 @@ void routeBetweenTwoStopsOnSameRoute(RaptorTestBuilder builder) { int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); assertEquals(1, connections.size()); - Connection connection = connections.getFirst(); - assertEquals(sourceStop, connection.getFromStopId()); - assertEquals(targetStop, connection.getToStopId()); - assertTrue(connection.getDepartureTime() >= departureTime, - "Departure time should be greater equal than searched for departure time"); - assertEquals(0, connection.getWalkTransfers().size()); - assertEquals(0, connection.getNumberOfTotalTransfers()); - assertEquals(0, connection.getNumberOfSameStationTransfers()); + Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 0, 1); } @Test @@ -115,15 +83,41 @@ void shouldFindConnectionBetweenOnlyFootpath(RaptorTestBuilder builder) { int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); assertEquals(1, connections.size()); - Connection connection = connections.getFirst(); - assertEquals(sourceStop, connection.getFromStopId()); - assertEquals(targetStop, connection.getToStopId()); - assertTrue(connection.getDepartureTime() >= departureTime, - "Departure time should be greater equal than searched for departure time"); - assertEquals(1, connection.getWalkTransfers().size()); - assertEquals(1, connection.getNumberOfTotalTransfers()); - assertEquals(0, connection.getNumberOfSameStationTransfers()); - assertEquals(0, connection.getRouteLegs().size()); + Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 1, 0); + } + + private static class Helpers { + + private static void assertConnection(Connection connection, String sourceStop, String targetStop, + int departureTime, int numSameStationTransfers, int numWalkTransfers, + int numTrips) { + assertEquals(sourceStop, connection.getFromStopId()); + assertEquals(targetStop, connection.getToStopId()); + assertTrue(connection.getDepartureTime() >= departureTime, + "Departure time should be greater equal than searched for departure time"); + + assertEquals(numSameStationTransfers, connection.getNumberOfSameStationTransfers(), + "Number of same station transfers should match"); + assertEquals(numWalkTransfers, connection.getWalkTransfers().size(), + "Number of walk transfers should match"); + assertEquals(numSameStationTransfers + numWalkTransfers, connection.getNumberOfTotalTransfers(), + "Number of transfers should match"); + + assertEquals(numTrips, connection.getRouteLegs().size(), "Number of trips should match"); + } + + private static void checkIfConnectionsAreParetoOptimal(List connections) { + Connection previousConnection = connections.getFirst(); + for (int i = 1; i < connections.size(); i++) { + Connection currentConnection = connections.get(i); + assertTrue(previousConnection.getDuration() > currentConnection.getDuration(), + "Previous connection should be slower than current connection"); + assertTrue(previousConnection.getRouteLegs().size() < currentConnection.getRouteLegs().size(), + "Previous connection should have fewer route legs than current connection"); + previousConnection = currentConnection; + } + } + } @Nested From 490318b16d67d5f2e90146aca57844393e9cac30 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 22:21:58 +0200 Subject: [PATCH 02/28] FIX: NAV-49 - Fix expanding source stops in initial round based on the number of target stops. --- src/main/java/ch/naviqore/raptor/Raptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index bb576c7a..d0620774 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -164,7 +164,7 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId markedStops.add(sourceStopIdxs[i]); } - for (int i = 0; i < targetStopIdxs.length; i++) { + for (int i = 0; i < sourceStopIdxs.length; i++) { expandFootpathsForSourceStop(earliestArrivals, earliestArrivalsPerRound, markedStops, sourceStopIdxs[i], departureTimes[i]); } From 9f785049191b193eb50011b8964ee6eee04a7ed9 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 22:24:31 +0200 Subject: [PATCH 03/28] FIX: NAV-47 - Fix getEarliestArrival round expecting different order of array than built in initial raptor. When the targetStops array was built, in was building ["StopIdx1", "StopIdx2", "WalkTime1", "Walktime2"] and the getEarliestArrival method was expecting ["StopIdx1", "WalkTime1", "StopIdx2","Walktime2"] --- src/main/java/ch/naviqore/raptor/Raptor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index d0620774..14e7fad1 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -147,9 +147,10 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId } int[] targetStops = new int[targetStopIdxs.length * 2]; - for (int i = 0; i < targetStopIdxs.length; i++) { - targetStops[i] = targetStopIdxs[i]; - targetStops[i + targetStopIdxs.length] = walkingDurationsToTarget[i]; + for (int i = 0; i < targetStops.length; i += 2) { + int index = (int) Math.ceil(i / 2.0); + targetStops[i] = targetStopIdxs[index]; + targetStops[i + 1] = walkingDurationsToTarget[index]; } final List earliestArrivalsPerRound = new ArrayList<>(); From 82eaa5ed477702e8a728eee1dbaa6671de638c70 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 22:26:36 +0200 Subject: [PATCH 04/28] FIX: NAV-47 - Prevent adding walking time to infinity, which results in extremly small negative earliest arrival times. --- src/main/java/ch/naviqore/raptor/Raptor.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index 14e7fad1..887da8ec 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -340,7 +340,14 @@ private int getEarliestArrivalTime(int[] targetStops, int[] earliestArrivals) { for (int i = 0; i < targetStops.length; i += 2) { int targetStopIdx = targetStops[i]; int walkDurationToTarget = targetStops[i + 1]; - earliestArrival = Math.min(earliestArrival, earliestArrivals[targetStopIdx] + walkDurationToTarget); + int earliestArrivalAtTarget = earliestArrivals[targetStopIdx]; + + // To Prevent Adding a number to Max Integer Value (resulting in a very small negative number) + if( earliestArrivalAtTarget == INFINITY ) { + continue; + } + + earliestArrival = Math.min(earliestArrival, earliestArrivalAtTarget + walkDurationToTarget); } return earliestArrival; } From cd653e1bdb0b736af2fb8ec850c360316193eaf2 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 22:27:03 +0200 Subject: [PATCH 05/28] ENH: NAV-49 - Stop adding same stop transfer time when entered with Initial leg. --- src/main/java/ch/naviqore/raptor/Raptor.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index 887da8ec..53a23e51 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -281,9 +281,15 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId // find active trip, increase trip offset tripOffset = 0; enteredAtArrival = earliestArrivalsLastRound[stopIdx]; + + int earliestDepartureTime = enteredAtArrival.arrivalTime; + if( enteredAtArrival.type != ArrivalType.INITIAL ) { + earliestDepartureTime += SAME_STOP_TRANSFER_TIME; + } + while (tripOffset < numberOfTrips) { StopTime currentStopTime = stopTimes[firstStopTimeIdx + tripOffset * numberOfStops + stopOffset]; - if (currentStopTime.departure() >= enteredAtArrival.arrivalTime + SAME_STOP_TRANSFER_TIME) { + if (currentStopTime.departure() >= earliestDepartureTime) { log.debug("Found active trip ({}) on route {}", tripOffset, currentRoute.id()); tripEntryTime = currentStopTime.departure(); break; From 9b8bf6de17025b03047f08c21a91414657c63fd3 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 22:29:19 +0200 Subject: [PATCH 06/28] TEST: NAV-49 - Add test for self intersecting route. --- .../java/ch/naviqore/raptor/RaptorTest.java | 20 ++++++++++++++++++ .../ch/naviqore/raptor/RaptorTestBuilder.java | 21 ++++++++++++++++--- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index 9341ff62..b31c2b9e 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -56,6 +56,26 @@ void routeBetweenTwoStopsOnSameRoute(RaptorTestBuilder builder) { Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 0, 1); } + @Test + void routeWithSelfIntersectingRoute(RaptorTestBuilder builder) { + builder.withAddRoute5_AH_selfIntersecting(); + Raptor raptor = builder.build(); + + String sourceStop = "A"; + String targetStop = "H"; + int departureTime = 10 * RaptorTestBuilder.SECONDS_IN_HOUR; + + List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + assertEquals(2, connections.size()); + + // First Connection Should have no transfers but ride the entire loop (slow) + Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 0, 1); + // Second Connection Should Change at Stop B and take the earlier trip of the same route there (faster) + Helpers.assertConnection(connections.get(1), sourceStop, targetStop, departureTime, 1, 0, 2); + + Helpers.checkIfConnectionsAreParetoOptimal(connections); + } + @Test void shouldNotFindConnectionBetweenNotLinkedStops(RaptorTestBuilder builder) { // Omit route R2/R4 and transfers to make stop Q (on R3) unreachable from A (on R1) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java index d615288f..cf99aa68 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java @@ -29,6 +29,7 @@ *
  • R2: H, B, I, J, K, L
  • *
  • R3: M, K, N, O, P, Q
  • *
  • R4: R, P, F, S
  • + *
  • R5: A, B, C, D, E, F, P, O, N, K, J, I, B, H
  • * *

    * Transfers: @@ -128,6 +129,11 @@ public RaptorTestBuilder withAddRoute4_RS() { return this; } + public RaptorTestBuilder withAddRoute5_AH_selfIntersecting() { + routes.add(new Route("R5", List.of("A", "B", "C", "D", "E", "F", "P", "O", "N", "K", "J", "I", "B", "H"))); + return this; + } + public RaptorTestBuilder withAddTransfer1_ND() { return withAddTransfer1_ND(60); } @@ -157,9 +163,14 @@ public Raptor buildWithDefaults() { } /** - * Route, times are in minutes. + * Route. * - * @param headWayTime the time between the trip departures. + * @param id the route id. + * @param stops the stops of the route. + * @param firstDepartureOffset the time of the first departure in minutes after the start of the day. + * @param headWayTime the time between the trip departures in minutes. + * @param travelTimeBetweenStops the travel time between stops in minutes. + * @param dwellTimeAtSTop the dwell time at a stop in minutes (time between arrival and departure). */ private record Route(String id, List stops, int firstDepartureOffset, int headWayTime, int travelTimeBetweenStops, int dwellTimeAtSTop) { @@ -171,7 +182,11 @@ public Route(String id, List stops) { } /** - * Transfer, times are in minutes. + * Transfer. + * + * @param sourceStop the id of the source stop. + * @param targetStop the id of the target stop. + * @param duration the (walking) duration of the transfer between stops in minutes. */ private record Transfer(String sourceStop, String targetStop, int duration) { } From dc33cf92284ab6cbbf1b9fd1f20addee2e8e5db4 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 22:29:40 +0200 Subject: [PATCH 07/28] TEST: NAV-49 - Add mutli stop routing tests. --- .../java/ch/naviqore/raptor/RaptorTest.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index b31c2b9e..4efd426c 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import java.util.List; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; @@ -76,6 +77,70 @@ void routeWithSelfIntersectingRoute(RaptorTestBuilder builder) { Helpers.checkIfConnectionsAreParetoOptimal(connections); } + @Test + void routeFromTwoSourceStopsWithSameDepartureTime(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("A", departureTime, "B", departureTime); + Map targetStops = Map.of("H", 0); + + // fastest and only connection should be B -> H + List connections = raptor.routeEarliestArrival(sourceStops, targetStops); + assertEquals(1, connections.size()); + Helpers.assertConnection(connections.getFirst(), "B", "H", departureTime, 0, 0, 1); + } + + @Test + void routeFromTwoSourceStopsWithLaterDepartureTimeOnCloserStop(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("A", departureTime, "B", + departureTime + RaptorTestBuilder.SECONDS_IN_HOUR); + Map targetStops = Map.of("H", 0); + + // fastest and only connection should be B -> H + List connections = raptor.routeEarliestArrival(sourceStops, targetStops); + assertEquals(2, connections.size()); + Helpers.assertConnection(connections.getFirst(), "B", "H", + departureTime + RaptorTestBuilder.SECONDS_IN_HOUR, 0, 0, 1); + Helpers.assertConnection(connections.get(1), "A", "H", departureTime, 1, 0, 2); + } + + @Test + void routeFromStopToTwoTargetStopsNoWalkTimeToTarget(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("A", departureTime); + Map targetStops = Map.of("F", 0, "S", 0); + + // fastest and only connection should be A -> F + List connections = raptor.routeEarliestArrival(sourceStops, targetStops); + assertEquals(1, connections.size()); + Helpers.assertConnection(connections.getFirst(), "A", "F", departureTime, 0, 0, 1); + } + + @Test + void routeFromStopToTwoTargetStopsWithWalkTimeToTarget(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("A", departureTime); + Map targetStops = Map.of("F", RaptorTestBuilder.SECONDS_IN_HOUR, "S", 0); + + // since F is closer to A than S, the fastest connection should be A -> F, but because of the hour + // walk time to target, the connection A -> S should be faster (no additional walk time) + List connections = raptor.routeEarliestArrival(sourceStops, targetStops); + assertEquals(2, connections.size()); + Helpers.assertConnection(connections.getFirst(), "A", "F", departureTime, 0, 0, 1); + Helpers.assertConnection(connections.get(1), "A", "S", departureTime, 1, 0, 2); + + // Note since the required walk time to target is not added as a leg, the solutions will not be pareto + // optimal without additional post-processing. + } + @Test void shouldNotFindConnectionBetweenNotLinkedStops(RaptorTestBuilder builder) { // Omit route R2/R4 and transfers to make stop Q (on R3) unreachable from A (on R1) From 09c78209c85e15d18a4bce63d26f30a4ea2e12cc Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 22:45:09 +0200 Subject: [PATCH 08/28] TEST: NAV-49 - Add some input validation tests for mutlip stop raptor requests. --- .../java/ch/naviqore/raptor/RaptorTest.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index 4efd426c..5938a12c 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -237,6 +237,88 @@ void shouldThrowErrorWhenTargetStopNotExists() { "Target stop has to exists"); } + @Test + void shouldNotThrowErrorForValidAndNonExistingSourceStop() { + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("A", departureTime, "NonExistentStop", departureTime); + Map targetStops = Map.of("H", 0); + + assertDoesNotThrow(() -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Source stops can contain non-existing stops, if one entry is valid"); + } + + @Test + void shouldThrowErrorForInvalidDepartureTimeFromOneOfManySourceStops() { + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("A", departureTime, "B", Integer.MAX_VALUE); + Map targetStops = Map.of("H", 0); + + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Departure time has to be valid for all valid source stops"); + } + + @Test + void shouldNotThrowErrorForValidAndNonExistingTargetStop() { + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("H", departureTime); + Map targetStops = Map.of("A", 0, "NonExistentStop", 0); + + assertDoesNotThrow(() -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Target stops can contain non-existing stops, if one entry is valid"); + } + + @Test + void shouldThrowErrorForInvalidWalkToTargetTimeFromOneOfManyTargetStops() { + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("H", departureTime); + Map targetStops = Map.of("A", 0, "B", -1); + + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Departure time has to be valid for all valid source stops"); + } + + @Test + void shouldThrowErrorNullSourceStops(){ + Map sourceStops = null; + Map targetStops = Map.of("H", 0); + + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Source stops cannot be null"); + } + + @Test + void shouldThrowErrorNullTargetStops(){ + Map sourceStops = Map.of("A", 0); + Map targetStops = null; + + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Target stops cannot be null"); + } + + @Test + void shouldThrowErrorEmptyMapSourceStops(){ + Map sourceStops = Map.of(); + Map targetStops = Map.of("H", 0); + + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Source and target stops cannot be null"); + } + + @Test + void shouldThrowErrorEmptyMapTargetStops(){ + Map sourceStops = Map.of("A", 0); + Map targetStops = Map.of(); + + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Source and target stops cannot be null"); + } + @Test void shouldThrowErrorWhenDepartureTimeIsOutOfRange() { String sourceStop = "A"; From 470bce1659398879a99205924f59a34b10b13a73 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 22:45:51 +0200 Subject: [PATCH 09/28] FIX: NAV-49 - Add a null check for stop maps passed to raptor. --- src/main/java/ch/naviqore/raptor/Raptor.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index 53a23e51..e12855e0 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -79,9 +79,9 @@ private Map createStopMap(List stopIds, List v } public List routeEarliestArrival(Map sourceStops, Map targetStopIds) { - InputValidator.validateStopPermutations(sourceStops, targetStopIds); Map validatedSourceStopIdx = validator.validateStops(sourceStops); Map validatedTargetStopIdx = validator.validateStops(targetStopIds); + InputValidator.validateStopPermutations(sourceStops, targetStopIds); int[] sourceStopIdxs = validatedSourceStopIdx.keySet().stream().mapToInt(Integer::intValue).toArray(); int[] departureTimes = validatedSourceStopIdx.values().stream().mapToInt(Integer::intValue).toArray(); int[] targetStopIdxs = validatedTargetStopIdx.keySet().stream().mapToInt(Integer::intValue).toArray(); @@ -502,13 +502,6 @@ private class InputValidator { private static void validateStopPermutations(Map sourceStops, Map targetStops) { - if (sourceStops.isEmpty()) { - throw new IllegalArgumentException("At least one source stop must be provided."); - } - if (targetStops.isEmpty()) { - throw new IllegalArgumentException("At least one target stop must be provided."); - } - sourceStops.values().forEach(InputValidator::validateDepartureTime); targetStops.values().forEach(InputValidator::validateWalkingTimeToTarget); @@ -545,6 +538,9 @@ private static void validateWalkingTimeToTarget(int walkingDurationToTarget) { * @return a map of valid stop IDs and their corresponding departure / walk to target times. */ private Map validateStops(Map stops) { + if(stops == null) { + throw new IllegalArgumentException("Stops must not be null."); + } if (stops.isEmpty()) { throw new IllegalArgumentException("At least one stop ID must be provided."); } From 80174e9bc7265a89228feac227fd8893f601dbfe Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Thu, 6 Jun 2024 23:07:06 +0200 Subject: [PATCH 10/28] TEST: NAV-49 - Add same station transfre tests. --- .../java/ch/naviqore/raptor/RaptorTest.java | 265 ++++++++++-------- .../ch/naviqore/raptor/RaptorTestBuilder.java | 6 +- 2 files changed, 160 insertions(+), 111 deletions(-) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index 5938a12c..d5ca14c1 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -205,143 +205,188 @@ private static void checkIfConnectionsAreParetoOptimal(List connecti } - @Nested - class InputValidation { + } - private Raptor raptor; + @Nested + class SameStationTransfers { - @BeforeEach - void setUp(RaptorTestBuilder builder) { - raptor = builder.buildWithDefaults(); - } + @Test + void shouldTakeFirstTripWithoutAddingSameStationTransferTime(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + // There should be a connection leaving stop A at 5:00 am + String sourceStop = "A"; + String targetStop = "B"; + int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; + List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + assertEquals(1, connections.size()); + assertEquals(departureTime, connections.getFirst().getDepartureTime()); + } - @Test - void shouldThrowErrorWhenSourceStopNotExists() { - String sourceStop = "NonExistentStop"; - String targetStop = "A"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + @Test + void shouldMissConnectingTripBecauseOfSameStationTransferTime(RaptorTestBuilder builder) { + Raptor raptor = builder.withAddRoute1_AG(19, 15, 5, 1).withAddRoute2_HL().build(); + // TODO: Adjust when same station transfers are stop specific + // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am + // Connection at 5:24 from B to C should be missed because of the same station transfer time (120s) + String sourceStop = "A"; + String targetStop = "H"; + int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; + List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStop, targetStop, departureTime), - "Source stop has to exists"); - } + assertEquals(1, connections.size()); + assertEquals(departureTime + 19 * 60, connections.getFirst().getDepartureTime()); - @Test - void shouldThrowErrorWhenTargetStopNotExists() { - String sourceStop = "A"; - String targetStop = "NonExistentStop"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + assertNotEquals(departureTime + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); + } - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStop, targetStop, departureTime), - "Target stop has to exists"); - } + @Test + void shouldCatchConnectingTripBecauseWithSameStationTransferTime(RaptorTestBuilder builder) { + Raptor raptor = builder.withAddRoute1_AG(17, 15, 5, 1).withAddRoute2_HL().build(); + // TODO: Adjust when same station transfers are stop specific + // There should be a connection leaving stop A at 5:17 am and arriving at stop B at 5:22 am + // Connection at 5:24 from B to C should be cached when the same station transfer time is 120s + String sourceStop = "A"; + String targetStop = "H"; + int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; + List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); - @Test - void shouldNotThrowErrorForValidAndNonExistingSourceStop() { - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("A", departureTime, "NonExistentStop", departureTime); - Map targetStops = Map.of("H", 0); + assertEquals(1, connections.size()); + assertEquals(departureTime + 17 * 60, connections.getFirst().getDepartureTime()); - assertDoesNotThrow(() -> raptor.routeEarliestArrival(sourceStops, targetStops), - "Source stops can contain non-existing stops, if one entry is valid"); - } + assertEquals(departureTime + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); + } - @Test - void shouldThrowErrorForInvalidDepartureTimeFromOneOfManySourceStops() { - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("A", departureTime, "B", Integer.MAX_VALUE); - Map targetStops = Map.of("H", 0); + } - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStops, targetStops), - "Departure time has to be valid for all valid source stops"); - } + @Nested + class InputValidation { - @Test - void shouldNotThrowErrorForValidAndNonExistingTargetStop() { - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("H", departureTime); - Map targetStops = Map.of("A", 0, "NonExistentStop", 0); + private Raptor raptor; - assertDoesNotThrow(() -> raptor.routeEarliestArrival(sourceStops, targetStops), - "Target stops can contain non-existing stops, if one entry is valid"); - } + @BeforeEach + void setUp(RaptorTestBuilder builder) { + raptor = builder.buildWithDefaults(); + } - @Test - void shouldThrowErrorForInvalidWalkToTargetTimeFromOneOfManyTargetStops() { - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("H", departureTime); - Map targetStops = Map.of("A", 0, "B", -1); + @Test + void shouldThrowErrorWhenSourceStopNotExists() { + String sourceStop = "NonExistentStop"; + String targetStop = "A"; + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStops, targetStops), - "Departure time has to be valid for all valid source stops"); - } + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStop, targetStop, departureTime), + "Source stop has to exists"); + } + + @Test + void shouldThrowErrorWhenTargetStopNotExists() { + String sourceStop = "A"; + String targetStop = "NonExistentStop"; + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - @Test - void shouldThrowErrorNullSourceStops(){ - Map sourceStops = null; - Map targetStops = Map.of("H", 0); + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStop, targetStop, departureTime), + "Target stop has to exists"); + } - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStops, targetStops), - "Source stops cannot be null"); - } + @Test + void shouldNotThrowErrorForValidAndNonExistingSourceStop() { + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("A", departureTime, "NonExistentStop", departureTime); + Map targetStops = Map.of("H", 0); - @Test - void shouldThrowErrorNullTargetStops(){ - Map sourceStops = Map.of("A", 0); - Map targetStops = null; + assertDoesNotThrow(() -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Source stops can contain non-existing stops, if one entry is valid"); + } - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStops, targetStops), - "Target stops cannot be null"); - } + @Test + void shouldThrowErrorForInvalidDepartureTimeFromOneOfManySourceStops() { + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("A", departureTime, "B", Integer.MAX_VALUE); + Map targetStops = Map.of("H", 0); - @Test - void shouldThrowErrorEmptyMapSourceStops(){ - Map sourceStops = Map.of(); - Map targetStops = Map.of("H", 0); + assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Departure time has to be valid for all valid source stops"); + } - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStops, targetStops), - "Source and target stops cannot be null"); - } + @Test + void shouldNotThrowErrorForValidAndNonExistingTargetStop() { + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("H", departureTime); + Map targetStops = Map.of("A", 0, "NonExistentStop", 0); - @Test - void shouldThrowErrorEmptyMapTargetStops(){ - Map sourceStops = Map.of("A", 0); - Map targetStops = Map.of(); + assertDoesNotThrow(() -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Target stops can contain non-existing stops, if one entry is valid"); + } - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStops, targetStops), - "Source and target stops cannot be null"); - } + @Test + void shouldThrowErrorForInvalidWalkToTargetTimeFromOneOfManyTargetStops() { + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map sourceStops = Map.of("H", departureTime); + Map targetStops = Map.of("A", 0, "B", -1); - @Test - void shouldThrowErrorWhenDepartureTimeIsOutOfRange() { - String sourceStop = "A"; - String targetStop = "B"; + assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Departure time has to be valid for all valid source stops"); + } - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStop, targetStop, -1), - "Departure time cannot be negative"); - assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStop, targetStop, - 49 * RaptorTestBuilder.SECONDS_IN_HOUR), "Departure time cannot be greater than two days"); - } + @Test + void shouldThrowErrorNullSourceStops() { + Map sourceStops = null; + Map targetStops = Map.of("H", 0); - @Test - void shouldThrowErrorWhenRequestBetweenSameStop() { - String sourceStop = "A"; - String targetStop = "A"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Source stops cannot be null"); + } - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStop, targetStop, departureTime), - "Stops cannot be the same"); - } + @Test + void shouldThrowErrorNullTargetStops() { + Map sourceStops = Map.of("A", 0); + Map targetStops = null; + + assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Target stops cannot be null"); + } + + @Test + void shouldThrowErrorEmptyMapSourceStops() { + Map sourceStops = Map.of(); + Map targetStops = Map.of("H", 0); + + assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Source and target stops cannot be null"); + } + + @Test + void shouldThrowErrorEmptyMapTargetStops() { + Map sourceStops = Map.of("A", 0); + Map targetStops = Map.of(); + + assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), + "Source and target stops cannot be null"); + } + + @Test + void shouldThrowErrorWhenDepartureTimeIsOutOfRange() { + String sourceStop = "A"; + String targetStop = "B"; + + assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStop, targetStop, -1), + "Departure time cannot be negative"); + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStop, targetStop, 49 * RaptorTestBuilder.SECONDS_IN_HOUR), + "Departure time cannot be greater than two days"); + } + + @Test + void shouldThrowErrorWhenRequestBetweenSameStop() { + String sourceStop = "A"; + String targetStop = "A"; + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + assertThrows(IllegalArgumentException.class, + () -> raptor.routeEarliestArrival(sourceStop, targetStop, departureTime), + "Stops cannot be the same"); } } diff --git a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java index cf99aa68..549f81f8 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java @@ -110,7 +110,11 @@ private static Raptor build(List routes, List transfers, int da } public RaptorTestBuilder withAddRoute1_AG() { - routes.add(new Route("R1", List.of("A", "B", "C", "D", "E", "F", "G"))); + return withAddRoute1_AG(0, 15, 5, 1); + } + + public RaptorTestBuilder withAddRoute1_AG(int offset, int headway, int travelTime, int dwellTime) { + routes.add(new Route("R1", List.of("A", "B", "C", "D", "E", "F", "G"), offset, headway, travelTime, dwellTime)); return this; } From 228ba606315126984679b73466af5e50df1b3a3b Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Fri, 7 Jun 2024 11:50:49 +0200 Subject: [PATCH 11/28] TEST: NAV-49 - Add tests for overlapping routes. --- .../java/ch/naviqore/raptor/RaptorTest.java | 44 +++++++++++++++++++ .../ch/naviqore/raptor/RaptorTestBuilder.java | 7 ++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index d5ca14c1..d527b9a6 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -171,6 +171,50 @@ void shouldFindConnectionBetweenOnlyFootpath(RaptorTestBuilder builder) { Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 1, 0); } + @Test + void shouldTakeFasterRouteOfOverlappingRoutes(RaptorTestBuilder builder) { + // Create Two Versions of the same route with different travel speeds (both leaving at same time from A) + Raptor raptor = builder.withAddRoute1_AG().withAddRoute1_AG("R1X", 0, 15, 3, 1).build(); + String sourceStop = "A"; + String targetStop = "G"; + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + + // Both Routes leave at 8:00 at Stop A, but R1 arrives at G at 8:35 whereas R1X arrives at G at 8:23 + // R1X should be taken + assertEquals(1, connections.size()); + Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 0, 1); + // check departure at 8:00 + Connection connection = connections.getFirst(); + assertEquals(departureTime, connection.getDepartureTime()); + // check arrival time at 8:23 + assertEquals(departureTime + 23 * 60, connection.getArrivalTime()); + // check that R1X(-F for forward) route was used + assertEquals("R1X-F", connection.getRouteLegs().getFirst().routeId()); + } + + @Test + void shouldTakeSlowerRouteOfOverlappingRoutesDueToEarlierDepartureTime(RaptorTestBuilder builder) { + // Create Two Versions of the same route with different travel speeds and different departure times + Raptor raptor = builder.withAddRoute1_AG().withAddRoute1_AG("R1X", 15, 30, 3, 1).build(); + String sourceStop = "A"; + String targetStop = "G"; + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + + // Route R1 leaves at 8:00 at Stop A and arrives at G at 8:35 whereas R1X leaves at 8:15 from Stop A and + // arrives at G at 8:38. R1 should be used. + assertEquals(1, connections.size()); + Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 0, 1); + // check departure at 8:00 + Connection connection = connections.getFirst(); + assertEquals(departureTime, connection.getDepartureTime()); + // check arrival time at 8:35 + assertEquals(departureTime + 35 * 60, connection.getArrivalTime()); + // check that R1(-F for forward) route was used + assertEquals("R1-F", connection.getRouteLegs().getFirst().routeId()); + } + private static class Helpers { private static void assertConnection(Connection connection, String sourceStop, String targetStop, diff --git a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java index 549f81f8..2254e924 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java @@ -114,7 +114,12 @@ public RaptorTestBuilder withAddRoute1_AG() { } public RaptorTestBuilder withAddRoute1_AG(int offset, int headway, int travelTime, int dwellTime) { - routes.add(new Route("R1", List.of("A", "B", "C", "D", "E", "F", "G"), offset, headway, travelTime, dwellTime)); + return withAddRoute1_AG("R1", offset, headway, travelTime, dwellTime); + } + + public RaptorTestBuilder withAddRoute1_AG(String routeId, int offset, int headway, int travelTime, int dwellTime) { + routes.add( + new Route(routeId, List.of("A", "B", "C", "D", "E", "F", "G"), offset, headway, travelTime, dwellTime)); return this; } From 737a35fbf627392191c5bb3477309619e2016887 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Fri, 7 Jun 2024 12:42:18 +0200 Subject: [PATCH 12/28] ENH: NAV-49 - Add some tests for IsoLines --- .../java/ch/naviqore/raptor/RaptorTest.java | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index d527b9a6..69f126b2 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -302,6 +302,120 @@ void shouldCatchConnectingTripBecauseWithSameStationTransferTime(RaptorTestBuild } + @Nested + class IsoLines { + + @Test + void shouldCreateIsoLinesToAllStops(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + + String sourceStop = "A"; + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map isoLines = raptor.getIsoLines(Map.of(sourceStop, departureTime)); + + int stopsInSystem = 19; + int expectedIsoLines = stopsInSystem - 1; + Helpers.assertIsoLines(isoLines, sourceStop, departureTime, expectedIsoLines); + } + + @Test + void shouldCreateIsoLinesToSomeStopsNotAllConnected(RaptorTestBuilder builder) { + // Route 1 and 3 are not connected, thus all Stops of Route 3 should not be reachable from A + Raptor raptor = builder.withAddRoute1_AG().withAddRoute3_MQ().build(); + + String sourceStop = "A"; + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map isoLines = raptor.getIsoLines(Map.of(sourceStop, departureTime)); + + List reachableStops = List.of("B", "C", "D", "E", "F", "G"); + // Not Reachable Stops: M, K, N, O, P, Q + + Helpers.assertIsoLines(isoLines, sourceStop, departureTime, reachableStops.size()); + + for (String stop : reachableStops) { + assertTrue(isoLines.containsKey(stop), "Stop " + stop + " should be reachable"); + } + } + + @Test + void shouldCreateIsoLinesToStopsOfOtherLineOnlyConnectedByFootpath(RaptorTestBuilder builder) { + // Route 1 and Route 3 are only connected by Footpath between Stops D and N + Raptor raptor = builder.withAddRoute1_AG().withAddRoute3_MQ().withAddTransfer1_ND().build(); + + String sourceStop = "A"; + int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + Map isoLines = raptor.getIsoLines(Map.of(sourceStop, departureTime)); + + List reachableStops = List.of("B", "C", "D", "E", "F", "G", "M", "K", "N", "O", "P", "Q"); + + Helpers.assertIsoLines(isoLines, sourceStop, departureTime, reachableStops.size()); + + for (String stop : reachableStops) { + assertTrue(isoLines.containsKey(stop), "Stop " + stop + " should be reachable"); + } + } + + @Test + void shouldCreateIsoLinesFromTwoNotConnectedSourceStops(RaptorTestBuilder builder) { + Raptor raptor = builder.withAddRoute1_AG().withAddRoute3_MQ().build(); + + Map departureTimeHours = Map.of("A", 8, "M", 16); + + List reachableStopsFromStopA = List.of("B", "C", "D", "E", "F", "G"); + Map sourceStops = Map.of("A", departureTimeHours.get("A") * RaptorTestBuilder.SECONDS_IN_HOUR, + "M", departureTimeHours.get("M") * RaptorTestBuilder.SECONDS_IN_HOUR); + List reachableStopsFromStopM = List.of("K", "N", "O", "P", "Q"); + + Map isoLines = raptor.getIsoLines(sourceStops); + + assertEquals(reachableStopsFromStopA.size() + reachableStopsFromStopM.size(), isoLines.size()); + + Map> sourceTargets = Map.of("A", reachableStopsFromStopA, "M", + reachableStopsFromStopM); + + for (Map.Entry> entry : sourceTargets.entrySet()) { + String sourceStop = entry.getKey(); + List reachableStops = entry.getValue(); + int departureTimeHour = departureTimeHours.get(sourceStop); + int departureTime = departureTimeHour * RaptorTestBuilder.SECONDS_IN_HOUR; + for (String stop : reachableStops) { + assertTrue(isoLines.containsKey(stop), "Stop " + stop + " should be reachable from " + sourceStop); + Connection connection = isoLines.get(stop); + assertTrue(connection.getDepartureTime() >= departureTime, + String.format("Connection should have departure time equal or after %d:00", + departureTimeHour)); + assertTrue(connection.getArrivalTime() < Integer.MAX_VALUE, + "Connection should have arrival time before infinity"); + assertEquals(connection.getFromStopId(), sourceStop, "From stop should be " + sourceStop); + assertEquals(connection.getToStopId(), stop, "To stop should be " + stop); + } + } + } + + private static class Helpers { + + private static final int INFINITY = Integer.MAX_VALUE; + + private static void assertIsoLines(Map isoLines, String sourceStopId, int departureTime, + int expectedIsoLines) { + assertEquals(expectedIsoLines, isoLines.size()); + assertFalse(isoLines.containsKey(sourceStopId), "Source stop should not be in iso lines"); + for (Map.Entry entry : isoLines.entrySet()) { + assertTrue(departureTime <= entry.getValue().getDepartureTime(), + "Departure time should be greater than or equal to departure time"); + assertTrue(departureTime < entry.getValue().getArrivalTime(), + "Arrival time should be greater than or equal to departure time"); + assertTrue(entry.getValue().getArrivalTime() < INFINITY, + "Arrival time should be less than INFINITY"); + assertEquals(sourceStopId, entry.getValue().getFromStopId(), "From stop should be source stop"); + assertEquals(entry.getKey(), entry.getValue().getToStopId(), "To stop should be key of map entry"); + } + } + + } + + } + @Nested class InputValidation { From 81c0cc7152995aed0c16e4316d392d9269d3ec87 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Fri, 7 Jun 2024 17:19:58 +0200 Subject: [PATCH 13/28] ENH: NAV-40 - Read same station transfer times into raptor. --- .../ch/naviqore/raptor/RaptorBuilder.java | 24 +++++++++++++++---- src/main/java/ch/naviqore/raptor/Stop.java | 2 +- .../impl/convert/GtfsToRaptorConverter.java | 10 +------- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/RaptorBuilder.java b/src/main/java/ch/naviqore/raptor/RaptorBuilder.java index 5695c861..9d702229 100644 --- a/src/main/java/ch/naviqore/raptor/RaptorBuilder.java +++ b/src/main/java/ch/naviqore/raptor/RaptorBuilder.java @@ -1,7 +1,5 @@ package ch.naviqore.raptor; -import lombok.AccessLevel; -import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; import org.jetbrains.annotations.NotNull; @@ -20,19 +18,30 @@ * * @author munterfi */ -@NoArgsConstructor(access = AccessLevel.PACKAGE) @Log4j2 public class RaptorBuilder { + private final static int DEFAULT_SAME_STATION_TRANSFER_TIME = 120; + + private final int defaultSameStationTransferTime; private final Map stops = new HashMap<>(); private final Map routeBuilders = new HashMap<>(); private final Map> transfers = new HashMap<>(); + private final Map sameStationTransfers = new HashMap<>(); private final Map> stopRoutes = new HashMap<>(); int stopTimeSize = 0; int routeStopSize = 0; int transferSize = 0; + RaptorBuilder() { + this(DEFAULT_SAME_STATION_TRANSFER_TIME); + } + + RaptorBuilder(int defaultSameStationTransferTime) { + this.defaultSameStationTransferTime = defaultSameStationTransferTime; + } + public RaptorBuilder addStop(String id) { if (stops.containsKey(id)) { throw new IllegalArgumentException("Stop " + id + " already exists"); @@ -90,6 +99,11 @@ public RaptorBuilder addTransfer(String sourceStopId, String targetStopId, int d throw new IllegalArgumentException("Target stop " + targetStopId + " does not exist"); } + if (sourceStopId.equals(targetStopId)) { + sameStationTransfers.put(sourceStopId, duration); + return this; + } + transfers.computeIfAbsent(sourceStopId, k -> new ArrayList<>()) .add(new Transfer(stops.get(targetStopId), duration)); transferSize++; @@ -152,8 +166,10 @@ private StopContext buildStopContext(Lookup lookup) { List currentTransfers = transfers.get(stopId); int numberOfTransfers = currentTransfers == null ? 0 : currentTransfers.size(); + int sameStopTransferTime = sameStationTransfers.getOrDefault(stopId, defaultSameStationTransferTime); + // add stop entry to stop array - stopArr[stopIdx] = new Stop(stopId, stopRouteIdx, currentStopRoutes.size(), + stopArr[stopIdx] = new Stop(stopId, stopRouteIdx, currentStopRoutes.size(), sameStopTransferTime, numberOfTransfers == 0 ? Raptor.NO_INDEX : transferIdx, numberOfTransfers); // add transfer entry to transfer array if there are any diff --git a/src/main/java/ch/naviqore/raptor/Stop.java b/src/main/java/ch/naviqore/raptor/Stop.java index c64dfd3d..cf7ec02b 100644 --- a/src/main/java/ch/naviqore/raptor/Stop.java +++ b/src/main/java/ch/naviqore/raptor/Stop.java @@ -1,4 +1,4 @@ package ch.naviqore.raptor; -record Stop(String id, int stopRouteIdx, int numberOfRoutes, int transferIdx, int numberOfTransfers) { +record Stop(String id, int stopRouteIdx, int numberOfRoutes, int sameStationTransferTime, int transferIdx, int numberOfTransfers) { } diff --git a/src/main/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverter.java b/src/main/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverter.java index 943c1870..426af015 100644 --- a/src/main/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverter.java +++ b/src/main/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverter.java @@ -84,7 +84,7 @@ private void addTransfers() { for (String stopId : addedStops) { Stop stop = schedule.getStops().get(stopId); for (Transfer transfer : stop.getTransfers()) { - if (transfer.getTransferType() == TransferType.MINIMUM_TIME && stop != transfer.getToStop() && transfer.getMinTransferTime() + if (transfer.getTransferType() == TransferType.MINIMUM_TIME && transfer.getMinTransferTime() .isPresent()) { try { builder.addTransfer(stop.getId(), transfer.getToStop().getId(), @@ -102,14 +102,6 @@ private void addTransfers() { for (TransferGenerator.Transfer transfer : additionalTransfers) { - if (transfer.from() == transfer.to()) { - // TODO: Make Raptor handle same station transfers correctly. This is a workaround to avoid adding - // transfers between the same station, as not implemented yet. - log.warn("Omit adding transfer from {} 2to {} with duration {} as it is the same stop", - transfer.from().getId(), transfer.to().getId(), transfer.duration()); - continue; - } - if (schedule.getStops() .get(transfer.from().getId()) .getTransfers() From 6741f54977cb62f18bb04733e6cdf6be506e9f76 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Fri, 7 Jun 2024 18:07:41 +0200 Subject: [PATCH 14/28] ENH: NAV-40 - Load Same Stop Transfer Time from Stop object in raptor. --- src/main/java/ch/naviqore/raptor/Raptor.java | 85 +++++++++----------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index e12855e0..722094e0 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -13,11 +13,9 @@ public class Raptor { public final static int INFINITY = Integer.MAX_VALUE; public final static int NO_INDEX = -1; - public final static int SAME_STOP_TRANSFER_TIME = 120; private final InputValidator validator = new InputValidator(); // lookup private final Map stopsToIdx; - private final Map routesToIdx; // stop context private final Transfer[] transfers; private final Stop[] stops; @@ -29,7 +27,6 @@ public class Raptor { Raptor(Lookup lookup, StopContext stopContext, RouteTraversal routeTraversal) { this.stopsToIdx = lookup.stops(); - this.routesToIdx = lookup.routes(); this.transfers = stopContext.transfers(); this.stops = stopContext.stops(); this.stopRoutes = stopContext.stopRoutes(); @@ -158,16 +155,14 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId Set markedStops = new HashSet<>(); for (int i = 0; i < sourceStopIdxs.length; i++) { - // subtract same stop transfer time, as this will be added by default before scanning routes - earliestArrivals[sourceStopIdxs[i]] = departureTimes[i] - SAME_STOP_TRANSFER_TIME; + earliestArrivals[sourceStopIdxs[i]] = departureTimes[i]; earliestArrivalsPerRound.getFirst()[sourceStopIdxs[i]] = new Leg(0, departureTimes[i], ArrivalType.INITIAL, NO_INDEX, NO_INDEX, sourceStopIdxs[i], null); markedStops.add(sourceStopIdxs[i]); } - for (int i = 0; i < sourceStopIdxs.length; i++) { - expandFootpathsForSourceStop(earliestArrivals, earliestArrivalsPerRound, markedStops, sourceStopIdxs[i], - departureTimes[i]); + for (int sourceStopIdx : sourceStopIdxs) { + expandFootpathsFromStop(sourceStopIdx, earliestArrivals, earliestArrivalsPerRound, markedStops, 0); } int earliestArrival = getEarliestArrivalTime(targetStops, earliestArrivals); @@ -283,8 +278,8 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId enteredAtArrival = earliestArrivalsLastRound[stopIdx]; int earliestDepartureTime = enteredAtArrival.arrivalTime; - if( enteredAtArrival.type != ArrivalType.INITIAL ) { - earliestDepartureTime += SAME_STOP_TRANSFER_TIME; + if (enteredAtArrival.type == ArrivalType.ROUTE) { + earliestDepartureTime += stop.sameStationTransferTime(); } while (tripOffset < numberOfTrips) { @@ -310,26 +305,7 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId // temp variable to add any new stops to markedStopsNext Set newStops = new HashSet<>(); for (int stopIdx : markedStopsNext) { - Stop currentStop = stops[stopIdx]; - if (currentStop.numberOfTransfers() == 0) { - continue; - } - for (int i = currentStop.transferIdx(); i < currentStop.numberOfTransfers(); i++) { - Transfer transfer = transfers[i]; - // TODO: Handle variable SAME_STOP_TRANSFER_TIMEs - int newTargetStopArrivalTime = earliestArrivals[stopIdx] + transfer.duration() - SAME_STOP_TRANSFER_TIME; - - // update improved arrival time - if (earliestArrivals[transfer.targetStopIdx()] > newTargetStopArrivalTime) { - log.debug("Stop {} was improved by transfer from stop {}", stops[transfer.targetStopIdx()].id(), - stops[stopIdx].id()); - earliestArrivals[transfer.targetStopIdx()] = newTargetStopArrivalTime; - earliestArrivalsThisRound[transfer.targetStopIdx()] = new Leg(earliestArrivals[stopIdx], - newTargetStopArrivalTime, ArrivalType.TRANSFER, i, NO_INDEX, transfer.targetStopIdx(), - earliestArrivalsThisRound[stopIdx]); - newStops.add(transfer.targetStopIdx()); - } - } + expandFootpathsFromStop(stopIdx, earliestArrivals, earliestArrivalsPerRound, newStops, round); } markedStopsNext.addAll(newStops); @@ -349,7 +325,7 @@ private int getEarliestArrivalTime(int[] targetStops, int[] earliestArrivals) { int earliestArrivalAtTarget = earliestArrivals[targetStopIdx]; // To Prevent Adding a number to Max Integer Value (resulting in a very small negative number) - if( earliestArrivalAtTarget == INFINITY ) { + if (earliestArrivalAtTarget == INFINITY) { continue; } @@ -412,9 +388,6 @@ private List reconstructParetoOptimalSolutions(List earliestA } else if (leg.type == ArrivalType.TRANSFER) { routeId = String.format("transfer_%s_%s", fromStopId, toStopId); type = Connection.LegType.WALK_TRANSFER; - // include same stop transfer time (which is subtracted before scanning routes) - arrivalTime += SAME_STOP_TRANSFER_TIME; - } else { throw new IllegalStateException("Unknown arrival type"); } @@ -433,26 +406,46 @@ private List reconstructParetoOptimalSolutions(List earliestA } } - private void expandFootpathsForSourceStop(int[] earliestArrivals, List earliestArrivalsPerRound, - Set markedStops, int sourceStopIdx, int departureTime) { + /** + * Expands all transfers between stops from a given stop. If a transfer improves the arrival time at the target + * stop, then the target stop is marked for the next round. And the improved arrival time is stored in the + * earliestArrivals array and the earliestArrivalsPerRound list (including the new Transfer Leg). + * + * @param stopIdx - The index of the stop to expand transfers from. + * @param earliestArrivals - A array with the overall best arrival time for each stop, indexed by stop + * index. Note: The arrival time is reduced by the same station transfer time for + * transfers, to make them comparable with route arrivals. + * @param earliestArrivalsPerRound - A list of arrays with the best arrival time for each stop per round, indexed by + * round. + * @param markedStops - A set of stop indices that have been marked for scanning in the next round. + * @param round - The current round to relax footpaths for. + */ + private void expandFootpathsFromStop(int stopIdx, int[] earliestArrivals, List earliestArrivalsPerRound, + Set markedStops, int round) { // if stop has no transfers, then no footpaths can be expanded - if (stops[sourceStopIdx].numberOfTransfers() == 0) { + if (stops[stopIdx].numberOfTransfers() == 0) { return; } + Stop sourceStop = stops[stopIdx]; + int arrivalTime = earliestArrivals[stopIdx]; - // mark all transfer stops, no checks needed for since all transfers will improve arrival time and can be - // marked - Stop sourceStop = stops[sourceStopIdx]; for (int i = sourceStop.transferIdx(); i < sourceStop.transferIdx() + sourceStop.numberOfTransfers(); i++) { Transfer transfer = transfers[i]; - int newTargetStopArrivalTime = departureTime + transfer.duration() - SAME_STOP_TRANSFER_TIME; - if (earliestArrivals[transfer.targetStopIdx()] <= newTargetStopArrivalTime) { + Stop targetStop = stops[transfer.targetStopIdx()]; + int newTargetStopArrivalTime = arrivalTime + transfer.duration(); + + // For Comparison with Route Arrivals the Arrival Time by Transfer must be reduced by the same stop transfer time + int comparableNewTargetStopArrivalTime = newTargetStopArrivalTime - targetStop.sameStationTransferTime(); + if (earliestArrivals[transfer.targetStopIdx()] <= comparableNewTargetStopArrivalTime) { continue; } - earliestArrivals[transfer.targetStopIdx()] = newTargetStopArrivalTime; - earliestArrivalsPerRound.getFirst()[transfer.targetStopIdx()] = new Leg(departureTime, + + log.debug("Stop {} was improved by transfer from stop {}", targetStop.id(), sourceStop.id()); + + earliestArrivals[transfer.targetStopIdx()] = comparableNewTargetStopArrivalTime; + earliestArrivalsPerRound.get(round)[transfer.targetStopIdx()] = new Leg(arrivalTime, newTargetStopArrivalTime, ArrivalType.TRANSFER, i, NO_INDEX, transfer.targetStopIdx(), - earliestArrivalsPerRound.getFirst()[sourceStopIdx]); + earliestArrivalsPerRound.get(round)[stopIdx]); markedStops.add(transfer.targetStopIdx()); } } @@ -538,7 +531,7 @@ private static void validateWalkingTimeToTarget(int walkingDurationToTarget) { * @return a map of valid stop IDs and their corresponding departure / walk to target times. */ private Map validateStops(Map stops) { - if(stops == null) { + if (stops == null) { throw new IllegalArgumentException("Stops must not be null."); } if (stops.isEmpty()) { From 521e088dd84d066feb840751770cd42fe287150b Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Fri, 7 Jun 2024 18:23:14 +0200 Subject: [PATCH 15/28] ENH: NAV-40 - Allow getting raptor builder with custom same station transfer time. --- src/main/java/ch/naviqore/raptor/Raptor.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index 722094e0..745a0afe 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -39,6 +39,10 @@ public static RaptorBuilder builder() { return new RaptorBuilder(); } + public static RaptorBuilder builder(int sameStationTransferTime) { + return new RaptorBuilder(sameStationTransferTime); + } + public List routeEarliestArrival(String sourceStopId, String targetStopId, int departureTime) { return routeEarliestArrival(createStopMap(sourceStopId, departureTime), createStopMap(targetStopId, 0)); } From 66347c8914e0a896ad594e095bc86b6d386e8a1f Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Fri, 7 Jun 2024 18:25:19 +0200 Subject: [PATCH 16/28] TEST: NAV-40 - Add tests for same station transfer times in raptor. --- src/main/java/ch/naviqore/raptor/Stop.java | 3 +- .../java/ch/naviqore/raptor/RaptorTest.java | 37 +++++++++++++++---- .../ch/naviqore/raptor/RaptorTestBuilder.java | 14 +++++-- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/Stop.java b/src/main/java/ch/naviqore/raptor/Stop.java index cf7ec02b..938576d4 100644 --- a/src/main/java/ch/naviqore/raptor/Stop.java +++ b/src/main/java/ch/naviqore/raptor/Stop.java @@ -1,4 +1,5 @@ package ch.naviqore.raptor; -record Stop(String id, int stopRouteIdx, int numberOfRoutes, int sameStationTransferTime, int transferIdx, int numberOfTransfers) { +record Stop(String id, int stopRouteIdx, int numberOfRoutes, int sameStationTransferTime, int transferIdx, + int numberOfTransfers) { } diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index 69f126b2..a17e2d6a 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -268,8 +268,10 @@ void shouldTakeFirstTripWithoutAddingSameStationTransferTime(RaptorTestBuilder b @Test void shouldMissConnectingTripBecauseOfSameStationTransferTime(RaptorTestBuilder builder) { - Raptor raptor = builder.withAddRoute1_AG(19, 15, 5, 1).withAddRoute2_HL().build(); - // TODO: Adjust when same station transfers are stop specific + Raptor raptor = builder.withAddRoute1_AG(19, 15, 5, 1) + .withAddRoute2_HL() + .withSameStationTransferTime(120) + .build(); // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am // Connection at 5:24 from B to C should be missed because of the same station transfer time (120s) String sourceStop = "A"; @@ -284,9 +286,29 @@ void shouldMissConnectingTripBecauseOfSameStationTransferTime(RaptorTestBuilder } @Test - void shouldCatchConnectingTripBecauseWithSameStationTransferTime(RaptorTestBuilder builder) { - Raptor raptor = builder.withAddRoute1_AG(17, 15, 5, 1).withAddRoute2_HL().build(); - // TODO: Adjust when same station transfers are stop specific + void shouldCatchConnectingTripBecauseOfNoSameStationTransferTime(RaptorTestBuilder builder) { + Raptor raptor = builder.withAddRoute1_AG(19, 15, 5, 1) + .withAddRoute2_HL() + .withSameStationTransferTime(0) + .build(); + // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am + // Connection at 5:24 from B to C should not be missed because of no same station transfer time + String sourceStop = "A"; + String targetStop = "H"; + int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; + List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + + assertEquals(1, connections.size()); + assertEquals(departureTime + 19 * 60, connections.getFirst().getDepartureTime()); + assertEquals(departureTime + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); + } + + @Test + void shouldCatchConnectingTripWithSameStationTransferTime(RaptorTestBuilder builder) { + Raptor raptor = builder.withAddRoute1_AG(17, 15, 5, 1) + .withAddRoute2_HL() + .withSameStationTransferTime(120) + .build(); // There should be a connection leaving stop A at 5:17 am and arriving at stop B at 5:22 am // Connection at 5:24 from B to C should be cached when the same station transfer time is 120s String sourceStop = "A"; @@ -362,8 +384,9 @@ void shouldCreateIsoLinesFromTwoNotConnectedSourceStops(RaptorTestBuilder builde Map departureTimeHours = Map.of("A", 8, "M", 16); List reachableStopsFromStopA = List.of("B", "C", "D", "E", "F", "G"); - Map sourceStops = Map.of("A", departureTimeHours.get("A") * RaptorTestBuilder.SECONDS_IN_HOUR, - "M", departureTimeHours.get("M") * RaptorTestBuilder.SECONDS_IN_HOUR); + Map sourceStops = Map.of("A", + departureTimeHours.get("A") * RaptorTestBuilder.SECONDS_IN_HOUR, "M", + departureTimeHours.get("M") * RaptorTestBuilder.SECONDS_IN_HOUR); List reachableStopsFromStopM = List.of("K", "N", "O", "P", "Q"); Map isoLines = raptor.getIsoLines(sourceStops); diff --git a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java index 2254e924..581b4290 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java @@ -44,12 +44,13 @@ public class RaptorTestBuilder { static final int SECONDS_IN_HOUR = 3600; private static final int DAY_START_HOUR = 5; private static final int DAY_END_HOUR = 25; - private final List routes = new ArrayList<>(); private final List transfers = new ArrayList<>(); + private int sameStationTransferTime = 120; - private static Raptor build(List routes, List transfers, int dayStart, int dayEnd) { - RaptorBuilder builder = Raptor.builder(); + private static Raptor build(List routes, List transfers, int dayStart, int dayEnd, + int sameStationTransferTime) { + RaptorBuilder builder = Raptor.builder(sameStationTransferTime); Set addedStops = new HashSet<>(); for (Route route : routes) { @@ -157,8 +158,13 @@ public RaptorTestBuilder withAddTransfer2_LR() { return this; } + public RaptorTestBuilder withSameStationTransferTime(int sameStationTransferTime) { + this.sameStationTransferTime = sameStationTransferTime; + return this; + } + public Raptor build() { - return build(routes, transfers, DAY_START_HOUR, DAY_END_HOUR); + return build(routes, transfers, DAY_START_HOUR, DAY_END_HOUR, sameStationTransferTime); } public Raptor buildWithDefaults() { From b9fd2158ee6c8ee531a6a3d2b063f35908b9a024 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sat, 8 Jun 2024 20:36:55 +0200 Subject: [PATCH 17/28] ENH: NAV-59 - Add QueryConfig Class in Raptor package. --- .../java/ch/naviqore/raptor/QueryConfig.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/ch/naviqore/raptor/QueryConfig.java diff --git a/src/main/java/ch/naviqore/raptor/QueryConfig.java b/src/main/java/ch/naviqore/raptor/QueryConfig.java new file mode 100644 index 00000000..a2f97b84 --- /dev/null +++ b/src/main/java/ch/naviqore/raptor/QueryConfig.java @@ -0,0 +1,57 @@ +package ch.naviqore.raptor; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +/** + * Configuration to query connections or iso-lines. Default values are set to infinity or zero, which means that no + * restrictions are set. + */ +@NoArgsConstructor +@Getter +public class QueryConfig { + + private static final int INFINITY = Integer.MAX_VALUE; + + private int maximumWalkingDuration = INFINITY; + private int minimumTransferDuration = 0; + private int maximumTransferNumber = INFINITY; + private int maximumTravelTime = INFINITY; + + public QueryConfig(int maximumWalkingDuration, int minimumTransferDuration, int maximumTransferNumber, + int maximumTravelTime) { + this.setMaximumWalkingDuration(maximumWalkingDuration); + this.setMinimumTransferDuration(minimumTransferDuration); + this.setMaximumTransferNumber(maximumTransferNumber); + this.setMaximumTravelTime(maximumTravelTime); + } + + public void setMaximumWalkingDuration(int maximumWalkingDuration) { + if (maximumWalkingDuration < 0) { + throw new IllegalArgumentException("Maximum walking duration must be greater than or equal to 0."); + } + this.maximumWalkingDuration = maximumWalkingDuration; + } + + public void setMinimumTransferDuration(int minimumTransferDuration) { + if (minimumTransferDuration < 0) { + throw new IllegalArgumentException("Minimum transfer duration must be greater than or equal to 0."); + } + this.minimumTransferDuration = minimumTransferDuration; + } + + public void setMaximumTransferNumber(int maximumTransferNumber) { + if (maximumTransferNumber < 0) { + throw new IllegalArgumentException("Maximum transfer number must be greater than or equal to 0."); + } + this.maximumTransferNumber = maximumTransferNumber; + } + + public void setMaximumTravelTime(int maximumTravelTime) { + if (maximumTravelTime <= 0) { + throw new IllegalArgumentException("Maximum transfer number must be greater than 0."); + } + this.maximumTravelTime = maximumTravelTime; + } + +} From 428c47e6c721ae291fd982abc7b02678075c2803 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sat, 8 Jun 2024 20:45:10 +0200 Subject: [PATCH 18/28] ENH: NAV-59 - Propagate query config down to spawnFromSourceStop. --- src/main/java/ch/naviqore/raptor/Raptor.java | 46 +++++++------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index 745a0afe..c8d8255b 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -47,39 +47,21 @@ public List routeEarliestArrival(String sourceStopId, String targetS return routeEarliestArrival(createStopMap(sourceStopId, departureTime), createStopMap(targetStopId, 0)); } - // TODO: Do we still need this? There are no usages... - public List routeEarliestArrival(Collection sourceStopIds, Collection targetStopIds, - int departureTime) { - Map sourceStops = createStopMap(sourceStopIds, departureTime); - Map targetStops = createStopMap(targetStopIds, 0); - return routeEarliestArrival(sourceStops, targetStops); + public List routeEarliestArrival(String sourceStopId, String targetStopId, int departureTime, + QueryConfig config) { + return routeEarliestArrival(createStopMap(sourceStopId, departureTime), createStopMap(targetStopId, 0), config); } private Map createStopMap(String stopId, int value) { return Map.of(stopId, value); } - private Map createStopMap(Collection stopIds, int value) { - Map stopMap = new HashMap<>(); - for (String stopId : stopIds) { - stopMap.put(stopId, value); - } - return stopMap; - } - - // TODO: Do we still need this? There are no usages... - private Map createStopMap(List stopIds, List values) { - if (stopIds.size() != values.size()) { - throw new IllegalArgumentException("Stop IDs and values must have the same size."); - } - Map stopMap = new HashMap<>(); - for (int i = 0; i < stopIds.size(); i++) { - stopMap.put(stopIds.get(i), values.get(i)); - } - return stopMap; + public List routeEarliestArrival(Map sourceStops, Map targetStopIds) { + return routeEarliestArrival(sourceStops, targetStopIds, new QueryConfig()); } - public List routeEarliestArrival(Map sourceStops, Map targetStopIds) { + public List routeEarliestArrival(Map sourceStops, Map targetStopIds, + QueryConfig config) { Map validatedSourceStopIdx = validator.validateStops(sourceStops); Map validatedTargetStopIdx = validator.validateStops(targetStopIds); InputValidator.validateStopPermutations(sourceStops, targetStopIds); @@ -90,17 +72,21 @@ public List routeEarliestArrival(Map sourceStops, M log.info("Routing earliest arrival from {} to {} at {}", sourceStopIdxs, targetStopIdxs, departureTimes); List earliestArrivalsPerRound = spawnFromSourceStop(sourceStopIdxs, targetStopIdxs, departureTimes, - walkingDurationsToTarget); + walkingDurationsToTarget, config); // get pareto-optimal solutions return reconstructParetoOptimalSolutions(earliestArrivalsPerRound, targetStopIdxs); } public Map getIsoLines(Map sourceStops) { + return getIsoLines(sourceStops, new QueryConfig()); + } + + public Map getIsoLines(Map sourceStops, QueryConfig config) { Map validatedSourceStopIdx = validator.validateStops(sourceStops); int[] sourceStopIdxs = validatedSourceStopIdx.keySet().stream().mapToInt(Integer::intValue).toArray(); int[] departureTimes = validatedSourceStopIdx.values().stream().mapToInt(Integer::intValue).toArray(); - List earliestArrivalsPerRound = spawnFromSourceStop(sourceStopIdxs, departureTimes); + List earliestArrivalsPerRound = spawnFromSourceStop(sourceStopIdxs, departureTimes, config); Map isoLines = new HashMap<>(); for (int i = 0; i < stops.length; i++) { @@ -128,13 +114,13 @@ public Map getIsoLines(Map sourceStops) { } // this implementation will spawn from source stop until all stops are reached with all pareto optimal connections - private List spawnFromSourceStop(int[] sourceStopIdx, int[] departureTime) { - return spawnFromSourceStop(sourceStopIdx, new int[]{}, departureTime, new int[]{}); + private List spawnFromSourceStop(int[] sourceStopIdx, int[] departureTime, QueryConfig config) { + return spawnFromSourceStop(sourceStopIdx, new int[]{}, departureTime, new int[]{}, config); } // if targetStopIdx is not empty, then the search will stop when target stop cannot be pareto optimized private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopIdxs, int[] departureTimes, - int[] walkingDurationsToTarget) { + int[] walkingDurationsToTarget, QueryConfig config) { // initialization final int[] earliestArrivals = new int[stops.length]; Arrays.fill(earliestArrivals, INFINITY); From 4a6bcfdfe2aacd8e72ac5f92966c209228705391 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sat, 8 Jun 2024 20:58:44 +0200 Subject: [PATCH 19/28] ENH: NAV-59 - Include maximum travel time to raptor from query config. --- src/main/java/ch/naviqore/raptor/Raptor.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index c8d8255b..80804547 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -133,6 +133,10 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId throw new IllegalArgumentException("Target stops and walking durations to target must have the same size."); } + // This is used to determine the criteria for maximum travel time + int earliestDeparture = Arrays.stream(departureTimes).min().orElseThrow(); + int latestAcceptedArrival = config.getMaximumTravelTime() == INFINITY ? INFINITY : earliestDeparture + config.getMaximumTravelTime(); + int[] targetStops = new int[targetStopIdxs.length * 2]; for (int i = 0; i < targetStops.length; i += 2) { int index = (int) Math.ceil(i / 2.0); @@ -154,7 +158,7 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId for (int sourceStopIdx : sourceStopIdxs) { expandFootpathsFromStop(sourceStopIdx, earliestArrivals, earliestArrivalsPerRound, markedStops, 0); } - int earliestArrival = getEarliestArrivalTime(targetStops, earliestArrivals); + int earliestArrival = getEarliestArrivalTime(targetStops, earliestArrivals, latestAcceptedArrival); // continue with further rounds as long as there are new marked stops int round = 1; @@ -243,7 +247,8 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId markedStopsNext.add(stopIdx); // check if this was a target stop if (Arrays.stream(targetStopIdxs).anyMatch(targetStopIdx -> targetStopIdx == stopIdx)) { - earliestArrival = getEarliestArrivalTime(targetStops, earliestArrivals); + earliestArrival = getEarliestArrivalTime(targetStops, earliestArrivals, + latestAcceptedArrival); log.debug("Earliest arrival to a target stop improved to {}", earliestArrival); } @@ -307,8 +312,8 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId return earliestArrivalsPerRound; } - private int getEarliestArrivalTime(int[] targetStops, int[] earliestArrivals) { - int earliestArrival = INFINITY; + private int getEarliestArrivalTime(int[] targetStops, int[] earliestArrivals, int latestAcceptedArrival) { + int earliestArrival = latestAcceptedArrival; for (int i = 0; i < targetStops.length; i += 2) { int targetStopIdx = targetStops[i]; int walkDurationToTarget = targetStops[i + 1]; From 197dc5e0b3d769b1d98f7964d59e72bc695203cc Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sat, 8 Jun 2024 21:02:10 +0200 Subject: [PATCH 20/28] REFACTOR: NAV-59 - Extract getRoutesToScan method. --- src/main/java/ch/naviqore/raptor/Raptor.java | 31 +++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index 80804547..70b20654 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -171,17 +171,7 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId earliestArrivalsPerRound.add(new Leg[stops.length]); Leg[] earliestArrivalsThisRound = earliestArrivalsPerRound.get(round); - // get routes of marked stops - Set routesToScan = new HashSet<>(); - for (int stopIdx : markedStops) { - Stop currentStop = stops[stopIdx]; - int stopRouteIdx = currentStop.stopRouteIdx(); - int stopRouteEndIdx = stopRouteIdx + currentStop.numberOfRoutes(); - while (stopRouteIdx < stopRouteEndIdx) { - routesToScan.add(stopRoutes[stopRouteIdx]); - stopRouteIdx++; - } - } + Set routesToScan = getRoutesToScan(markedStops); log.debug("Routes to scan: {}", routesToScan); // scan routes @@ -312,6 +302,25 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId return earliestArrivalsPerRound; } + /* + * Get all routes to scan from the marked stops. + * + * @param markedStops - The set of marked stops from the previous round. + */ + private Set getRoutesToScan(Set markedStops) { + Set routesToScan = new HashSet<>(); + for (int stopIdx : markedStops) { + Stop currentStop = stops[stopIdx]; + int stopRouteIdx = currentStop.stopRouteIdx(); + int stopRouteEndIdx = stopRouteIdx + currentStop.numberOfRoutes(); + while (stopRouteIdx < stopRouteEndIdx) { + routesToScan.add(stopRoutes[stopRouteIdx]); + stopRouteIdx++; + } + } + return routesToScan; + } + private int getEarliestArrivalTime(int[] targetStops, int[] earliestArrivals, int latestAcceptedArrival) { int earliestArrival = latestAcceptedArrival; for (int i = 0; i < targetStops.length; i += 2) { From 04ef69161c08a779ae9040184e162016ba1f96f1 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sat, 8 Jun 2024 21:05:20 +0200 Subject: [PATCH 21/28] ENH: NAV-59 - Implement maximum transfer number from config. --- src/main/java/ch/naviqore/raptor/Raptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index 70b20654..b259cb92 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -162,7 +162,7 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId // continue with further rounds as long as there are new marked stops int round = 1; - while (!markedStops.isEmpty()) { + while (!markedStops.isEmpty() && (round - 1) <= config.getMaximumTransferNumber()) { log.debug("Scanning routes for round {}", round); Set markedStopsNext = new HashSet<>(); From 04794ed9e568d8c0f53dbd717d2041ebe4968db2 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sat, 8 Jun 2024 21:16:11 +0200 Subject: [PATCH 22/28] ENH: NAV-59 - Add implementations for minTransferTime and maxWalkingTime. --- src/main/java/ch/naviqore/raptor/Raptor.java | 24 ++++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index b259cb92..3fa86b18 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -137,6 +137,9 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId int earliestDeparture = Arrays.stream(departureTimes).min().orElseThrow(); int latestAcceptedArrival = config.getMaximumTravelTime() == INFINITY ? INFINITY : earliestDeparture + config.getMaximumTravelTime(); + int maxWalkingDuration = config.getMaximumWalkingDuration(); + int minTransferDuration = config.getMinimumTransferDuration(); + int[] targetStops = new int[targetStopIdxs.length * 2]; for (int i = 0; i < targetStops.length; i += 2) { int index = (int) Math.ceil(i / 2.0); @@ -156,7 +159,8 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId } for (int sourceStopIdx : sourceStopIdxs) { - expandFootpathsFromStop(sourceStopIdx, earliestArrivals, earliestArrivalsPerRound, markedStops, 0); + expandFootpathsFromStop(sourceStopIdx, earliestArrivals, earliestArrivalsPerRound, markedStops, 0, + maxWalkingDuration, minTransferDuration); } int earliestArrival = getEarliestArrivalTime(targetStops, earliestArrivals, latestAcceptedArrival); @@ -264,7 +268,7 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId int earliestDepartureTime = enteredAtArrival.arrivalTime; if (enteredAtArrival.type == ArrivalType.ROUTE) { - earliestDepartureTime += stop.sameStationTransferTime(); + earliestDepartureTime += Math.max(stop.sameStationTransferTime(), minTransferDuration); } while (tripOffset < numberOfTrips) { @@ -290,7 +294,8 @@ private List spawnFromSourceStop(int[] sourceStopIdxs, int[] targetStopId // temp variable to add any new stops to markedStopsNext Set newStops = new HashSet<>(); for (int stopIdx : markedStopsNext) { - expandFootpathsFromStop(stopIdx, earliestArrivals, earliestArrivalsPerRound, newStops, round); + expandFootpathsFromStop(stopIdx, earliestArrivals, earliestArrivalsPerRound, newStops, round, + maxWalkingDuration, minTransferDuration); } markedStopsNext.addAll(newStops); @@ -423,9 +428,14 @@ private List reconstructParetoOptimalSolutions(List earliestA * round. * @param markedStops - A set of stop indices that have been marked for scanning in the next round. * @param round - The current round to relax footpaths for. + * @param maxWalkingDuration - The maximum walking duration to reach the target stop. If the walking duration + * exceeds this value, the target stop is not reached. + * @param minTransferDuration - The minimum transfer duration time, since this is intended as rest period it is + * added to the walk time. */ private void expandFootpathsFromStop(int stopIdx, int[] earliestArrivals, List earliestArrivalsPerRound, - Set markedStops, int round) { + Set markedStops, int round, int maxWalkingDuration, + int minTransferDuration) { // if stop has no transfers, then no footpaths can be expanded if (stops[stopIdx].numberOfTransfers() == 0) { return; @@ -436,7 +446,11 @@ private void expandFootpathsFromStop(int stopIdx, int[] earliestArrivals, List Date: Sat, 8 Jun 2024 22:42:12 +0200 Subject: [PATCH 23/28] TEST: NAV-59 - Add tests for query config. --- .../java/ch/naviqore/raptor/RaptorTest.java | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index a17e2d6a..dbf31fe3 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -324,6 +324,167 @@ void shouldCatchConnectingTripWithSameStationTransferTime(RaptorTestBuilder buil } + /* + * Tests for the application of the QueryConfig class. + * Note: Since the QueryConfig is used in SpawnFromSourceStop no additional tests for the IsoLines QueryConfig are + * needed. + */ + @Nested + class QueryConfigTest { + + private static final String SOURCE_STOP = "A"; + private static final String TARGET_STOP = "Q"; + private static final int DEPARTURE_TIME = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + + @Test + void shouldFindWalkableTransferWithMaxWalkingTime(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + QueryConfig queryConfig = new QueryConfig(); + queryConfig.setMaximumWalkingDuration(RaptorTestBuilder.SECONDS_IN_HOUR); + + // Should return two pareto optimal connections: + // 1. Connection (with two route legs and one transfer (including footpath) --> slower but fewer transfers) + // - Route R1-F from A to D + // - Foot Transfer from D to N (30 minutes walk time + // - Route R3-F from N to Q + + // 2. Connection (with three route legs and two transfers (same station) --> faster but more transfers) + // - Route R1-F from A to F + // - Route R4-R from F to P + // - Route R3-F from P to Q + List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, + queryConfig); + + // check if 2 connections were found + assertEquals(2, connections.size()); + EarliestArrival.Helpers.assertConnection(connections.getFirst(), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, + 0, 1, 2); + EarliestArrival.Helpers.assertConnection(connections.get(1), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, 2, 0, + 3); + EarliestArrival.Helpers.checkIfConnectionsAreParetoOptimal(connections); + } + + @Test + void shouldNotFindWalkableTransferWithMaxWalkingTime(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + QueryConfig queryConfig = new QueryConfig(); + queryConfig.setMaximumWalkingDuration(RaptorTestBuilder.SECONDS_IN_HOUR / 4); // 15 minutes + + // Should only find three route leg connections, since direct transfer between D and N is longer than + // allowed maximum walking distance (60 minutes): + List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, + queryConfig); + assertEquals(1, connections.size()); + EarliestArrival.Helpers.assertConnection(connections.getFirst(), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, + 2, 0, 3); + } + + @Test + void shouldFindConnectionWithMaxTransferNumber(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + QueryConfig queryConfig = new QueryConfig(); + queryConfig.setMaximumTransferNumber(1); + + // Should only find the connection with the fewest transfers: + // 1. Connection (with two route legs and one transfer (including footpath) --> slower but fewer transfers) + // - Route R1-F from A to D + // - Foot Transfer from D to N + // - Route R3-F from N to Q + // 2. Connection with two transfers (see above) should not be found + List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, + queryConfig); + assertEquals(1, connections.size()); + EarliestArrival.Helpers.assertConnection(connections.getFirst(), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, + 0, 1, 2); + } + + @Test + void shouldFindConnectionWithMaxTravelTime(RaptorTestBuilder builder) { + Raptor raptor = builder.buildWithDefaults(); + QueryConfig queryConfig = new QueryConfig(); + queryConfig.setMaximumTravelTime(RaptorTestBuilder.SECONDS_IN_HOUR); + + // Should only find the quicker connection (more transfers): + // - Route R1-F from A to F + // - Route R4-R from F to P + // - Route R3-F from P to Q + List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, + queryConfig); + assertEquals(1, connections.size()); + EarliestArrival.Helpers.assertConnection(connections.getFirst(), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, + 2, 0, 3); + } + + @Test + void shouldUseSameStationTransferTimeWithZeroMinimumTransferDuration(RaptorTestBuilder builder) { + QueryConfig queryConfig = new QueryConfig(); + queryConfig.setMinimumTransferDuration(0); + + Raptor raptor = builder.withAddRoute1_AG(19, 15, 5, 1) + .withAddRoute2_HL() + .withSameStationTransferTime(120) + .build(); + // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am. Connection + // at 5:24 (next 5:39) from B to C should be missed because of the same station transfer time (120s), + // regardless of minimum same transfer duration at 0s + String sourceStop = "A"; + String targetStop = "H"; + int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; + + List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime, + queryConfig); + + assertEquals(1, connections.size()); + assertEquals(departureTime + 19 * 60, connections.getFirst().getDepartureTime()); + assertEquals(departureTime + 39 * 60, connections.getFirst().getLegs().get(1).departureTime()); + } + + @Test + void shouldUseMinimumTransferTime(RaptorTestBuilder builder) { + QueryConfig queryConfig = new QueryConfig(); + queryConfig.setMinimumTransferDuration(20 * 60); // 20 minutes + + Raptor raptor = builder.withAddRoute1_AG(19, 15, 5, 1) + .withAddRoute2_HL() + .withSameStationTransferTime(120) + .build(); + // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am. Connection + // at 5:24 and 5:39 from B to C should be missed because of the minimum transfer duration (20 minutes) + String sourceStop = "A"; + String targetStop = "H"; + int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; + + List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime, + queryConfig); + + assertEquals(1, connections.size()); + assertEquals(departureTime + 19 * 60, connections.getFirst().getDepartureTime()); + assertEquals(departureTime + 54 * 60, connections.getFirst().getLegs().get(1).departureTime()); + } + + @Test + void shouldAddMinimumTransferTimeToWalkTransferDuration(RaptorTestBuilder builder) { + QueryConfig queryConfig = new QueryConfig(); + queryConfig.setMinimumTransferDuration(20 * 60); // 20 minutes + + Raptor raptor = builder.buildWithDefaults(); + List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, + queryConfig); + + assertEquals(2, connections.size()); + Connection firstConnection = connections.getFirst(); + EarliestArrival.Helpers.assertConnection(firstConnection, SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, 0, 1, + 2); + + // The walk transfer from D to N takes 60 minutes and the route from N to Q leaves every 75 minutes. + Connection.Leg firstLeg = firstConnection.getRouteLegs().getFirst(); + Connection.Leg secondLeg = firstConnection.getRouteLegs().getLast(); + int timeDiff = secondLeg.departureTime() - firstLeg.arrivalTime(); + assertTrue(timeDiff >= 75 * 60, "Time between trips should be at least 75 minutes"); + } + + } + @Nested class IsoLines { From a6d6f25acd4ca528e25d61678548d6fd4f94fed0 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sat, 8 Jun 2024 23:20:41 +0200 Subject: [PATCH 24/28] ENH: NAV-59 - Add query config to service and pass down to raptor correctly. --- .../impl/PublicTransitServiceImpl.java | 57 ++++++++++++++----- .../ch/naviqore/service/impl/TypeMapper.java | 7 +++ 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/main/java/ch/naviqore/service/impl/PublicTransitServiceImpl.java b/src/main/java/ch/naviqore/service/impl/PublicTransitServiceImpl.java index 1a46e877..692e777f 100644 --- a/src/main/java/ch/naviqore/service/impl/PublicTransitServiceImpl.java +++ b/src/main/java/ch/naviqore/service/impl/PublicTransitServiceImpl.java @@ -29,6 +29,7 @@ import java.io.IOException; import java.io.UncheckedIOException; +import java.time.Duration; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; @@ -177,7 +178,8 @@ private List getConnections(@Nullable ch.naviqore.gtfs.schedule.mode if (sourceStop != null) { sourceStops = getAllChildStopsFromStop(map(sourceStop), departureTime); } else if (sourceLocation != null) { - sourceStops = getStopsWithWalkTimeFromLocation(sourceLocation, departureTime); + sourceStops = getStopsWithWalkTimeFromLocation(sourceLocation, departureTime, + config.getMaximumWalkingDuration()); } else { throw new IllegalArgumentException("Either sourceStop or sourceLocation must be provided."); } @@ -185,15 +187,21 @@ private List getConnections(@Nullable ch.naviqore.gtfs.schedule.mode if (targetStop != null) { targetStops = getAllChildStopsFromStop(map(targetStop)); } else if (targetLocation != null) { - targetStops = getStopsWithWalkTimeFromLocation(targetLocation); + targetStops = getStopsWithWalkTimeFromLocation(targetLocation, config.getMaximumWalkingDuration()); } else { throw new IllegalArgumentException("Either targetStop or targetLocation must be provided."); } + // In this case either no source stop or target stop is within walkable distance + if (sourceStops.isEmpty() || targetStops.isEmpty()) { + return List.of(); + } + // query connection from raptor Raptor raptor = new GtfsToRaptorConverter(schedule, additionalTransfers).convert(time.toLocalDate()); - List connections = raptor.routeEarliestArrival(sourceStops, targetStops); + List connections = raptor.routeEarliestArrival(sourceStops, targetStops, + map(config)); List result = new ArrayList<>(); @@ -210,17 +218,24 @@ private List getConnections(@Nullable ch.naviqore.gtfs.schedule.mode lastMile = getLastWalk(targetLocation, connection.getToStopId(), arrivalTime, targetStops); } - result.add(map(connection, firstMile, lastMile, time.toLocalDate(), schedule)); + Connection serviceConnection = map(connection, firstMile, lastMile, time.toLocalDate(), schedule); + + // Filter needed because the raptor algorithm does not consider the firstMile and lastMile walk time + if (Duration.between(serviceConnection.getDepartureTime(), serviceConnection.getArrivalTime()) + .getSeconds() <= config.getMaximumTravelTime()) { + result.add(serviceConnection); + } } return result; } - public Map getStopsWithWalkTimeFromLocation(GeoCoordinate location) { - return getStopsWithWalkTimeFromLocation(location, 0); + public Map getStopsWithWalkTimeFromLocation(GeoCoordinate location, int maxWalkDuration) { + return getStopsWithWalkTimeFromLocation(location, 0, maxWalkDuration); } - public Map getStopsWithWalkTimeFromLocation(GeoCoordinate location, int startTimeInSeconds) { + public Map getStopsWithWalkTimeFromLocation(GeoCoordinate location, int startTimeInSeconds, + int maxWalkDuration) { // TODO: Make configurable int maxSearchRadius = 500; List nearestStops = new ArrayList<>( @@ -232,8 +247,10 @@ public Map getStopsWithWalkTimeFromLocation(GeoCoordinate locat Map stopsWithWalkTime = new HashMap<>(); for (ch.naviqore.gtfs.schedule.model.Stop stop : nearestStops) { - stopsWithWalkTime.put(stop.getId(), - startTimeInSeconds + walkCalculator.calculateWalk(location, stop.getCoordinate()).duration()); + int walkDuration = walkCalculator.calculateWalk(location, stop.getCoordinate()).duration(); + if (walkDuration <= maxWalkDuration) { + stopsWithWalkTime.put(stop.getId(), startTimeInSeconds + walkDuration); + } } return stopsWithWalkTime; } @@ -255,12 +272,13 @@ public Map getAllChildStopsFromStop(Stop stop, int startTimeInS public Map getIsolines(GeoCoordinate source, LocalDateTime departureTime, ConnectionQueryConfig config) { Map sourceStops = getStopsWithWalkTimeFromLocation(source, - departureTime.toLocalTime().toSecondOfDay()); + departureTime.toLocalTime().toSecondOfDay(), config.getMaximumWalkingDuration()); // TODO: Not always create a new raptor, use mask on stop times based on active trips Raptor raptor = new GtfsToRaptorConverter(schedule, additionalTransfers).convert(departureTime.toLocalDate()); - return mapToStopConnectionMap(raptor.getIsoLines(sourceStops), sourceStops, source, departureTime); + return mapToStopConnectionMap(raptor.getIsoLines(sourceStops, map(config)), sourceStops, source, departureTime, + config); } @Override @@ -271,12 +289,14 @@ public Map getIsolines(Stop source, LocalDateTime departureTim // TODO: Not always create a new raptor, use mask on stop times based on active trips Raptor raptor = new GtfsToRaptorConverter(schedule, additionalTransfers).convert(departureTime.toLocalDate()); - return mapToStopConnectionMap(raptor.getIsoLines(sourceStops), sourceStops, null, departureTime); + return mapToStopConnectionMap(raptor.getIsoLines(sourceStops, map(config)), sourceStops, null, departureTime, + config); } private Map mapToStopConnectionMap(Map isoLines, Map sourceStops, - @Nullable GeoCoordinate source, LocalDateTime departureTime) { + @Nullable GeoCoordinate source, LocalDateTime departureTime, + ConnectionQueryConfig config) { Map result = new HashMap<>(); for (Map.Entry entry : isoLines.entrySet()) { @@ -287,8 +307,15 @@ private Map mapToStopConnectionMap(Map Date: Sun, 9 Jun 2024 22:50:47 +0200 Subject: [PATCH 25/28] REFACTOR: NAV-40 - Remove default implementation in Raptor Builder for same station transfer times. --- .../app/service/ServiceConfigParser.java | 3 ++- src/main/java/ch/naviqore/raptor/Raptor.java | 4 ---- .../ch/naviqore/raptor/RaptorBuilder.java | 6 ----- .../service/config/ServiceConfig.java | 23 +++++++++++++++++-- .../impl/PublicTransitServiceImpl.java | 3 ++- .../impl/convert/GtfsToRaptorConverter.java | 9 ++++---- src/test/java/ch/naviqore/Benchmark.java | 2 +- .../impl/convert/GtfsToRaptorConverterIT.java | 3 ++- 8 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/main/java/ch/naviqore/app/service/ServiceConfigParser.java b/src/main/java/ch/naviqore/app/service/ServiceConfigParser.java index a093cb24..cb9d9e63 100644 --- a/src/main/java/ch/naviqore/app/service/ServiceConfigParser.java +++ b/src/main/java/ch/naviqore/app/service/ServiceConfigParser.java @@ -13,13 +13,14 @@ public class ServiceConfigParser { public ServiceConfigParser(@Value("${gtfs.static.url}") String gtfsUrl, @Value("${transfer.time.minimum:120}") int minimumTransferTime, + @Value("${transfer.defaultSameStationTransferTime:120}") int sameStationTransferTime, @Value("${walking.distance.maximum:500}") int maxWalkingDistance, @Value("${walking.speed:3500}") int walkingSpeed, @Value("${walking.calculator.type:BEE_LINE_DISTANCE}") String walkCalculatorTypeStr) { ServiceConfig.WalkCalculatorType walkCalculatorType = ServiceConfig.WalkCalculatorType.valueOf( walkCalculatorTypeStr.toUpperCase()); this.serviceConfig = new ServiceConfig(gtfsUrl, minimumTransferTime, maxWalkingDistance, walkingSpeed, - walkCalculatorType); + sameStationTransferTime, walkCalculatorType); } } \ No newline at end of file diff --git a/src/main/java/ch/naviqore/raptor/Raptor.java b/src/main/java/ch/naviqore/raptor/Raptor.java index 3fa86b18..6454af5c 100644 --- a/src/main/java/ch/naviqore/raptor/Raptor.java +++ b/src/main/java/ch/naviqore/raptor/Raptor.java @@ -35,10 +35,6 @@ public class Raptor { this.routeStops = routeTraversal.routeStops(); } - public static RaptorBuilder builder() { - return new RaptorBuilder(); - } - public static RaptorBuilder builder(int sameStationTransferTime) { return new RaptorBuilder(sameStationTransferTime); } diff --git a/src/main/java/ch/naviqore/raptor/RaptorBuilder.java b/src/main/java/ch/naviqore/raptor/RaptorBuilder.java index 9d702229..2d379879 100644 --- a/src/main/java/ch/naviqore/raptor/RaptorBuilder.java +++ b/src/main/java/ch/naviqore/raptor/RaptorBuilder.java @@ -21,8 +21,6 @@ @Log4j2 public class RaptorBuilder { - private final static int DEFAULT_SAME_STATION_TRANSFER_TIME = 120; - private final int defaultSameStationTransferTime; private final Map stops = new HashMap<>(); private final Map routeBuilders = new HashMap<>(); @@ -34,10 +32,6 @@ public class RaptorBuilder { int routeStopSize = 0; int transferSize = 0; - RaptorBuilder() { - this(DEFAULT_SAME_STATION_TRANSFER_TIME); - } - RaptorBuilder(int defaultSameStationTransferTime) { this.defaultSameStationTransferTime = defaultSameStationTransferTime; } diff --git a/src/main/java/ch/naviqore/service/config/ServiceConfig.java b/src/main/java/ch/naviqore/service/config/ServiceConfig.java index f5923eb3..3b12845e 100644 --- a/src/main/java/ch/naviqore/service/config/ServiceConfig.java +++ b/src/main/java/ch/naviqore/service/config/ServiceConfig.java @@ -11,15 +11,34 @@ public class ServiceConfig { private final int minimumTransferTime; private final int maxWalkingDistance; private final int walkingSpeed; + private final int sameStationTransferTime; private final WalkCalculatorType walkCalculatorType; private final String gtfsUrl; public ServiceConfig(String gtfsUrl, int minimumTransferTime, int maxWalkingDistance, int walkingSpeed, - WalkCalculatorType walkCalculatorType) { + int sameStationTransferTime, WalkCalculatorType walkCalculatorType) { this.gtfsUrl = gtfsUrl; + + if (minimumTransferTime < 0) { + throw new IllegalArgumentException("Minimum transfer time must be greater than or equal to 0."); + } this.minimumTransferTime = minimumTransferTime; + + if (maxWalkingDistance < 0) { + throw new IllegalArgumentException("Maximum walking distance must be greater than or equal to 0."); + } this.maxWalkingDistance = maxWalkingDistance; + + if (walkingSpeed <= 0) { + throw new IllegalArgumentException("Walking speed must be greater than to 0."); + } this.walkingSpeed = walkingSpeed; + + if (sameStationTransferTime < 0) { + throw new IllegalArgumentException("Same station transfer time must be greater than or equal to 0."); + } + this.sameStationTransferTime = sameStationTransferTime; + this.walkCalculatorType = walkCalculatorType; } @@ -27,7 +46,7 @@ public ServiceConfig(String gtfsUrl, int minimumTransferTime, int maxWalkingDist * Constructor with defaults */ public ServiceConfig(String gtfsUrl) { - this(gtfsUrl, 120, 500, 3500, WalkCalculatorType.BEE_LINE_DISTANCE); + this(gtfsUrl, 120, 500, 3500, 120, WalkCalculatorType.BEE_LINE_DISTANCE); } public enum WalkCalculatorType { diff --git a/src/main/java/ch/naviqore/service/impl/PublicTransitServiceImpl.java b/src/main/java/ch/naviqore/service/impl/PublicTransitServiceImpl.java index 380b706c..4a130f0f 100644 --- a/src/main/java/ch/naviqore/service/impl/PublicTransitServiceImpl.java +++ b/src/main/java/ch/naviqore/service/impl/PublicTransitServiceImpl.java @@ -493,7 +493,8 @@ private Raptor getRaptor(LocalDate date) { Set activeServices = this.activeServices.computeIfAbsent(date, () -> getActiveServices(date)); return raptorCache.computeIfAbsent(activeServices, - () -> new GtfsToRaptorConverter(schedule, additionalTransfers).convert(date)); + () -> new GtfsToRaptorConverter(schedule, additionalTransfers, + config.getSameStationTransferTime()).convert(date)); } // get all active calendars form the gtfs for given date, serves as key for caching raptor instances diff --git a/src/main/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverter.java b/src/main/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverter.java index 426af015..4644064f 100644 --- a/src/main/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverter.java +++ b/src/main/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverter.java @@ -27,19 +27,20 @@ public class GtfsToRaptorConverter { private final Set addedSubRoutes = new HashSet<>(); private final Set addedStops = new HashSet<>(); - private final RaptorBuilder builder = Raptor.builder(); + private final RaptorBuilder builder; private final GtfsRoutePartitioner partitioner; private final List additionalTransfers; private final GtfsSchedule schedule; - public GtfsToRaptorConverter(GtfsSchedule schedule) { - this(schedule, List.of()); + public GtfsToRaptorConverter(GtfsSchedule schedule, int sameStationTransferTime) { + this(schedule, List.of(), sameStationTransferTime); } - public GtfsToRaptorConverter(GtfsSchedule schedule, List additionalTransfers) { + public GtfsToRaptorConverter(GtfsSchedule schedule, List additionalTransfers, int sameStationTransferTime) { this.partitioner = new GtfsRoutePartitioner(schedule); this.additionalTransfers = additionalTransfers; this.schedule = schedule; + this.builder = Raptor.builder(sameStationTransferTime); } public Raptor convert(LocalDate date) { diff --git a/src/test/java/ch/naviqore/Benchmark.java b/src/test/java/ch/naviqore/Benchmark.java index e889b287..5df6c6fd 100644 --- a/src/test/java/ch/naviqore/Benchmark.java +++ b/src/test/java/ch/naviqore/Benchmark.java @@ -97,7 +97,7 @@ private static Raptor initializeRaptor(GtfsSchedule schedule) throws Interrupted SAME_STATION_TRANSFER_TIME); additionalGeneratedTransfers.addAll(sameStationTransferGenerator.generateTransfers(schedule)); - Raptor raptor = new GtfsToRaptorConverter(schedule, additionalGeneratedTransfers).convert(SCHEDULE_DATE); + Raptor raptor = new GtfsToRaptorConverter(schedule, additionalGeneratedTransfers, SAME_STATION_TRANSFER_TIME).convert(SCHEDULE_DATE); manageResources(); return raptor; } diff --git a/src/test/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverterIT.java b/src/test/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverterIT.java index 9407abbd..49b30a38 100644 --- a/src/test/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverterIT.java +++ b/src/test/java/ch/naviqore/service/impl/convert/GtfsToRaptorConverterIT.java @@ -18,6 +18,7 @@ class GtfsToRaptorConverterIT { private static final LocalDate DATE = LocalDate.of(2009, 4, 26); + private static final int SAME_STATION_TRANSFER_TIME = 120; private GtfsSchedule schedule; @BeforeEach @@ -28,7 +29,7 @@ void setUp(@TempDir Path tempDir) throws IOException { @Test void shouldConvertGtfsScheduleToRaptor() { - GtfsToRaptorConverter mapper = new GtfsToRaptorConverter(schedule); + GtfsToRaptorConverter mapper = new GtfsToRaptorConverter(schedule, SAME_STATION_TRANSFER_TIME); Raptor raptor = mapper.convert(DATE); assertThat(raptor).isNotNull(); } From 564b3457e017234e8bb08c9395e97be7b839737d Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sun, 9 Jun 2024 23:08:46 +0200 Subject: [PATCH 26/28] TEST: NAV-40 - Improve documentation of same station transfer tests. --- .../java/ch/naviqore/raptor/RaptorTest.java | 63 +++++++++---------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index dbf31fe3..9ab84c28 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -254,72 +254,69 @@ private static void checkIfConnectionsAreParetoOptimal(List connecti @Nested class SameStationTransfers { - @Test - void shouldTakeFirstTripWithoutAddingSameStationTransferTime(RaptorTestBuilder builder) { - Raptor raptor = builder.buildWithDefaults(); - // There should be a connection leaving stop A at 5:00 am - String sourceStop = "A"; - String targetStop = "B"; - int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + private static final String SOURCE_STOP = "A"; + private static final String TARGET_STOP = "H"; + private static final int DEPARTURE_TIME = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; // 5 am + private static final int HEADWAY_TIME = 15; + private static final int TRAVEL_TIME = 5; + private static final int DWELL_TIME = 1; + + @Test + void shouldTakeFirstTripWithoutAddingSameStationTransferTimeAtFirstStop(RaptorTestBuilder builder) { + Raptor raptor = builder.withAddRoute1_AG().withAddRoute2_HL().withSameStationTransferTime(120).build(); + // There should be a connection leaving stop A at 5:00 am and this test should ensure that the same station + // transfer time is not added at the first stop, i.e. departure time at 5:00 am should allow to board the + // first trip at 5:00 am + List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME); assertEquals(1, connections.size()); - assertEquals(departureTime, connections.getFirst().getDepartureTime()); + assertEquals(DEPARTURE_TIME, connections.getFirst().getDepartureTime()); } @Test void shouldMissConnectingTripBecauseOfSameStationTransferTime(RaptorTestBuilder builder) { - Raptor raptor = builder.withAddRoute1_AG(19, 15, 5, 1) + Raptor raptor = builder.withAddRoute1_AG(19, HEADWAY_TIME, TRAVEL_TIME, DWELL_TIME) .withAddRoute2_HL() .withSameStationTransferTime(120) .build(); // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am - // Connection at 5:24 from B to C should be missed because of the same station transfer time (120s) - String sourceStop = "A"; - String targetStop = "H"; - int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + // Connection at 5:24 from B to H should be missed because of the same station transfer time (120s) + List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME); assertEquals(1, connections.size()); - assertEquals(departureTime + 19 * 60, connections.getFirst().getDepartureTime()); + assertEquals(DEPARTURE_TIME + 19 * 60, connections.getFirst().getDepartureTime()); - assertNotEquals(departureTime + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); + assertNotEquals(DEPARTURE_TIME + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); } @Test void shouldCatchConnectingTripBecauseOfNoSameStationTransferTime(RaptorTestBuilder builder) { - Raptor raptor = builder.withAddRoute1_AG(19, 15, 5, 1) + Raptor raptor = builder.withAddRoute1_AG(19, HEADWAY_TIME, TRAVEL_TIME, DWELL_TIME) .withAddRoute2_HL() .withSameStationTransferTime(0) .build(); // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am - // Connection at 5:24 from B to C should not be missed because of no same station transfer time - String sourceStop = "A"; - String targetStop = "H"; - int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + // Connection at 5:24 from B to H should not be missed because of no same station transfer time + List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME); assertEquals(1, connections.size()); - assertEquals(departureTime + 19 * 60, connections.getFirst().getDepartureTime()); - assertEquals(departureTime + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); + assertEquals(DEPARTURE_TIME + 19 * 60, connections.getFirst().getDepartureTime()); + assertEquals(DEPARTURE_TIME + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); } @Test void shouldCatchConnectingTripWithSameStationTransferTime(RaptorTestBuilder builder) { - Raptor raptor = builder.withAddRoute1_AG(17, 15, 5, 1) + Raptor raptor = builder.withAddRoute1_AG(17, HEADWAY_TIME, TRAVEL_TIME, DWELL_TIME) .withAddRoute2_HL() .withSameStationTransferTime(120) .build(); // There should be a connection leaving stop A at 5:17 am and arriving at stop B at 5:22 am - // Connection at 5:24 from B to C should be cached when the same station transfer time is 120s - String sourceStop = "A"; - String targetStop = "H"; - int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + // Connection at 5:24 from B to H should be cached when the same station transfer time is 120s + List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME); assertEquals(1, connections.size()); - assertEquals(departureTime + 17 * 60, connections.getFirst().getDepartureTime()); + assertEquals(DEPARTURE_TIME + 17 * 60, connections.getFirst().getDepartureTime()); - assertEquals(departureTime + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); + assertEquals(DEPARTURE_TIME + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); } } From afea594c6bcd9dc12a4b7e9cbaa7061d6c7e2673 Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Sun, 9 Jun 2024 23:18:05 +0200 Subject: [PATCH 27/28] TEST: NAV-59 - Add more comments to clarify test case with departure from two stops with different departure times. --- src/test/java/ch/naviqore/raptor/RaptorTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index 9ab84c28..8a17a42a 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -100,12 +100,15 @@ void routeFromTwoSourceStopsWithLaterDepartureTimeOnCloserStop(RaptorTestBuilder departureTime + RaptorTestBuilder.SECONDS_IN_HOUR); Map targetStops = Map.of("H", 0); - // fastest and only connection should be B -> H + // B -> H has no transfers but later arrival time (due to departure time one hour later) + // A -> H has one transfer but earlier arrival time List connections = raptor.routeEarliestArrival(sourceStops, targetStops); assertEquals(2, connections.size()); Helpers.assertConnection(connections.getFirst(), "B", "H", departureTime + RaptorTestBuilder.SECONDS_IN_HOUR, 0, 0, 1); Helpers.assertConnection(connections.get(1), "A", "H", departureTime, 1, 0, 2); + assertTrue(connections.getFirst().getArrivalTime() > connections.get(1).getArrivalTime(), + "Connection from A should arrive earlier than connection from B"); } @Test From c3cfca68e82f6250b31389497b3a4e62fda927cd Mon Sep 17 00:00:00 2001 From: Lukas Connolly Date: Mon, 10 Jun 2024 00:09:23 +0200 Subject: [PATCH 28/28] TEST: NAV-59 - Add extra comments or constants for test documentation. --- .../java/ch/naviqore/raptor/RaptorTest.java | 401 ++++++++---------- .../ch/naviqore/raptor/RaptorTestBuilder.java | 14 +- 2 files changed, 182 insertions(+), 233 deletions(-) diff --git a/src/test/java/ch/naviqore/raptor/RaptorTest.java b/src/test/java/ch/naviqore/raptor/RaptorTest.java index 8a17a42a..cf66a2c7 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTest.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTest.java @@ -16,11 +16,35 @@ @ExtendWith(RaptorTestExtension.class) class RaptorTest { + private static final int INFINITY = Integer.MAX_VALUE; + + private static final String STOP_A = "A"; + private static final String STOP_B = "B"; + private static final String STOP_C = "C"; + private static final String STOP_D = "D"; + private static final String STOP_E = "E"; + private static final String STOP_F = "F"; + private static final String STOP_G = "G"; + private static final String STOP_H = "H"; + private static final String STOP_K = "K"; + private static final String STOP_N = "N"; + private static final String STOP_M = "M"; + private static final String STOP_O = "O"; + private static final String STOP_P = "P"; + private static final String STOP_Q = "Q"; + private static final String STOP_S = "S"; + + private static final int FIVE_AM = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; + private static final int EIGHT_AM = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + private static final int NINE_AM = 9 * RaptorTestBuilder.SECONDS_IN_HOUR; + @Nested class EarliestArrival { + private static final int DEPARTURE_TIME = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + @Test - void shouldFindConnectionsBetweenIntersectingRoutes(RaptorTestBuilder builder) { + void findConnectionsBetweenIntersectingRoutes(RaptorTestBuilder builder) { // Should return two pareto optimal connections: // 1. Connection (with two route legs and one transfer (including footpath) --> slower but fewer transfers) // - Route R1-F from A to D @@ -33,15 +57,12 @@ void shouldFindConnectionsBetweenIntersectingRoutes(RaptorTestBuilder builder) { // - Route R3-F from P to Q Raptor raptor = builder.buildWithDefaults(); - String sourceStop = "A"; - String targetStop = "Q"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_Q, EIGHT_AM); // check if 2 connections were found assertEquals(2, connections.size()); - Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 1, 2); - Helpers.assertConnection(connections.get(1), sourceStop, targetStop, departureTime, 2, 0, 3); + Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_Q, EIGHT_AM, 0, 1, 2); + Helpers.assertConnection(connections.get(1), STOP_A, STOP_Q, EIGHT_AM, 2, 0, 3); Helpers.checkIfConnectionsAreParetoOptimal(connections); } @@ -49,12 +70,9 @@ void shouldFindConnectionsBetweenIntersectingRoutes(RaptorTestBuilder builder) { void routeBetweenTwoStopsOnSameRoute(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); - String sourceStop = "A"; - String targetStop = "B"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_B, EIGHT_AM); assertEquals(1, connections.size()); - Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 0, 1); + Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_B, EIGHT_AM, 0, 0, 1); } @Test @@ -62,17 +80,13 @@ void routeWithSelfIntersectingRoute(RaptorTestBuilder builder) { builder.withAddRoute5_AH_selfIntersecting(); Raptor raptor = builder.build(); - String sourceStop = "A"; - String targetStop = "H"; - int departureTime = 10 * RaptorTestBuilder.SECONDS_IN_HOUR; - - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_H, EIGHT_AM); assertEquals(2, connections.size()); // First Connection Should have no transfers but ride the entire loop (slow) - Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 0, 1); + Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_H, EIGHT_AM, 0, 0, 1); // Second Connection Should Change at Stop B and take the earlier trip of the same route there (faster) - Helpers.assertConnection(connections.get(1), sourceStop, targetStop, departureTime, 1, 0, 2); + Helpers.assertConnection(connections.get(1), STOP_A, STOP_H, EIGHT_AM, 1, 0, 2); Helpers.checkIfConnectionsAreParetoOptimal(connections); } @@ -81,32 +95,28 @@ void routeWithSelfIntersectingRoute(RaptorTestBuilder builder) { void routeFromTwoSourceStopsWithSameDepartureTime(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("A", departureTime, "B", departureTime); - Map targetStops = Map.of("H", 0); + Map sourceStops = Map.of(STOP_A, DEPARTURE_TIME, STOP_B, EIGHT_AM); + Map targetStops = Map.of(STOP_H, 0); // fastest and only connection should be B -> H List connections = raptor.routeEarliestArrival(sourceStops, targetStops); assertEquals(1, connections.size()); - Helpers.assertConnection(connections.getFirst(), "B", "H", departureTime, 0, 0, 1); + Helpers.assertConnection(connections.getFirst(), STOP_B, STOP_H, EIGHT_AM, 0, 0, 1); } @Test void routeFromTwoSourceStopsWithLaterDepartureTimeOnCloserStop(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("A", departureTime, "B", - departureTime + RaptorTestBuilder.SECONDS_IN_HOUR); - Map targetStops = Map.of("H", 0); + Map sourceStops = Map.of(STOP_A, EIGHT_AM, STOP_B, NINE_AM); + Map targetStops = Map.of(STOP_H, 0); // B -> H has no transfers but later arrival time (due to departure time one hour later) // A -> H has one transfer but earlier arrival time List connections = raptor.routeEarliestArrival(sourceStops, targetStops); assertEquals(2, connections.size()); - Helpers.assertConnection(connections.getFirst(), "B", "H", - departureTime + RaptorTestBuilder.SECONDS_IN_HOUR, 0, 0, 1); - Helpers.assertConnection(connections.get(1), "A", "H", departureTime, 1, 0, 2); + Helpers.assertConnection(connections.getFirst(), STOP_B, STOP_H, NINE_AM, 0, 0, 1); + Helpers.assertConnection(connections.get(1), STOP_A, STOP_H, EIGHT_AM, 1, 0, 2); assertTrue(connections.getFirst().getArrivalTime() > connections.get(1).getArrivalTime(), "Connection from A should arrive earlier than connection from B"); } @@ -115,49 +125,45 @@ void routeFromTwoSourceStopsWithLaterDepartureTimeOnCloserStop(RaptorTestBuilder void routeFromStopToTwoTargetStopsNoWalkTimeToTarget(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("A", departureTime); - Map targetStops = Map.of("F", 0, "S", 0); + Map sourceStops = Map.of(STOP_A, EIGHT_AM); + Map targetStops = Map.of(STOP_F, 0, STOP_S, 0); // fastest and only connection should be A -> F List connections = raptor.routeEarliestArrival(sourceStops, targetStops); assertEquals(1, connections.size()); - Helpers.assertConnection(connections.getFirst(), "A", "F", departureTime, 0, 0, 1); + Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_F, EIGHT_AM, 0, 0, 1); } @Test void routeFromStopToTwoTargetStopsWithWalkTimeToTarget(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("A", departureTime); - Map targetStops = Map.of("F", RaptorTestBuilder.SECONDS_IN_HOUR, "S", 0); + Map sourceStops = Map.of(STOP_A, EIGHT_AM); + // Add one-hour walk time to target from stop F and no extra walk time from stop S + Map targetStops = Map.of(STOP_F, RaptorTestBuilder.SECONDS_IN_HOUR, STOP_S, 0); // since F is closer to A than S, the fastest connection should be A -> F, but because of the hour // walk time to target, the connection A -> S should be faster (no additional walk time) List connections = raptor.routeEarliestArrival(sourceStops, targetStops); assertEquals(2, connections.size()); - Helpers.assertConnection(connections.getFirst(), "A", "F", departureTime, 0, 0, 1); - Helpers.assertConnection(connections.get(1), "A", "S", departureTime, 1, 0, 2); + Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_F, EIGHT_AM, 0, 0, 1); + Helpers.assertConnection(connections.get(1), STOP_A, STOP_S, EIGHT_AM, 1, 0, 2); // Note since the required walk time to target is not added as a leg, the solutions will not be pareto // optimal without additional post-processing. } @Test - void shouldNotFindConnectionBetweenNotLinkedStops(RaptorTestBuilder builder) { + void notFindConnectionBetweenNotLinkedStops(RaptorTestBuilder builder) { // Omit route R2/R4 and transfers to make stop Q (on R3) unreachable from A (on R1) Raptor raptor = builder.withAddRoute1_AG().withAddRoute3_MQ().build(); - String sourceStop = "A"; - String targetStop = "Q"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_Q, EIGHT_AM); assertTrue(connections.isEmpty(), "No connection should be found"); } @Test - void shouldFindConnectionBetweenOnlyFootpath(RaptorTestBuilder builder) { + void findConnectionBetweenOnlyFootpath(RaptorTestBuilder builder) { Raptor raptor = builder.withAddRoute1_AG() .withAddRoute2_HL() .withAddRoute3_MQ() @@ -166,54 +172,51 @@ void shouldFindConnectionBetweenOnlyFootpath(RaptorTestBuilder builder) { .withAddTransfer2_LR() .build(); - String sourceStop = "N"; - String targetStop = "D"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + List connections = raptor.routeEarliestArrival(STOP_N, STOP_D, EIGHT_AM); assertEquals(1, connections.size()); - Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 1, 0); + Helpers.assertConnection(connections.getFirst(), STOP_N, STOP_D, EIGHT_AM, 0, 1, 0); } @Test - void shouldTakeFasterRouteOfOverlappingRoutes(RaptorTestBuilder builder) { + void takeFasterRouteOfOverlappingRoutes(RaptorTestBuilder builder) { // Create Two Versions of the same route with different travel speeds (both leaving at same time from A) - Raptor raptor = builder.withAddRoute1_AG().withAddRoute1_AG("R1X", 0, 15, 3, 1).build(); - String sourceStop = "A"; - String targetStop = "G"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + Raptor raptor = builder.withAddRoute1_AG() + .withAddRoute1_AG("R1X", RaptorTestBuilder.DEFAULT_OFFSET, RaptorTestBuilder.DEFAULT_HEADWAY_TIME, + 3, RaptorTestBuilder.DEFAULT_DWELL_TIME) + .build(); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_G, EIGHT_AM); // Both Routes leave at 8:00 at Stop A, but R1 arrives at G at 8:35 whereas R1X arrives at G at 8:23 // R1X should be taken assertEquals(1, connections.size()); - Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 0, 1); + Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_G, EIGHT_AM, 0, 0, 1); // check departure at 8:00 Connection connection = connections.getFirst(); - assertEquals(departureTime, connection.getDepartureTime()); + assertEquals(EIGHT_AM, connection.getDepartureTime()); // check arrival time at 8:23 - assertEquals(departureTime + 23 * 60, connection.getArrivalTime()); + assertEquals(EIGHT_AM + 23 * 60, connection.getArrivalTime()); // check that R1X(-F for forward) route was used assertEquals("R1X-F", connection.getRouteLegs().getFirst().routeId()); } @Test - void shouldTakeSlowerRouteOfOverlappingRoutesDueToEarlierDepartureTime(RaptorTestBuilder builder) { + void takeSlowerRouteOfOverlappingRoutesDueToEarlierDepartureTime(RaptorTestBuilder builder) { // Create Two Versions of the same route with different travel speeds and different departure times - Raptor raptor = builder.withAddRoute1_AG().withAddRoute1_AG("R1X", 15, 30, 3, 1).build(); - String sourceStop = "A"; - String targetStop = "G"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime); + Raptor raptor = builder.withAddRoute1_AG() + .withAddRoute1_AG("R1X", 15, 30, 3, + RaptorTestBuilder.DEFAULT_DWELL_TIME) + .build(); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_G, EIGHT_AM); // Route R1 leaves at 8:00 at Stop A and arrives at G at 8:35 whereas R1X leaves at 8:15 from Stop A and // arrives at G at 8:38. R1 should be used. assertEquals(1, connections.size()); - Helpers.assertConnection(connections.getFirst(), sourceStop, targetStop, departureTime, 0, 0, 1); + Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_G, EIGHT_AM, 0, 0, 1); // check departure at 8:00 Connection connection = connections.getFirst(); - assertEquals(departureTime, connection.getDepartureTime()); + assertEquals(EIGHT_AM, connection.getDepartureTime()); // check arrival time at 8:35 - assertEquals(departureTime + 35 * 60, connection.getArrivalTime()); + assertEquals(EIGHT_AM + 35 * 60, connection.getArrivalTime()); // check that R1(-F for forward) route was used assertEquals("R1-F", connection.getRouteLegs().getFirst().routeId()); } @@ -257,69 +260,65 @@ private static void checkIfConnectionsAreParetoOptimal(List connecti @Nested class SameStationTransfers { - private static final String SOURCE_STOP = "A"; - private static final String TARGET_STOP = "H"; - private static final int DEPARTURE_TIME = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; // 5 am - private static final int HEADWAY_TIME = 15; - private static final int TRAVEL_TIME = 5; - private static final int DWELL_TIME = 1; - @Test - void shouldTakeFirstTripWithoutAddingSameStationTransferTimeAtFirstStop(RaptorTestBuilder builder) { + void takeFirstTripWithoutAddingSameStationTransferTimeAtFirstStop(RaptorTestBuilder builder) { Raptor raptor = builder.withAddRoute1_AG().withAddRoute2_HL().withSameStationTransferTime(120).build(); // There should be a connection leaving stop A at 5:00 am and this test should ensure that the same station // transfer time is not added at the first stop, i.e. departure time at 5:00 am should allow to board the // first trip at 5:00 am - List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_H, FIVE_AM); assertEquals(1, connections.size()); - assertEquals(DEPARTURE_TIME, connections.getFirst().getDepartureTime()); + assertEquals(FIVE_AM, connections.getFirst().getDepartureTime()); } @Test - void shouldMissConnectingTripBecauseOfSameStationTransferTime(RaptorTestBuilder builder) { - Raptor raptor = builder.withAddRoute1_AG(19, HEADWAY_TIME, TRAVEL_TIME, DWELL_TIME) + void missConnectingTripBecauseOfSameStationTransferTime(RaptorTestBuilder builder) { + Raptor raptor = builder.withAddRoute1_AG(19, RaptorTestBuilder.DEFAULT_HEADWAY_TIME, + RaptorTestBuilder.DEFAULT_TIME_BETWEEN_STOPS, RaptorTestBuilder.DEFAULT_DWELL_TIME) .withAddRoute2_HL() .withSameStationTransferTime(120) .build(); // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am // Connection at 5:24 from B to H should be missed because of the same station transfer time (120s) - List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_H, FIVE_AM); assertEquals(1, connections.size()); - assertEquals(DEPARTURE_TIME + 19 * 60, connections.getFirst().getDepartureTime()); + assertEquals(FIVE_AM + 19 * 60, connections.getFirst().getDepartureTime()); - assertNotEquals(DEPARTURE_TIME + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); + assertNotEquals(FIVE_AM + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); } @Test - void shouldCatchConnectingTripBecauseOfNoSameStationTransferTime(RaptorTestBuilder builder) { - Raptor raptor = builder.withAddRoute1_AG(19, HEADWAY_TIME, TRAVEL_TIME, DWELL_TIME) + void catchConnectingTripBecauseOfNoSameStationTransferTime(RaptorTestBuilder builder) { + Raptor raptor = builder.withAddRoute1_AG(19, RaptorTestBuilder.DEFAULT_HEADWAY_TIME, + RaptorTestBuilder.DEFAULT_TIME_BETWEEN_STOPS, RaptorTestBuilder.DEFAULT_DWELL_TIME) .withAddRoute2_HL() .withSameStationTransferTime(0) .build(); // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am // Connection at 5:24 from B to H should not be missed because of no same station transfer time - List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_H, FIVE_AM); assertEquals(1, connections.size()); - assertEquals(DEPARTURE_TIME + 19 * 60, connections.getFirst().getDepartureTime()); - assertEquals(DEPARTURE_TIME + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); + assertEquals(FIVE_AM + 19 * 60, connections.getFirst().getDepartureTime()); + assertEquals(FIVE_AM + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); } @Test - void shouldCatchConnectingTripWithSameStationTransferTime(RaptorTestBuilder builder) { - Raptor raptor = builder.withAddRoute1_AG(17, HEADWAY_TIME, TRAVEL_TIME, DWELL_TIME) + void catchConnectingTripWithSameStationTransferTime(RaptorTestBuilder builder) { + Raptor raptor = builder.withAddRoute1_AG(17, RaptorTestBuilder.DEFAULT_HEADWAY_TIME, + RaptorTestBuilder.DEFAULT_TIME_BETWEEN_STOPS, RaptorTestBuilder.DEFAULT_DWELL_TIME) .withAddRoute2_HL() .withSameStationTransferTime(120) .build(); // There should be a connection leaving stop A at 5:17 am and arriving at stop B at 5:22 am // Connection at 5:24 from B to H should be cached when the same station transfer time is 120s - List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_H, FIVE_AM); assertEquals(1, connections.size()); - assertEquals(DEPARTURE_TIME + 17 * 60, connections.getFirst().getDepartureTime()); + assertEquals(FIVE_AM + 17 * 60, connections.getFirst().getDepartureTime()); - assertEquals(DEPARTURE_TIME + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); + assertEquals(FIVE_AM + 24 * 60, connections.getFirst().getLegs().get(1).departureTime()); } } @@ -330,14 +329,10 @@ void shouldCatchConnectingTripWithSameStationTransferTime(RaptorTestBuilder buil * needed. */ @Nested - class QueryConfigTest { - - private static final String SOURCE_STOP = "A"; - private static final String TARGET_STOP = "Q"; - private static final int DEPARTURE_TIME = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; + class QueryConfiguration { @Test - void shouldFindWalkableTransferWithMaxWalkingTime(RaptorTestBuilder builder) { + void findWalkableTransferWithMaxWalkingTime(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); QueryConfig queryConfig = new QueryConfig(); queryConfig.setMaximumWalkingDuration(RaptorTestBuilder.SECONDS_IN_HOUR); @@ -352,35 +347,30 @@ void shouldFindWalkableTransferWithMaxWalkingTime(RaptorTestBuilder builder) { // - Route R1-F from A to F // - Route R4-R from F to P // - Route R3-F from P to Q - List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, - queryConfig); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_Q, EIGHT_AM, queryConfig); // check if 2 connections were found assertEquals(2, connections.size()); - EarliestArrival.Helpers.assertConnection(connections.getFirst(), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, - 0, 1, 2); - EarliestArrival.Helpers.assertConnection(connections.get(1), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, 2, 0, - 3); + EarliestArrival.Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_Q, EIGHT_AM, 0, 1, 2); + EarliestArrival.Helpers.assertConnection(connections.get(1), STOP_A, STOP_Q, EIGHT_AM, 2, 0, 3); EarliestArrival.Helpers.checkIfConnectionsAreParetoOptimal(connections); } @Test - void shouldNotFindWalkableTransferWithMaxWalkingTime(RaptorTestBuilder builder) { + void notFindWalkableTransferWithMaxWalkingTime(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); QueryConfig queryConfig = new QueryConfig(); queryConfig.setMaximumWalkingDuration(RaptorTestBuilder.SECONDS_IN_HOUR / 4); // 15 minutes // Should only find three route leg connections, since direct transfer between D and N is longer than // allowed maximum walking distance (60 minutes): - List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, - queryConfig); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_Q, EIGHT_AM, queryConfig); assertEquals(1, connections.size()); - EarliestArrival.Helpers.assertConnection(connections.getFirst(), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, - 2, 0, 3); + EarliestArrival.Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_Q, EIGHT_AM, 2, 0, 3); } @Test - void shouldFindConnectionWithMaxTransferNumber(RaptorTestBuilder builder) { + void findConnectionWithMaxTransferNumber(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); QueryConfig queryConfig = new QueryConfig(); queryConfig.setMaximumTransferNumber(1); @@ -391,15 +381,13 @@ void shouldFindConnectionWithMaxTransferNumber(RaptorTestBuilder builder) { // - Foot Transfer from D to N // - Route R3-F from N to Q // 2. Connection with two transfers (see above) should not be found - List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, - queryConfig); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_Q, EIGHT_AM, queryConfig); assertEquals(1, connections.size()); - EarliestArrival.Helpers.assertConnection(connections.getFirst(), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, - 0, 1, 2); + EarliestArrival.Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_Q, EIGHT_AM, 0, 1, 2); } @Test - void shouldFindConnectionWithMaxTravelTime(RaptorTestBuilder builder) { + void findConnectionWithMaxTravelTime(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); QueryConfig queryConfig = new QueryConfig(); queryConfig.setMaximumTravelTime(RaptorTestBuilder.SECONDS_IN_HOUR); @@ -408,15 +396,13 @@ void shouldFindConnectionWithMaxTravelTime(RaptorTestBuilder builder) { // - Route R1-F from A to F // - Route R4-R from F to P // - Route R3-F from P to Q - List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, - queryConfig); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_Q, EIGHT_AM, queryConfig); assertEquals(1, connections.size()); - EarliestArrival.Helpers.assertConnection(connections.getFirst(), SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, - 2, 0, 3); + EarliestArrival.Helpers.assertConnection(connections.getFirst(), STOP_A, STOP_Q, EIGHT_AM, 2, 0, 3); } @Test - void shouldUseSameStationTransferTimeWithZeroMinimumTransferDuration(RaptorTestBuilder builder) { + void useSameStationTransferTimeWithZeroMinimumTransferDuration(RaptorTestBuilder builder) { QueryConfig queryConfig = new QueryConfig(); queryConfig.setMinimumTransferDuration(0); @@ -427,20 +413,15 @@ void shouldUseSameStationTransferTimeWithZeroMinimumTransferDuration(RaptorTestB // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am. Connection // at 5:24 (next 5:39) from B to C should be missed because of the same station transfer time (120s), // regardless of minimum same transfer duration at 0s - String sourceStop = "A"; - String targetStop = "H"; - int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; - - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime, - queryConfig); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_H, FIVE_AM, queryConfig); assertEquals(1, connections.size()); - assertEquals(departureTime + 19 * 60, connections.getFirst().getDepartureTime()); - assertEquals(departureTime + 39 * 60, connections.getFirst().getLegs().get(1).departureTime()); + assertEquals(FIVE_AM + 19 * 60, connections.getFirst().getDepartureTime()); + assertEquals(FIVE_AM + 39 * 60, connections.getFirst().getLegs().get(1).departureTime()); } @Test - void shouldUseMinimumTransferTime(RaptorTestBuilder builder) { + void useMinimumTransferTime(RaptorTestBuilder builder) { QueryConfig queryConfig = new QueryConfig(); queryConfig.setMinimumTransferDuration(20 * 60); // 20 minutes @@ -450,31 +431,24 @@ void shouldUseMinimumTransferTime(RaptorTestBuilder builder) { .build(); // There should be a connection leaving stop A at 5:19 am and arriving at stop B at 5:24 am. Connection // at 5:24 and 5:39 from B to C should be missed because of the minimum transfer duration (20 minutes) - String sourceStop = "A"; - String targetStop = "H"; - int departureTime = 5 * RaptorTestBuilder.SECONDS_IN_HOUR; - - List connections = raptor.routeEarliestArrival(sourceStop, targetStop, departureTime, - queryConfig); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_H, FIVE_AM, queryConfig); assertEquals(1, connections.size()); - assertEquals(departureTime + 19 * 60, connections.getFirst().getDepartureTime()); - assertEquals(departureTime + 54 * 60, connections.getFirst().getLegs().get(1).departureTime()); + assertEquals(FIVE_AM + 19 * 60, connections.getFirst().getDepartureTime()); + assertEquals(FIVE_AM + 54 * 60, connections.getFirst().getLegs().get(1).departureTime()); } @Test - void shouldAddMinimumTransferTimeToWalkTransferDuration(RaptorTestBuilder builder) { + void addMinimumTransferTimeToWalkTransferDuration(RaptorTestBuilder builder) { QueryConfig queryConfig = new QueryConfig(); queryConfig.setMinimumTransferDuration(20 * 60); // 20 minutes Raptor raptor = builder.buildWithDefaults(); - List connections = raptor.routeEarliestArrival(SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, - queryConfig); + List connections = raptor.routeEarliestArrival(STOP_A, STOP_Q, EIGHT_AM, queryConfig); assertEquals(2, connections.size()); Connection firstConnection = connections.getFirst(); - EarliestArrival.Helpers.assertConnection(firstConnection, SOURCE_STOP, TARGET_STOP, DEPARTURE_TIME, 0, 1, - 2); + EarliestArrival.Helpers.assertConnection(firstConnection, STOP_A, STOP_Q, EIGHT_AM, 0, 1, 2); // The walk transfer from D to N takes 60 minutes and the route from N to Q leaves every 75 minutes. Connection.Leg firstLeg = firstConnection.getRouteLegs().getFirst(); @@ -489,31 +463,25 @@ void shouldAddMinimumTransferTimeToWalkTransferDuration(RaptorTestBuilder builde class IsoLines { @Test - void shouldCreateIsoLinesToAllStops(RaptorTestBuilder builder) { + void createIsoLinesToAllStops(RaptorTestBuilder builder) { Raptor raptor = builder.buildWithDefaults(); - - String sourceStop = "A"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map isoLines = raptor.getIsoLines(Map.of(sourceStop, departureTime)); + Map isoLines = raptor.getIsoLines(Map.of(STOP_A, EIGHT_AM)); int stopsInSystem = 19; int expectedIsoLines = stopsInSystem - 1; - Helpers.assertIsoLines(isoLines, sourceStop, departureTime, expectedIsoLines); + Helpers.assertIsoLines(isoLines, expectedIsoLines); } @Test - void shouldCreateIsoLinesToSomeStopsNotAllConnected(RaptorTestBuilder builder) { + void createIsoLinesToSomeStopsNotAllConnected(RaptorTestBuilder builder) { // Route 1 and 3 are not connected, thus all Stops of Route 3 should not be reachable from A Raptor raptor = builder.withAddRoute1_AG().withAddRoute3_MQ().build(); + Map isoLines = raptor.getIsoLines(Map.of(STOP_A, EIGHT_AM)); - String sourceStop = "A"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map isoLines = raptor.getIsoLines(Map.of(sourceStop, departureTime)); - - List reachableStops = List.of("B", "C", "D", "E", "F", "G"); + List reachableStops = List.of(STOP_B, STOP_C, STOP_D, STOP_E, STOP_F, STOP_G); // Not Reachable Stops: M, K, N, O, P, Q - Helpers.assertIsoLines(isoLines, sourceStop, departureTime, reachableStops.size()); + Helpers.assertIsoLines(isoLines, reachableStops.size()); for (String stop : reachableStops) { assertTrue(isoLines.containsKey(stop), "Stop " + stop + " should be reachable"); @@ -521,17 +489,15 @@ void shouldCreateIsoLinesToSomeStopsNotAllConnected(RaptorTestBuilder builder) { } @Test - void shouldCreateIsoLinesToStopsOfOtherLineOnlyConnectedByFootpath(RaptorTestBuilder builder) { + void createIsoLinesToStopsOfOtherLineOnlyConnectedByFootpath(RaptorTestBuilder builder) { // Route 1 and Route 3 are only connected by Footpath between Stops D and N Raptor raptor = builder.withAddRoute1_AG().withAddRoute3_MQ().withAddTransfer1_ND().build(); + Map isoLines = raptor.getIsoLines(Map.of(STOP_A, EIGHT_AM)); - String sourceStop = "A"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map isoLines = raptor.getIsoLines(Map.of(sourceStop, departureTime)); + List reachableStops = List.of(STOP_B, STOP_C, STOP_D, STOP_E, STOP_F, STOP_G, STOP_M, STOP_K, + STOP_N, STOP_O, STOP_P, STOP_Q); - List reachableStops = List.of("B", "C", "D", "E", "F", "G", "M", "K", "N", "O", "P", "Q"); - - Helpers.assertIsoLines(isoLines, sourceStop, departureTime, reachableStops.size()); + Helpers.assertIsoLines(isoLines, reachableStops.size()); for (String stop : reachableStops) { assertTrue(isoLines.containsKey(stop), "Stop " + stop + " should be reachable"); @@ -539,22 +505,22 @@ void shouldCreateIsoLinesToStopsOfOtherLineOnlyConnectedByFootpath(RaptorTestBui } @Test - void shouldCreateIsoLinesFromTwoNotConnectedSourceStops(RaptorTestBuilder builder) { + void createIsoLinesFromTwoNotConnectedSourceStops(RaptorTestBuilder builder) { Raptor raptor = builder.withAddRoute1_AG().withAddRoute3_MQ().build(); - Map departureTimeHours = Map.of("A", 8, "M", 16); + Map departureTimeHours = Map.of(STOP_A, 8, STOP_M, 16); - List reachableStopsFromStopA = List.of("B", "C", "D", "E", "F", "G"); - Map sourceStops = Map.of("A", - departureTimeHours.get("A") * RaptorTestBuilder.SECONDS_IN_HOUR, "M", - departureTimeHours.get("M") * RaptorTestBuilder.SECONDS_IN_HOUR); - List reachableStopsFromStopM = List.of("K", "N", "O", "P", "Q"); + List reachableStopsFromStopA = List.of(STOP_B, STOP_C, STOP_D, STOP_E, STOP_F, STOP_G); + Map sourceStops = Map.of(STOP_A, + departureTimeHours.get(STOP_A) * RaptorTestBuilder.SECONDS_IN_HOUR, STOP_M, + departureTimeHours.get(STOP_M) * RaptorTestBuilder.SECONDS_IN_HOUR); + List reachableStopsFromStopM = List.of(STOP_K, STOP_N, STOP_O, STOP_P, STOP_Q); Map isoLines = raptor.getIsoLines(sourceStops); assertEquals(reachableStopsFromStopA.size() + reachableStopsFromStopM.size(), isoLines.size()); - Map> sourceTargets = Map.of("A", reachableStopsFromStopA, "M", + Map> sourceTargets = Map.of(STOP_A, reachableStopsFromStopA, STOP_M, reachableStopsFromStopM); for (Map.Entry> entry : sourceTargets.entrySet()) { @@ -577,21 +543,18 @@ void shouldCreateIsoLinesFromTwoNotConnectedSourceStops(RaptorTestBuilder builde } private static class Helpers { - - private static final int INFINITY = Integer.MAX_VALUE; - - private static void assertIsoLines(Map isoLines, String sourceStopId, int departureTime, - int expectedIsoLines) { + private static void assertIsoLines(Map isoLines, int expectedIsoLines) { assertEquals(expectedIsoLines, isoLines.size()); - assertFalse(isoLines.containsKey(sourceStopId), "Source stop should not be in iso lines"); + assertFalse(isoLines.containsKey(RaptorTest.STOP_A), "Source stop should not be in iso lines"); for (Map.Entry entry : isoLines.entrySet()) { - assertTrue(departureTime <= entry.getValue().getDepartureTime(), + assertTrue(RaptorTest.EIGHT_AM <= entry.getValue().getDepartureTime(), "Departure time should be greater than or equal to departure time"); - assertTrue(departureTime < entry.getValue().getArrivalTime(), + assertTrue(RaptorTest.EIGHT_AM < entry.getValue().getArrivalTime(), "Arrival time should be greater than or equal to departure time"); assertTrue(entry.getValue().getArrivalTime() < INFINITY, "Arrival time should be less than INFINITY"); - assertEquals(sourceStopId, entry.getValue().getFromStopId(), "From stop should be source stop"); + assertEquals(RaptorTest.STOP_A, entry.getValue().getFromStopId(), + "From stop should be source stop"); assertEquals(entry.getKey(), entry.getValue().getToStopId(), "To stop should be key of map entry"); } } @@ -611,79 +574,67 @@ void setUp(RaptorTestBuilder builder) { } @Test - void shouldThrowErrorWhenSourceStopNotExists() { + void throwErrorWhenSourceStopNotExists() { String sourceStop = "NonExistentStop"; - String targetStop = "A"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStop, targetStop, departureTime), - "Source stop has to exists"); + () -> raptor.routeEarliestArrival(sourceStop, STOP_A, EIGHT_AM), "Source stop has to exists"); } @Test - void shouldThrowErrorWhenTargetStopNotExists() { - String sourceStop = "A"; + void throwErrorWhenTargetStopNotExists() { String targetStop = "NonExistentStop"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStop, targetStop, departureTime), - "Target stop has to exists"); + () -> raptor.routeEarliestArrival(STOP_A, targetStop, EIGHT_AM), "Target stop has to exists"); } @Test - void shouldNotThrowErrorForValidAndNonExistingSourceStop() { - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("A", departureTime, "NonExistentStop", departureTime); - Map targetStops = Map.of("H", 0); + void notThrowErrorForValidAndNonExistingSourceStop() { + Map sourceStops = Map.of(STOP_A, EIGHT_AM, "NonExistentStop", EIGHT_AM); + Map targetStops = Map.of(STOP_H, 0); assertDoesNotThrow(() -> raptor.routeEarliestArrival(sourceStops, targetStops), "Source stops can contain non-existing stops, if one entry is valid"); } @Test - void shouldThrowErrorForInvalidDepartureTimeFromOneOfManySourceStops() { - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("A", departureTime, "B", Integer.MAX_VALUE); - Map targetStops = Map.of("H", 0); + void throwErrorForInvalidDepartureTimeFromOneOfManySourceStops() { + Map sourceStops = Map.of(STOP_A, EIGHT_AM, STOP_B, Integer.MAX_VALUE); + Map targetStops = Map.of(STOP_H, 0); assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), "Departure time has to be valid for all valid source stops"); } @Test - void shouldNotThrowErrorForValidAndNonExistingTargetStop() { - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("H", departureTime); - Map targetStops = Map.of("A", 0, "NonExistentStop", 0); + void notThrowErrorForValidAndNonExistingTargetStop() { + Map sourceStops = Map.of(STOP_H, EIGHT_AM); + Map targetStops = Map.of(STOP_A, 0, "NonExistentStop", 0); assertDoesNotThrow(() -> raptor.routeEarliestArrival(sourceStops, targetStops), "Target stops can contain non-existing stops, if one entry is valid"); } @Test - void shouldThrowErrorForInvalidWalkToTargetTimeFromOneOfManyTargetStops() { - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - Map sourceStops = Map.of("H", departureTime); - Map targetStops = Map.of("A", 0, "B", -1); + void throwErrorForInvalidWalkToTargetTimeFromOneOfManyTargetStops() { + Map sourceStops = Map.of(STOP_H, EIGHT_AM); + Map targetStops = Map.of(STOP_A, 0, STOP_B, -1); assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), "Departure time has to be valid for all valid source stops"); } @Test - void shouldThrowErrorNullSourceStops() { + void throwErrorNullSourceStops() { Map sourceStops = null; - Map targetStops = Map.of("H", 0); + Map targetStops = Map.of(STOP_H, 0); assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), "Source stops cannot be null"); } @Test - void shouldThrowErrorNullTargetStops() { - Map sourceStops = Map.of("A", 0); + void throwErrorNullTargetStops() { + Map sourceStops = Map.of(STOP_A, 0); Map targetStops = null; assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), @@ -691,17 +642,17 @@ void shouldThrowErrorNullTargetStops() { } @Test - void shouldThrowErrorEmptyMapSourceStops() { + void throwErrorEmptyMapSourceStops() { Map sourceStops = Map.of(); - Map targetStops = Map.of("H", 0); + Map targetStops = Map.of(STOP_H, 0); assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), "Source and target stops cannot be null"); } @Test - void shouldThrowErrorEmptyMapTargetStops() { - Map sourceStops = Map.of("A", 0); + void throwErrorEmptyMapTargetStops() { + Map sourceStops = Map.of(STOP_A, 0); Map targetStops = Map.of(); assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStops, targetStops), @@ -709,25 +660,17 @@ void shouldThrowErrorEmptyMapTargetStops() { } @Test - void shouldThrowErrorWhenDepartureTimeIsOutOfRange() { - String sourceStop = "A"; - String targetStop = "B"; - - assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(sourceStop, targetStop, -1), + void throwErrorWhenDepartureTimeIsOutOfRange() { + assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(STOP_A, STOP_B, -1), "Departure time cannot be negative"); assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStop, targetStop, 49 * RaptorTestBuilder.SECONDS_IN_HOUR), + () -> raptor.routeEarliestArrival(STOP_A, STOP_B, 49 * RaptorTestBuilder.SECONDS_IN_HOUR), "Departure time cannot be greater than two days"); } @Test - void shouldThrowErrorWhenRequestBetweenSameStop() { - String sourceStop = "A"; - String targetStop = "A"; - int departureTime = 8 * RaptorTestBuilder.SECONDS_IN_HOUR; - - assertThrows(IllegalArgumentException.class, - () -> raptor.routeEarliestArrival(sourceStop, targetStop, departureTime), + void throwErrorWhenRequestBetweenSameStop() { + assertThrows(IllegalArgumentException.class, () -> raptor.routeEarliestArrival(STOP_A, STOP_A, EIGHT_AM), "Stops cannot be the same"); } diff --git a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java index 581b4290..8bd6072a 100644 --- a/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java +++ b/src/test/java/ch/naviqore/raptor/RaptorTestBuilder.java @@ -42,8 +42,14 @@ public class RaptorTestBuilder { static final int SECONDS_IN_HOUR = 3600; - private static final int DAY_START_HOUR = 5; - private static final int DAY_END_HOUR = 25; + static final int DAY_START_HOUR = 5; + static final int DAY_END_HOUR = 25; + + static final int DEFAULT_TIME_BETWEEN_STOPS = 5; + static final int DEFAULT_DWELL_TIME = 1; + static final int DEFAULT_HEADWAY_TIME = 15; + static final int DEFAULT_OFFSET = 0; + private final List routes = new ArrayList<>(); private final List transfers = new ArrayList<>(); private int sameStationTransferTime = 120; @@ -111,7 +117,7 @@ private static Raptor build(List routes, List transfers, int da } public RaptorTestBuilder withAddRoute1_AG() { - return withAddRoute1_AG(0, 15, 5, 1); + return withAddRoute1_AG(DEFAULT_OFFSET, DEFAULT_HEADWAY_TIME, DEFAULT_TIME_BETWEEN_STOPS, DEFAULT_DWELL_TIME); } public RaptorTestBuilder withAddRoute1_AG(int offset, int headway, int travelTime, int dwellTime) { @@ -191,7 +197,7 @@ private record Route(String id, List stops, int firstDepartureOffset, in int travelTimeBetweenStops, int dwellTimeAtSTop) { public Route(String id, List stops) { - this(id, stops, 0, 15, 5, 1); + this(id, stops, DEFAULT_OFFSET, DEFAULT_HEADWAY_TIME, DEFAULT_TIME_BETWEEN_STOPS, DEFAULT_DWELL_TIME); } }