From 927a1c141271bc9ec4501aca97410c1b1fc8b33c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 15:45:53 +0200 Subject: [PATCH 01/20] Remove duplicate method --- .../ext/flex/trip/UnscheduledTripTest.java | 12 ++++++------ .../ext/flex/trip/ScheduledDeviatedTrip.java | 16 ++++------------ .../ext/flex/trip/UnscheduledTrip.java | 18 +++++------------- 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 030c9de3398..fe006450791 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -191,8 +191,8 @@ void testUnscheduledTrip() { assertEquals(T11_00, trip.earliestDepartureTime(STOP_B)); assertEquals(T15_00, trip.latestArrivalTime(STOP_B)); - assertEquals(PickDrop.SCHEDULED, trip.getPickupType(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getDropOffType(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); } @Test @@ -216,8 +216,8 @@ void testUnscheduledFeederTripFromScheduledStop() { assertEquals(T10_00, trip.earliestDepartureTime(STOP_B)); assertEquals(T14_00, trip.latestArrivalTime(STOP_B)); - assertEquals(PickDrop.SCHEDULED, trip.getPickupType(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getDropOffType(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); } @Test @@ -236,8 +236,8 @@ void testUnscheduledFeederTripToScheduledStop() { .withStopTimes(List.of(fromStopTime, toStopTime)) .build(); - assertEquals(PickDrop.SCHEDULED, trip.getPickupType(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getDropOffType(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); + assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); } static Stream testRegularStopToAreaEarliestDepartureTimeTestCases() { diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java index 06bbd11fac7..3148634f945 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java @@ -92,7 +92,7 @@ public Stream getFlexAccessTemplates( ArrayList res = new ArrayList<>(); for (int toIndex = fromIndex; toIndex < stopTimes.length; toIndex++) { - if (getDropOffType(toIndex).isNotRoutable()) { + if (getAlightRule(toIndex).isNotRoutable()) { continue; } for (StopLocation stop : expandStops(stopTimes[toIndex].stop)) { @@ -132,7 +132,7 @@ public Stream getFlexEgressTemplates( ArrayList res = new ArrayList<>(); for (int fromIndex = toIndex; fromIndex >= 0; fromIndex--) { - if (getPickupType(fromIndex).isNotRoutable()) { + if (getBoardRule(fromIndex).isNotRoutable()) { continue; } for (StopLocation stop : expandStops(stopTimes[fromIndex].stop)) { @@ -230,14 +230,6 @@ public boolean isAlightingPossible(NearbyStop stop) { return getToIndex(stop) != -1; } - public PickDrop getPickupType(int i) { - return stopTimes[i].pickupType; - } - - public PickDrop getDropOffType(int i) { - return stopTimes[i].dropOffType; - } - @Override public boolean sameAs(@Nonnull ScheduledDeviatedTrip other) { return ( @@ -262,7 +254,7 @@ private Collection expandStops(StopLocation stop) { private int getFromIndex(NearbyStop accessEgress) { for (int i = 0; i < stopTimes.length; i++) { - if (getPickupType(i).isNotRoutable()) { + if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop; @@ -281,7 +273,7 @@ private int getFromIndex(NearbyStop accessEgress) { private int getToIndex(NearbyStop accessEgress) { for (int i = stopTimes.length - 1; i >= 0; i--) { - if (getDropOffType(i).isNotRoutable()) { + if (getAlightRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop; diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index b54c6173387..c2ff03fd29e 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -38,7 +38,7 @@ public class UnscheduledTrip extends FlexTrip { // unscheduled trips can contain one or two stop_times - private static final Set N_STOPS = Set.of(1, 2); + private static final Set N_STOPS = Set.of(1, 2, 3); private final StopTimeWindow[] stopTimes; @@ -94,7 +94,7 @@ public Stream getFlexAccessTemplates( int toIndex = stopTimes.length - 1; // Check if trip is possible - if (fromIndex == -1 || fromIndex > toIndex || getDropOffType(toIndex).isNotRoutable()) { + if (fromIndex == -1 || fromIndex > toIndex || getBoardRule(toIndex).isNotRoutable()) { return Stream.empty(); } @@ -123,7 +123,7 @@ public Stream getFlexEgressTemplates( int toIndex = getToIndex(egress); // Check if trip is possible - if (toIndex == -1 || fromIndex > toIndex || getPickupType(fromIndex).isNotRoutable()) { + if (toIndex == -1 || fromIndex > toIndex || getBoardRule(fromIndex).isNotRoutable()) { return Stream.empty(); } @@ -229,14 +229,6 @@ public boolean isAlightingPossible(NearbyStop stop) { return getToIndex(stop) != -1; } - public PickDrop getPickupType(int i) { - return stopTimes[i].pickupType(); - } - - public PickDrop getDropOffType(int i) { - return stopTimes[i].dropOffType(); - } - @Override public boolean sameAs(@Nonnull UnscheduledTrip other) { return ( @@ -261,7 +253,7 @@ private Collection expandStops(StopLocation stop) { private int getFromIndex(NearbyStop accessEgress) { for (int i = 0; i < stopTimes.length; i++) { - if (getPickupType(i).isNotRoutable()) { + if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); @@ -280,7 +272,7 @@ private int getFromIndex(NearbyStop accessEgress) { private int getToIndex(NearbyStop accessEgress) { for (int i = stopTimes.length - 1; i >= 0; i--) { - if (getDropOffType(i).isNotRoutable()) { + if (getBoardRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); From e2bb1c57bb4019a3d10b888728e29479f0587221 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 13 Sep 2023 17:51:57 +0200 Subject: [PATCH 02/20] Clean up some flex code --- .../opentripplanner/ext/flex/FlexRouter.java | 8 ++-- .../ext/flex/trip/ScheduledDeviatedTrip.java | 2 +- .../ext/flex/trip/UnscheduledTrip.java | 44 ++++++++----------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java b/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java index 970adf4a267..c84cb8823a7 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java +++ b/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java @@ -153,7 +153,7 @@ public Collection createFlexAccesses() { return this.flexAccessTemplates.stream() .flatMap(template -> template.createFlexAccessEgressStream(graph, transitService)) - .collect(Collectors.toList()); + .toList(); } public Collection createFlexEgresses() { @@ -162,7 +162,7 @@ public Collection createFlexEgresses() { return this.flexEgressTemplates.stream() .flatMap(template -> template.createFlexAccessEgressStream(graph, transitService)) - .collect(Collectors.toList()); + .toList(); } private void calculateFlexAccessTemplates() { @@ -186,7 +186,7 @@ private void calculateFlexAccessTemplates() { .getFlexAccessTemplates(it.accessEgress(), date, accessFlexPathCalculator, config) ) ) - .collect(Collectors.toList()); + .toList(); } private void calculateFlexEgressTemplates() { @@ -210,7 +210,7 @@ private void calculateFlexEgressTemplates() { .getFlexEgressTemplates(it.accessEgress(), date, egressFlexPathCalculator, config) ) ) - .collect(Collectors.toList()); + .toList(); } private Stream getClosestFlexTrips( diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java index 3148634f945..edff0860933 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java @@ -31,7 +31,7 @@ import org.opentripplanner.transit.model.site.StopLocation; /** - * A scheduled deviated trip is similar to a regular scheduled trip, except that is continues stop + * A scheduled deviated trip is similar to a regular scheduled trip, except that it contains stop * locations, which are not stops, but other types, such as groups of stops or location areas. */ public class ScheduledDeviatedTrip diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index c2ff03fd29e..c204c9b34d5 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -3,10 +3,7 @@ import static org.opentripplanner.model.PickDrop.NONE; import static org.opentripplanner.model.StopTime.MISSING_VALUE; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @@ -39,6 +36,7 @@ public class UnscheduledTrip extends FlexTrip N_STOPS = Set.of(1, 2, 3); + private static final int INDEX_NOT_FOUND = -1; private final StopTimeWindow[] stopTimes; @@ -94,19 +92,16 @@ public Stream getFlexAccessTemplates( int toIndex = stopTimes.length - 1; // Check if trip is possible - if (fromIndex == -1 || fromIndex > toIndex || getBoardRule(toIndex).isNotRoutable()) { + if ( + fromIndex == INDEX_NOT_FOUND || fromIndex > toIndex || getAlightRule(toIndex).isNotRoutable() + ) { return Stream.empty(); } - ArrayList res = new ArrayList<>(); - - for (StopLocation stop : expandStops(stopTimes[toIndex].stop())) { - res.add( + return expandStops(stopTimes[toIndex].stop()) + .map(stop -> new FlexAccessTemplate(access, this, fromIndex, toIndex, stop, date, calculator, config) ); - } - - return res.stream(); } @Override @@ -123,19 +118,16 @@ public Stream getFlexEgressTemplates( int toIndex = getToIndex(egress); // Check if trip is possible - if (toIndex == -1 || fromIndex > toIndex || getBoardRule(fromIndex).isNotRoutable()) { + if ( + toIndex == INDEX_NOT_FOUND || fromIndex > toIndex || getBoardRule(fromIndex).isNotRoutable() + ) { return Stream.empty(); } - ArrayList res = new ArrayList<>(); - - for (StopLocation stop : expandStops(stopTimes[fromIndex].stop())) { - res.add( + return expandStops(stopTimes[fromIndex].stop()) + .map(stop -> new FlexEgressTemplate(egress, this, fromIndex, toIndex, stop, date, calculator, config) ); - } - - return res.stream(); } @Override @@ -221,12 +213,12 @@ public PickDrop getAlightRule(int i) { @Override public boolean isBoardingPossible(NearbyStop stop) { - return getFromIndex(stop) != -1; + return getFromIndex(stop) != INDEX_NOT_FOUND; } @Override public boolean isAlightingPossible(NearbyStop stop) { - return getToIndex(stop) != -1; + return getToIndex(stop) != INDEX_NOT_FOUND; } @Override @@ -245,10 +237,10 @@ public TransitBuilder copy() { return new UnscheduledTripBuilder(this); } - private Collection expandStops(StopLocation stop) { + private Stream expandStops(StopLocation stop) { return stop instanceof GroupStop groupStop - ? groupStop.getLocations() - : Collections.singleton(stop); + ? groupStop.getLocations().stream() + : Stream.of(stop); } private int getFromIndex(NearbyStop accessEgress) { @@ -267,7 +259,7 @@ private int getFromIndex(NearbyStop accessEgress) { } } } - return -1; + return INDEX_NOT_FOUND; } private int getToIndex(NearbyStop accessEgress) { @@ -286,7 +278,7 @@ private int getToIndex(NearbyStop accessEgress) { } } } - return -1; + return INDEX_NOT_FOUND; } private Optional departureTimeWindow( From 1a4c3df6fc349974cbdc07bf0c8ab290d85531fe Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 11:09:58 +0200 Subject: [PATCH 03/20] Take mulitple AreaStops into account --- .../ext/flex/trip/UnscheduledTrip.java | 64 ++++++++++++++----- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index c204c9b34d5..9b58a2c00fa 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -9,6 +9,7 @@ import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nonnull; import org.opentripplanner.ext.flex.FlexServiceDate; @@ -85,22 +86,43 @@ public Stream getFlexAccessTemplates( FlexPathCalculator calculator, FlexConfig config ) { - // Find boarding index - int fromIndex = getFromIndex(access); + // Find boarding index, also check if it's boardable + final int fromIndex = getFromIndex(access); // Alighting is always at the last stop for unscheduled trips - int toIndex = stopTimes.length - 1; + final int lastIndexInTrip = stopTimes.length - 1; // Check if trip is possible - if ( - fromIndex == INDEX_NOT_FOUND || fromIndex > toIndex || getAlightRule(toIndex).isNotRoutable() - ) { + if (fromIndex == INDEX_NOT_FOUND || fromIndex > lastIndexInTrip) { return Stream.empty(); } - return expandStops(stopTimes[toIndex].stop()) - .map(stop -> - new FlexAccessTemplate(access, this, fromIndex, toIndex, stop, date, calculator, config) + IntStream indices; + if (stopTimes.length == 1) { + indices = IntStream.of(fromIndex); + } else { + indices = IntStream.range(fromIndex + 1, lastIndexInTrip); + } + // check for every stop after fromIndex if you can alight, if so return a template + return indices + // if you cannot alight at a certain index, the trip is not possible + .filter(alightIndex -> getAlightRule(alightIndex).isRoutable()) + // expand GroupStops and build IndexedStopLocations + .mapToObj(this::expandStops) + // flatten stream of streams + .flatMap(s -> s) + // create template + .map(alightStop -> + new FlexAccessTemplate( + access, + this, + fromIndex, + alightStop.index, + alightStop.stop, + date, + calculator, + config + ) ); } @@ -124,9 +146,18 @@ public Stream getFlexEgressTemplates( return Stream.empty(); } - return expandStops(stopTimes[fromIndex].stop()) - .map(stop -> - new FlexEgressTemplate(egress, this, fromIndex, toIndex, stop, date, calculator, config) + return expandStops(fromIndex) + .map(indexedStop -> + new FlexEgressTemplate( + egress, + this, + fromIndex, + toIndex, + indexedStop.stop, + date, + calculator, + config + ) ); } @@ -237,10 +268,11 @@ public TransitBuilder copy() { return new UnscheduledTripBuilder(this); } - private Stream expandStops(StopLocation stop) { + private Stream expandStops(int index) { + var stop = stopTimes[index].stop(); return stop instanceof GroupStop groupStop - ? groupStop.getLocations().stream() - : Stream.of(stop); + ? groupStop.getLocations().stream().map(s -> new IndexedStopLocation(index, s)) + : Stream.of(new IndexedStopLocation(index, stop)); } private int getFromIndex(NearbyStop accessEgress) { @@ -308,4 +340,6 @@ private Optional arrivalTimeWindow( // requested-arrival-time must be within return toTime.intersect(fromTimeShifted); } + + private record IndexedStopLocation(int index, StopLocation stop) {} } From 378b8dedceb3469b990fe7e2c09883961f770d91 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 11:15:16 +0200 Subject: [PATCH 04/20] Fix tests --- .../opentripplanner/ext/flex/trip/UnscheduledTripTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index fe006450791..d7708b53d30 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -192,7 +192,7 @@ void testUnscheduledTrip() { assertEquals(T15_00, trip.latestArrivalTime(STOP_B)); assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getAlightRule(STOP_B)); } @Test @@ -217,7 +217,7 @@ void testUnscheduledFeederTripFromScheduledStop() { assertEquals(T14_00, trip.latestArrivalTime(STOP_B)); assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getAlightRule(STOP_B)); } @Test @@ -237,7 +237,7 @@ void testUnscheduledFeederTripToScheduledStop() { .build(); assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_A)); - assertEquals(PickDrop.SCHEDULED, trip.getBoardRule(STOP_B)); + assertEquals(PickDrop.SCHEDULED, trip.getAlightRule(STOP_B)); } static Stream testRegularStopToAreaEarliestDepartureTimeTestCases() { From 71fa02724941dc9585baf45b5268c2dbe0c243d0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 11:59:00 +0200 Subject: [PATCH 05/20] Fix computation of boarding/alighting --- .../opentripplanner/ext/flex/trip/UnscheduledTrip.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 9b58a2c00fa..51db4d8b3dd 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -101,16 +102,16 @@ public Stream getFlexAccessTemplates( if (stopTimes.length == 1) { indices = IntStream.of(fromIndex); } else { - indices = IntStream.range(fromIndex + 1, lastIndexInTrip); + indices = IntStream.range(fromIndex + 1, lastIndexInTrip + 1); } // check for every stop after fromIndex if you can alight, if so return a template return indices - // if you cannot alight at a certain index, the trip is not possible + // if you cannot alight at an index, the trip is not possible .filter(alightIndex -> getAlightRule(alightIndex).isRoutable()) // expand GroupStops and build IndexedStopLocations .mapToObj(this::expandStops) // flatten stream of streams - .flatMap(s -> s) + .flatMap(Function.identity()) // create template .map(alightStop -> new FlexAccessTemplate( @@ -296,7 +297,7 @@ private int getFromIndex(NearbyStop accessEgress) { private int getToIndex(NearbyStop accessEgress) { for (int i = stopTimes.length - 1; i >= 0; i--) { - if (getBoardRule(i).isNotRoutable()) { + if (getAlightRule(i).isNotRoutable()) { continue; } StopLocation stop = stopTimes[i].stop(); From 4bcbf57ac61fd1ca3057a1a5a2eef46ff5629b55 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 14:28:55 +0200 Subject: [PATCH 06/20] Also compute several egress templates --- .../ext/flex/trip/UnscheduledTripTest.java | 2 +- .../ext/flex/trip/UnscheduledTrip.java | 34 +++++++++++++------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index d7708b53d30..6c3d9a6cef7 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -108,7 +108,7 @@ void testIsUnscheduledTrip() { isUnscheduledTrip(List.of(scheduledStop, scheduledStop)), "Two scheduled stop times is not unscheduled" ); - assertFalse( + assertTrue( isUnscheduledTrip(List.of(unscheduledStop, unscheduledStop, unscheduledStop)), "Three unscheduled stop times is not unscheduled" ); diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 51db4d8b3dd..403ae84618a 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -90,7 +90,7 @@ public Stream getFlexAccessTemplates( // Find boarding index, also check if it's boardable final int fromIndex = getFromIndex(access); - // Alighting is always at the last stop for unscheduled trips + // templates will be generated from the boardingIndex to the end of the trip final int lastIndexInTrip = stopTimes.length - 1; // Check if trip is possible @@ -134,27 +134,39 @@ public Stream getFlexEgressTemplates( FlexPathCalculator calculator, FlexConfig config ) { - // Boarding is always at the first stop for unscheduled trips - int fromIndex = 0; + // templates will be generated from the first index to the toIndex + int firstIndexInStop = 0; - // Find alighting index + // Find alighting index, also check if alighting is allowed int toIndex = getToIndex(egress); // Check if trip is possible - if ( - toIndex == INDEX_NOT_FOUND || fromIndex > toIndex || getBoardRule(fromIndex).isNotRoutable() - ) { + if (toIndex == INDEX_NOT_FOUND || firstIndexInStop > toIndex) { return Stream.empty(); } - return expandStops(fromIndex) - .map(indexedStop -> + IntStream indices; + if (stopTimes.length == 1) { + indices = IntStream.of(toIndex); + } else { + indices = IntStream.range(firstIndexInStop, toIndex + 1); + } + // check for every stop after fromIndex if you can alight, if so return a template + return indices + // if you cannot board at this index, the trip is not possible + .filter(boardIndex -> getBoardRule(boardIndex).isRoutable()) + // expand GroupStops and build IndexedStopLocations + .mapToObj(this::expandStops) + // flatten stream of streams + .flatMap(Function.identity()) + // create template + .map(boardStop -> new FlexEgressTemplate( egress, this, - fromIndex, toIndex, - indexedStop.stop, + boardStop.index, + boardStop.stop, date, calculator, config From 8cf74b56e6bfe63d90380736ab56f040ac70b46d Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 17:24:11 +0200 Subject: [PATCH 07/20] Reorganise tests --- .../ext/flex/GtfsFlexTest.java | 98 +++++++++++++++++ .../ext/flex/trip/UnscheduledTripTest.java | 103 +----------------- 2 files changed, 102 insertions(+), 99 deletions(-) create mode 100644 src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java new file mode 100644 index 00000000000..5065068d4a2 --- /dev/null +++ b/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java @@ -0,0 +1,98 @@ +package org.opentripplanner.ext.flex; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.opentripplanner.TestOtpModel; +import org.opentripplanner.ext.flex.trip.FlexTrip; +import org.opentripplanner.ext.flex.trip.UnscheduledTrip; +import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.standalone.config.sandbox.FlexConfig; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitModel; + +/** + * This test makes sure that one of the example feeds in the GTFS-Flex repo works. It's the City of + * Aspen Downtown taxi service which is a completely unscheduled trip that takes you door-to-door in + * the city. + *

+ * It only contains a single stop time which in GTFS static would not work but is valid in GTFS + * Flex. + */ +public class GtfsFlexTest extends FlexTest { + + private static TransitModel transitModel; + + @BeforeAll + static void setup() { + TestOtpModel model = FlexTest.buildFlexGraph(ASPEN_GTFS); + transitModel = model.transitModel(); + } + + @Test + void parseAspenTaxiAsUnscheduledTrip() { + var flexTrips = transitModel.getAllFlexTrips(); + assertFalse(flexTrips.isEmpty()); + assertEquals( + Set.of("t_1289262_b_29084_tn_0", "t_1289257_b_28352_tn_0"), + flexTrips.stream().map(FlexTrip::getId).map(FeedScopedId::getId).collect(Collectors.toSet()) + ); + + assertEquals( + Set.of(UnscheduledTrip.class), + flexTrips.stream().map(FlexTrip::getClass).collect(Collectors.toSet()) + ); + } + + @Test + void calculateAccessTemplate() { + var trip = getFlexTrip(); + var nearbyStop = getNearbyStop(trip); + + var accesses = trip + .getFlexAccessTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT) + .toList(); + + assertEquals(1, accesses.size()); + + var access = accesses.get(0); + assertEquals(0, access.fromStopIndex); + assertEquals(0, access.toStopIndex); + } + + @Test + void calculateEgressTemplate() { + var trip = getFlexTrip(); + var nearbyStop = getNearbyStop(trip); + var egresses = trip + .getFlexEgressTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT) + .toList(); + + assertEquals(1, egresses.size()); + + var egress = egresses.get(0); + assertEquals(0, egress.fromStopIndex); + assertEquals(0, egress.toStopIndex); + } + + @Test + void shouldGeneratePatternForFlexTripWithSingleStop() { + assertFalse(transitModel.getAllTripPatterns().isEmpty()); + } + private static NearbyStop getNearbyStop(FlexTrip trip) { + assertEquals(1, trip.getStops().size()); + var stopLocation = trip.getStops().iterator().next(); + return new NearbyStop(stopLocation, 0, List.of(), null); + } + + private static FlexTrip getFlexTrip() { + var flexTrips = transitModel.getAllFlexTrips(); + return flexTrips.iterator().next(); + } + +} diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 6c3d9a6cef7..1241a1e5d9e 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -9,39 +9,21 @@ import static org.opentripplanner.transit.model._data.TransitModelForTest.id; import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.Stream; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import org.locationtech.jts.geom.Coordinate; -import org.opentripplanner.TestOtpModel; -import org.opentripplanner.ext.flex.FlexTest; -import org.opentripplanner.framework.geometry.GeometryUtils; +import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; -import org.opentripplanner.routing.graphfinder.NearbyStop; -import org.opentripplanner.standalone.config.sandbox.FlexConfig; import org.opentripplanner.transit.model._data.TransitModelForTest; -import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; -import org.opentripplanner.transit.service.TransitModel; - -/** - * This test makes sure that one of the example feeds in the GTFS-Flex repo works. It's the City of - * Aspen Downtown taxi service which is a completely unscheduled trip that takes you door-to-door in - * the city. - *

- * It only contains a single stop time which in GTFS static would not work but is valid in GTFS - * Flex. - */ -public class UnscheduledTripTest extends FlexTest { + +public class UnscheduledTripTest { private static final int STOP_A = 0; private static final int STOP_B = 1; @@ -53,25 +35,8 @@ public class UnscheduledTripTest extends FlexTest { private static final StopLocation AREA_STOP = TransitModelForTest.areaStopForTest( "area", - GeometryUtils - .getGeometryFactory() - .createPolygon( - new Coordinate[] { - new Coordinate(11.0, 63.0), - new Coordinate(11.5, 63.0), - new Coordinate(11.5, 63.5), - new Coordinate(11.0, 63.5), - new Coordinate(11.0, 63.0), - } - ) + Polygons.BERLIN ); - static TransitModel transitModel; - - @BeforeAll - static void setup() { - TestOtpModel model = FlexTest.buildFlexGraph(ASPEN_GTFS); - transitModel = model.transitModel(); - } @Test void testIsUnscheduledTrip() { @@ -118,57 +83,6 @@ void testIsUnscheduledTrip() { ); } - @Test - void parseAspenTaxiAsUnscheduledTrip() { - var flexTrips = transitModel.getAllFlexTrips(); - assertFalse(flexTrips.isEmpty()); - assertEquals( - Set.of("t_1289262_b_29084_tn_0", "t_1289257_b_28352_tn_0"), - flexTrips.stream().map(FlexTrip::getId).map(FeedScopedId::getId).collect(Collectors.toSet()) - ); - - assertEquals( - Set.of(UnscheduledTrip.class), - flexTrips.stream().map(FlexTrip::getClass).collect(Collectors.toSet()) - ); - } - - @Test - void calculateAccessTemplate() { - var trip = getFlexTrip(); - var nearbyStop = getNearbyStop(trip); - - var accesses = trip - .getFlexAccessTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT) - .toList(); - - assertEquals(1, accesses.size()); - - var access = accesses.get(0); - assertEquals(0, access.fromStopIndex); - assertEquals(0, access.toStopIndex); - } - - @Test - void calculateEgressTemplate() { - var trip = getFlexTrip(); - var nearbyStop = getNearbyStop(trip); - var egresses = trip - .getFlexEgressTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT) - .toList(); - - assertEquals(1, egresses.size()); - - var egress = egresses.get(0); - assertEquals(0, egress.fromStopIndex); - assertEquals(0, egress.toStopIndex); - } - - @Test - void shouldGeneratePatternForFlexTripWithSingleStop() { - assertFalse(transitModel.getAllTripPatterns().isEmpty()); - } - @Test void testUnscheduledTrip() { var fromStopTime = new StopTime(); @@ -558,16 +472,7 @@ private static String timeToString(int time) { return TimeUtils.timeToStrCompact(time, MISSING_VALUE, "MISSING_VALUE"); } - private static NearbyStop getNearbyStop(FlexTrip trip) { - assertEquals(1, trip.getStops().size()); - var stopLocation = trip.getStops().iterator().next(); - return new NearbyStop(stopLocation, 0, List.of(), null); - } - private static FlexTrip getFlexTrip() { - var flexTrips = transitModel.getAllFlexTrips(); - return flexTrips.iterator().next(); - } private static StopTime area(String startTime, String endTime) { var stopTime = new StopTime(); From 23a690a3d07ab4e02d2455fb4a709d82483adcfe Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 17:48:40 +0200 Subject: [PATCH 08/20] Refactor Flex tests --- .../ext/flex/GtfsFlexTest.java | 2 +- .../ext/flex/trip/UnscheduledTripTest.java | 96 ++++++++++--------- .../support/VariableArgumentsProvider.java | 14 ++- 3 files changed, 65 insertions(+), 47 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java index 5065068d4a2..78c4a2f3ddc 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java @@ -84,6 +84,7 @@ void calculateEgressTemplate() { void shouldGeneratePatternForFlexTripWithSingleStop() { assertFalse(transitModel.getAllTripPatterns().isEmpty()); } + private static NearbyStop getNearbyStop(FlexTrip trip) { assertEquals(1, trip.getStops().size()); var stopLocation = trip.getStops().iterator().next(); @@ -94,5 +95,4 @@ private static NearbyStop getNearbyStop(FlexTrip trip) { var flexTrips = transitModel.getAllFlexTrips(); return flexTrips.iterator().next(); } - } diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 1241a1e5d9e..e0fcfdd00b6 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -8,10 +8,14 @@ import static org.opentripplanner.model.StopTime.MISSING_VALUE; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import java.util.stream.Stream; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.framework.time.DurationUtils; @@ -19,6 +23,7 @@ import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; +import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; @@ -38,49 +43,52 @@ public class UnscheduledTripTest { Polygons.BERLIN ); - @Test - void testIsUnscheduledTrip() { - var scheduledStop = new StopTime(); - scheduledStop.setArrivalTime(30); - scheduledStop.setDepartureTime(60); - - var unscheduledStop = new StopTime(); - unscheduledStop.setFlexWindowStart(30); - unscheduledStop.setFlexWindowEnd(300); - - assertFalse(isUnscheduledTrip(List.of()), "Empty stop times is not a unscheduled trip"); - assertFalse( - isUnscheduledTrip(List.of(scheduledStop)), - "Single scheduled stop time is not unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(unscheduledStop)), - "Single unscheduled stop time is unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(unscheduledStop, unscheduledStop)), - "Two unscheduled stop times is unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(unscheduledStop, scheduledStop)), - "Unscheduled + scheduled stop times is unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(scheduledStop, unscheduledStop)), - "Scheduled + unscheduled stop times is unscheduled" - ); - assertFalse( - isUnscheduledTrip(List.of(scheduledStop, scheduledStop)), - "Two scheduled stop times is not unscheduled" - ); - assertTrue( - isUnscheduledTrip(List.of(unscheduledStop, unscheduledStop, unscheduledStop)), - "Three unscheduled stop times is not unscheduled" - ); - assertFalse( - isUnscheduledTrip(List.of(scheduledStop, scheduledStop, scheduledStop)), - "Three scheduled stop times is not unscheduled" - ); + @Nested + class IsUnscheduledTrip { + + private static final StopTime SCHEDULED_STOP = new StopTime(); + private static final StopTime UNSCHEDULED_STOP = new StopTime(); + + static { + SCHEDULED_STOP.setArrivalTime(30); + SCHEDULED_STOP.setDepartureTime(60); + + UNSCHEDULED_STOP.setFlexWindowStart(30); + UNSCHEDULED_STOP.setFlexWindowEnd(300); + } + + static Collection notUnscheduled = Stream + .of( + List.of(), + List.of(SCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) + ) + .map(Arguments::of) + .toList(); + + @ParameterizedTest + @VariableSource("notUnscheduled") + void isNotUnscheduled(List stopTimes) { + assertFalse(isUnscheduledTrip(stopTimes)); + } + + static Collection unscheduled = Stream + .of( + List.of(UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP) + ) + .map(Arguments::of) + .collect(Collectors.toList()); + + @ParameterizedTest + @VariableSource("unscheduled") + void isUnscheduled(List stopTimes) { + assertTrue(isUnscheduledTrip(stopTimes)); + } } @Test @@ -472,8 +480,6 @@ private static String timeToString(int time) { return TimeUtils.timeToStrCompact(time, MISSING_VALUE, "MISSING_VALUE"); } - - private static StopTime area(String startTime, String endTime) { var stopTime = new StopTime(); stopTime.setStop(AREA_STOP); diff --git a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java index 1ae9fb2af84..85e2c455da6 100644 --- a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java +++ b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java @@ -1,6 +1,8 @@ package org.opentripplanner.test.support; import java.lang.reflect.Field; +import java.util.Collection; +import java.util.List; import java.util.stream.Stream; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; @@ -50,6 +52,16 @@ private Stream getValue(Field field) { field.setAccessible(accessible); - return value == null ? null : (Stream) value; + if (value == null) { + return null; + } else if (value instanceof Collection collection) { + return (Stream) collection.stream(); + } else if (value instanceof Stream stream) { + return stream; + } else { + throw new IllegalArgumentException( + "Cannot convert %s to stream.".formatted(value.getClass()) + ); + } } } From e3b78c20acd4c2c0b83eeb3d5f446c547d026df5 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 14 Sep 2023 20:43:25 +0200 Subject: [PATCH 09/20] Move test cases, cleanup StopTime --- .../ext/flex/trip/UnscheduledTripTest.java | 4 +- .../org/opentripplanner/model/StopTime.java | 47 ++++--------------- 2 files changed, 11 insertions(+), 40 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index e0fcfdd00b6..d8ccee70236 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -61,8 +61,6 @@ class IsUnscheduledTrip { .of( List.of(), List.of(SCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) ) @@ -78,6 +76,8 @@ void isNotUnscheduled(List stopTimes) { static Collection unscheduled = Stream .of( List.of(UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP) ) diff --git a/src/main/java/org/opentripplanner/model/StopTime.java b/src/main/java/org/opentripplanner/model/StopTime.java index 0ea5dfc15c0..957cc56285d 100644 --- a/src/main/java/org/opentripplanner/model/StopTime.java +++ b/src/main/java/org/opentripplanner/model/StopTime.java @@ -3,7 +3,7 @@ import java.util.List; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.framework.time.TimeUtils; +import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.StopTimeKey; import org.opentripplanner.transit.model.timetable.Trip; @@ -136,10 +136,6 @@ public void setArrivalTime(int arrivalTime) { this.arrivalTime = arrivalTime; } - public void clearArrivalTime() { - this.arrivalTime = MISSING_VALUE; - } - public boolean isDepartureTimeSet() { return departureTime != MISSING_VALUE; } @@ -155,10 +151,6 @@ public void setDepartureTime(int departureTime) { this.departureTime = departureTime; } - public void clearDepartureTime() { - this.departureTime = MISSING_VALUE; - } - public boolean isTimepointSet() { return timepoint != MISSING_VALUE; } @@ -174,10 +166,6 @@ public void setTimepoint(int timepoint) { this.timepoint = timepoint; } - public void clearTimepoint() { - this.timepoint = MISSING_VALUE; - } - public I18NString getStopHeadsign() { return stopHeadsign; } @@ -222,10 +210,6 @@ public void setShapeDistTraveled(double shapeDistTraveled) { this.shapeDistTraveled = shapeDistTraveled; } - public void clearShapeDistTraveled() { - this.shapeDistTraveled = MISSING_VALUE; - } - public String getFarePeriodId() { return farePeriodId; } @@ -309,29 +293,16 @@ public void cancel() { dropOffType = PickDrop.CANCELLED; } - public void cancelDropOff() { - dropOffType = PickDrop.CANCELLED; - } - - public void cancelPickup() { - pickupType = PickDrop.CANCELLED; - } - @Override public String toString() { - return ( - "StopTime(seq=" + - getStopSequence() + - " stop=" + - getStop().getId() + - " trip=" + - getTrip().getId() + - " times=" + - TimeUtils.timeToStrLong(getArrivalTime()) + - "-" + - TimeUtils.timeToStrLong(getDepartureTime()) + - ")" - ); + return ToStringBuilder + .of(this.getClass()) + .addNum("seq", stopSequence) + .addObj("stop", stop) + .addObj("trip", trip) + .addServiceTime("arrival", arrivalTime) + .addServiceTime("departure", departureTime) + .toString(); } private static int getAvailableTime(int... times) { From cf47a0442a07b64546a75fb570406c2ef3c89d0c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 15 Sep 2023 15:56:06 +0200 Subject: [PATCH 10/20] Update test --- .../opentripplanner/model/impl/OtpTransitServiceImplTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java index 9ecdbbeb451..7a464995ba6 100644 --- a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java +++ b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java @@ -117,7 +117,7 @@ public void testGetAllStopTimes() { assertEquals(88, stopTimes.size()); assertEquals( - "StopTime(seq=1 stop=F:A trip=agency:1.1 times=00:00:00-00:00:00)", + "StopTime{seq: 1, stop: RegularStop{F:A A}, trip: Trip{agency:1.1 1}, arrival: 0:00, departure: 0:00}", first(stopTimes).toString() ); } From 1f1db518e98dfc1dfa1f701730928a5aa03d75e7 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 16 Sep 2023 20:38:53 +0200 Subject: [PATCH 11/20] Improve isUnscheduledTrip check --- .../ext/flex/trip/UnscheduledTripTest.java | 16 +++++----- .../org/opentripplanner/ext/flex/README.md | 4 +-- .../ext/flex/trip/UnscheduledTrip.java | 29 ++++++++++++++----- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index d8ccee70236..1bedecb5435 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -8,9 +8,8 @@ import static org.opentripplanner.model.StopTime.MISSING_VALUE; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; -import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -57,15 +56,14 @@ class IsUnscheduledTrip { UNSCHEDULED_STOP.setFlexWindowEnd(300); } - static Collection notUnscheduled = Stream + static Stream notUnscheduled = Stream .of( List.of(), List.of(SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) ) - .map(Arguments::of) - .toList(); + .map(Arguments::of); @ParameterizedTest @VariableSource("notUnscheduled") @@ -73,16 +71,16 @@ void isNotUnscheduled(List stopTimes) { assertFalse(isUnscheduledTrip(stopTimes)); } - static Collection unscheduled = Stream + static Stream unscheduled = Stream .of( List.of(UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP) + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), + Collections.nCopies(10, UNSCHEDULED_STOP) ) - .map(Arguments::of) - .collect(Collectors.toList()); + .map(Arguments::of); @ParameterizedTest @VariableSource("unscheduled") diff --git a/src/ext/java/org/opentripplanner/ext/flex/README.md b/src/ext/java/org/opentripplanner/ext/flex/README.md index 8f099279450..a6d161c3088 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/README.md +++ b/src/ext/java/org/opentripplanner/ext/flex/README.md @@ -14,7 +14,7 @@ The algorithm runs in two phases. 1. Flex access/egress templates are generated from the places reachable by the access/egress mode. The templates contain information about how to reach the flex service and the service itself. -1. From these templates, flex accesses are generated by alighting at all places after the boarding. +2. From these templates, flex accesses are generated by alighting at all places after the boarding. If the alighting is not at a stop, transfers are generated to all stops reachable by walking distance. For egresses the flow is the opposite. @@ -29,7 +29,7 @@ FlexTrip class, with the required methods. #### UnscheduledTrip -This type of trip consists of one or two service areas, with a set operating window. The trip is +This type of trip consists any number of service areas, with a set operating window. The trip is possible at any time within the set window, and the class can represent multiple potential trips. #### ScheduledDeviatedTrip diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 403ae84618a..c72148dcc98 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -36,8 +36,7 @@ */ public class UnscheduledTrip extends FlexTrip { - // unscheduled trips can contain one or two stop_times - private static final Set N_STOPS = Set.of(1, 2, 3); + private static final Set N_STOPS = Set.of(1, 2); private static final int INDEX_NOT_FOUND = -1; private final StopTimeWindow[] stopTimes; @@ -68,16 +67,32 @@ public static UnscheduledTripBuilder of(FeedScopedId id) { return new UnscheduledTripBuilder(id); } + /** + * Tests if the stop times constitute an {@link UnscheduledTrip}. + *

+ * Returns true for the following cases: + * - A single fixed scheduled stop followed by a flexible one + * - One or more stop times with a flexible time window but no fixed stop in between them + */ public static boolean isUnscheduledTrip(List stopTimes) { Predicate hasFlexWindow = st -> st.getFlexWindowStart() != MISSING_VALUE || st.getFlexWindowEnd() != MISSING_VALUE; Predicate notContinuousStop = stopTime -> stopTime.getFlexContinuousDropOff() == NONE && stopTime.getFlexContinuousPickup() == NONE; - return ( - N_STOPS.contains(stopTimes.size()) && - stopTimes.stream().anyMatch(hasFlexWindow) && - stopTimes.stream().allMatch(notContinuousStop) - ); + if (stopTimes.isEmpty()) { + return false; + } + if (N_STOPS.contains(stopTimes.size())) { + return ( + N_STOPS.contains(stopTimes.size()) && + stopTimes.stream().anyMatch(hasFlexWindow) && + stopTimes.stream().allMatch(notContinuousStop) + ); + } else { + return ( + stopTimes.stream().allMatch(hasFlexWindow) && stopTimes.stream().allMatch(notContinuousStop) + ); + } } @Override From 04da7a179c22e40909b1e183c4d24a66269577a0 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 16 Sep 2023 20:58:37 +0200 Subject: [PATCH 12/20] Automatically convert to Arguments --- .../ext/flex/trip/UnscheduledTripTest.java | 33 ++++++++----------- .../ext/flex/trip/UnscheduledTrip.java | 10 +++--- .../support/VariableArgumentsProvider.java | 18 ++++++++-- 3 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 1bedecb5435..3b8819fb31b 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -14,7 +14,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.framework.time.DurationUtils; @@ -56,14 +55,12 @@ class IsUnscheduledTrip { UNSCHEDULED_STOP.setFlexWindowEnd(300); } - static Stream notUnscheduled = Stream - .of( - List.of(), - List.of(SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) - ) - .map(Arguments::of); + static List> notUnscheduled = List.of( + List.of(), + List.of(SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) + ); @ParameterizedTest @VariableSource("notUnscheduled") @@ -71,16 +68,14 @@ void isNotUnscheduled(List stopTimes) { assertFalse(isUnscheduledTrip(stopTimes)); } - static Stream unscheduled = Stream - .of( - List.of(UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), - Collections.nCopies(10, UNSCHEDULED_STOP) - ) - .map(Arguments::of); + static List> unscheduled = List.of( + List.of(UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), + Collections.nCopies(10, UNSCHEDULED_STOP) + ); @ParameterizedTest @VariableSource("unscheduled") diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index c72148dcc98..d3a749204c9 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -79,19 +79,17 @@ public static boolean isUnscheduledTrip(List stopTimes) { st.getFlexWindowStart() != MISSING_VALUE || st.getFlexWindowEnd() != MISSING_VALUE; Predicate notContinuousStop = stopTime -> stopTime.getFlexContinuousDropOff() == NONE && stopTime.getFlexContinuousPickup() == NONE; + boolean noContinuousStops = stopTimes.stream().allMatch(notContinuousStop); if (stopTimes.isEmpty()) { return false; - } - if (N_STOPS.contains(stopTimes.size())) { + } else if (N_STOPS.contains(stopTimes.size())) { return ( N_STOPS.contains(stopTimes.size()) && stopTimes.stream().anyMatch(hasFlexWindow) && - stopTimes.stream().allMatch(notContinuousStop) + noContinuousStops ); } else { - return ( - stopTimes.stream().allMatch(hasFlexWindow) && stopTimes.stream().allMatch(notContinuousStop) - ); + return stopTimes.stream().allMatch(hasFlexWindow) && noContinuousStops; } } diff --git a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java index 85e2c455da6..0d4ba1071a3 100644 --- a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java +++ b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java @@ -2,8 +2,9 @@ import java.lang.reflect.Field; import java.util.Collection; -import java.util.List; +import java.util.function.Function; import java.util.stream.Stream; +import javax.annotation.Nonnull; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; @@ -55,13 +56,24 @@ private Stream getValue(Field field) { if (value == null) { return null; } else if (value instanceof Collection collection) { - return (Stream) collection.stream(); + return collection.stream().map(toArguments()); } else if (value instanceof Stream stream) { - return stream; + return stream.map(toArguments()); } else { throw new IllegalArgumentException( "Cannot convert %s to stream.".formatted(value.getClass()) ); } } + + @Nonnull + private static Function toArguments() { + return val -> { + if (val instanceof Arguments arguments) { + return arguments; + } else { + return Arguments.of(val); + } + }; + } } From 105cc5fc7d0003d918f360f6bf09e8bfa6b31907 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Sat, 16 Sep 2023 21:51:39 +0200 Subject: [PATCH 13/20] Add test case for computing access templates --- .../ext/flex/trip/UnscheduledTripTest.java | 97 ++++++++++++++++++- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 3b8819fb31b..8be6aa294fa 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -8,19 +8,26 @@ import static org.opentripplanner.model.StopTime.MISSING_VALUE; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; +import gnu.trove.set.hash.TIntHashSet; +import java.time.LocalDate; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner._support.geometry.Polygons; +import org.opentripplanner.ext.flex.FlexServiceDate; +import org.opentripplanner.ext.flex.flexpathcalculator.DirectFlexPathCalculator; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; import org.opentripplanner.model.PickDrop; import org.opentripplanner.model.StopTime; +import org.opentripplanner.routing.graphfinder.NearbyStop; +import org.opentripplanner.standalone.config.sandbox.FlexConfig; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.site.RegularStop; @@ -30,6 +37,7 @@ public class UnscheduledTripTest { private static final int STOP_A = 0; private static final int STOP_B = 1; + private static final int STOP_C = 2; private static final int T10_00 = TimeUtils.hm2time(10, 0); private static final int T11_00 = TimeUtils.hm2time(11, 0); private static final int T14_00 = TimeUtils.hm2time(14, 0); @@ -55,7 +63,7 @@ class IsUnscheduledTrip { UNSCHEDULED_STOP.setFlexWindowEnd(300); } - static List> notUnscheduled = List.of( + static final List> notUnscheduled = List.of( List.of(), List.of(SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP), @@ -68,7 +76,7 @@ void isNotUnscheduled(List stopTimes) { assertFalse(isUnscheduledTrip(stopTimes)); } - static List> unscheduled = List.of( + static final List> unscheduled = List.of( List.of(UNSCHEDULED_STOP), List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), @@ -469,6 +477,81 @@ void testAreaToAreaLatestArrivalTime(TestCase tc) { ); } + static Stream multipleAreasEarliestDepartureTimeTestCases() { + var from = area("10:00", "10:05"); + var middle = area("10:10", "10:15"); + var to = area("10:20", "10:25"); + + var tc = new TestCase.Builder(from, to).withStopTimes(List.of(from, middle, to)); + + return Stream.of( + tc + .expected( + "Requested departure time is after flex service departure window start, duration 21m", + "10:01" + ) + .request("10:01", "21m") + .build(), + tc + .expected( + "Requested departure time is before flex service departure window start, duration 1h", + "10:00" + ) + .request("09:50", "24m") + .build() + ); + } + + @ParameterizedTest + @MethodSource("multipleAreasEarliestDepartureTimeTestCases") + void testMultipleAreasEarliestDepartureTime(TestCase tc) { + assertEquals( + timeToString(tc.expectedTime), + timeToString( + tc.trip().earliestDepartureTime(tc.requestedTime, STOP_A, STOP_C, tc.tripDuration) + ) + ); + } + + @Nested + class FlexTemplates { + + private static final DirectFlexPathCalculator CALCULATOR = new DirectFlexPathCalculator(); + + @Test + void templates() { + var from = area("10:00", "10:05"); + var middle = area("10:10", "10:15"); + var to = area("10:20", "10:25"); + + var trip = new TestCase.Builder(from, to) + .withStopTimes(List.of(from, middle, to)) + .build() + .trip(); + + var nearbyStop = new NearbyStop(to.getStop(), 100, List.of(), null); + var flexServiceDate = new FlexServiceDate( + LocalDate.of(2023, 9, 16), + to.getFlexWindowStart(), + new TIntHashSet() + ); + + var templates = trip + .getFlexAccessTemplates(nearbyStop, flexServiceDate, CALCULATOR, FlexConfig.DEFAULT) + .toList(); + + assertEquals(2, templates.size()); + + var first = templates.get(0); + assertEquals(first.fromStopIndex, 0); + assertEquals(first.toStopIndex, 1); + + var second = templates.get(1); + assertEquals(second.fromStopIndex, 0); + assertEquals(second.toStopIndex, 2); + } + } + private static String timeToString(int time) { return TimeUtils.timeToStrCompact(time, MISSING_VALUE, "MISSING_VALUE"); } @@ -500,6 +583,7 @@ private static StopTime regularStopTime(int arrivalTime, int departureTime) { record TestCase( StopTime from, StopTime to, + List stopTimes, String expectedDescription, int expectedTime, int requestedTime, @@ -510,7 +594,7 @@ static Builder tc(StopTime start, StopTime end) { } UnscheduledTrip trip() { - return UnscheduledTrip.of(id("UNSCHEDULED")).withStopTimes(List.of(from, to)).build(); + return UnscheduledTrip.of(id("UNSCHEDULED")).withStopTimes(stopTimes).build(); } @Override @@ -549,6 +633,7 @@ private static class Builder { private final StopTime from; private final StopTime to; private String expectedDescription; + private List stopTimes; private int expectedTime; private int requestedTime; private int tripDuration; @@ -558,6 +643,11 @@ public Builder(StopTime from, StopTime to) { this.to = to; } + public Builder withStopTimes(List stopTimes) { + this.stopTimes = stopTimes; + return this; + } + Builder expected(String expectedDescription, String expectedTime) { this.expectedDescription = expectedDescription; this.expectedTime = TimeUtils.time(expectedTime); @@ -580,6 +670,7 @@ TestCase build() { return new TestCase( from, to, + Objects.requireNonNullElse(stopTimes, List.of(from, to)), expectedDescription, expectedTime, requestedTime, From adef349d25069b8773f19780947c4bda150bd98c Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 18 Sep 2023 10:00:00 +0200 Subject: [PATCH 14/20] Improve tests --- .../ext/flex/trip/UnscheduledTripTest.java | 70 +++++++++++++------ 1 file changed, 48 insertions(+), 22 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 8be6aa294fa..f13449cb727 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Objects; import java.util.stream.Stream; +import javax.annotation.Nonnull; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -517,38 +518,63 @@ void testMultipleAreasEarliestDepartureTime(TestCase tc) { class FlexTemplates { private static final DirectFlexPathCalculator CALCULATOR = new DirectFlexPathCalculator(); + static final StopTime FIRST = area("10:00", "10:05"); + static final StopTime SECOND = area("10:10", "10:15"); + static final StopTime THIRD = area("10:20", "10:25"); + static final StopTime FOURTH = area("10:30", "10:35"); + private static final FlexServiceDate FLEX_SERVICE_DATE = new FlexServiceDate( + LocalDate.of(2023, 9, 16), + 0, + new TIntHashSet() + ); @Test - void templates() { - var from = area("10:00", "10:05"); - var middle = area("10:10", "10:15"); - var to = area("10:20", "10:25"); + void accessTemplates() { + var trip = trip(List.of(FIRST, SECOND, THIRD, FOURTH)); - var trip = new TestCase.Builder(from, to) - .withStopTimes(List.of(from, middle, to)) - .build() - .trip(); + var nearbyStop = new NearbyStop(FOURTH.getStop(), 100, List.of(), null); - var nearbyStop = new NearbyStop(to.getStop(), 100, List.of(), null); - var flexServiceDate = new FlexServiceDate( - LocalDate.of(2023, 9, 16), - to.getFlexWindowStart(), - new TIntHashSet() - ); + var templates = trip + .getFlexAccessTemplates(nearbyStop, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) + .toList(); + + assertEquals(3, templates.size()); + + List + .of(0, 1, 2) + .forEach(index -> { + var template = templates.get(index); + assertEquals(template.fromStopIndex, 0); + assertEquals(template.toStopIndex, index + 1); + }); + } + + @Test + void accessTemplatesNoAlighting() { + var second = area("10:10", "10:15"); + second.setDropOffType(PickDrop.NONE); + + var trip = trip(List.of(FIRST, second, THIRD, FOURTH)); + + var nearbyStop = new NearbyStop(FOURTH.getStop(), 100, List.of(), null); var templates = trip - .getFlexAccessTemplates(nearbyStop, flexServiceDate, CALCULATOR, FlexConfig.DEFAULT) + .getFlexAccessTemplates(nearbyStop, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) .toList(); assertEquals(2, templates.size()); + List + .of(0, 1) + .forEach(index -> { + var template = templates.get(index); + assertEquals(template.fromStopIndex, 0); + assertEquals(template.toStopIndex, index + 2); + }); + } - var first = templates.get(0); - assertEquals(first.fromStopIndex, 0); - assertEquals(first.toStopIndex, 1); - - var second = templates.get(1); - assertEquals(second.fromStopIndex, 0); - assertEquals(second.toStopIndex, 2); + @Nonnull + private static UnscheduledTrip trip(List stopTimes) { + return new TestCase.Builder(FIRST, THIRD).withStopTimes(stopTimes).build().trip(); } } From 2d70974b553f617657a56e41a3cc59c3f4ed1063 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Mon, 18 Sep 2023 12:09:36 +0200 Subject: [PATCH 15/20] Add tests for egress --- .../ext/flex/trip/UnscheduledTripTest.java | 50 ++++++++++++++----- .../ext/flex/trip/UnscheduledTrip.java | 6 +-- .../org/opentripplanner/model/plan/Leg.java | 1 - 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index f13449cb727..065daa652ab 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -22,6 +22,8 @@ import org.opentripplanner._support.geometry.Polygons; import org.opentripplanner.ext.flex.FlexServiceDate; import org.opentripplanner.ext.flex.flexpathcalculator.DirectFlexPathCalculator; +import org.opentripplanner.ext.flex.template.FlexAccessTemplate; +import org.opentripplanner.ext.flex.template.FlexEgressTemplate; import org.opentripplanner.framework.time.DurationUtils; import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.framework.tostring.ToStringBuilder; @@ -527,16 +529,18 @@ class FlexTemplates { 0, new TIntHashSet() ); + private static final NearbyStop NEARBY_STOP = new NearbyStop( + FOURTH.getStop(), + 100, + List.of(), + null + ); @Test void accessTemplates() { var trip = trip(List.of(FIRST, SECOND, THIRD, FOURTH)); - var nearbyStop = new NearbyStop(FOURTH.getStop(), 100, List.of(), null); - - var templates = trip - .getFlexAccessTemplates(nearbyStop, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) - .toList(); + var templates = accessTemplates(trip); assertEquals(3, templates.size()); @@ -544,8 +548,8 @@ void accessTemplates() { .of(0, 1, 2) .forEach(index -> { var template = templates.get(index); - assertEquals(template.fromStopIndex, 0); - assertEquals(template.toStopIndex, index + 1); + assertEquals(0, template.fromStopIndex); + assertEquals(index + 1, template.toStopIndex); }); } @@ -556,11 +560,7 @@ void accessTemplatesNoAlighting() { var trip = trip(List.of(FIRST, second, THIRD, FOURTH)); - var nearbyStop = new NearbyStop(FOURTH.getStop(), 100, List.of(), null); - - var templates = trip - .getFlexAccessTemplates(nearbyStop, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) - .toList(); + var templates = accessTemplates(trip); assertEquals(2, templates.size()); List @@ -572,10 +572,36 @@ void accessTemplatesNoAlighting() { }); } + @Test + void egressTemplates() { + var trip = trip(List.of(FIRST, SECOND, THIRD, FOURTH)); + + var templates = egressTemplates(trip); + + assertEquals(4, templates.size()); + var template = templates.get(0); + assertEquals(template.fromStopIndex, 3); + assertEquals(template.toStopIndex, 0); + } + @Nonnull private static UnscheduledTrip trip(List stopTimes) { return new TestCase.Builder(FIRST, THIRD).withStopTimes(stopTimes).build().trip(); } + + @Nonnull + private static List accessTemplates(UnscheduledTrip trip) { + return trip + .getFlexAccessTemplates(NEARBY_STOP, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) + .toList(); + } + + @Nonnull + private static List egressTemplates(UnscheduledTrip trip) { + return trip + .getFlexEgressTemplates(NEARBY_STOP, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT) + .toList(); + } } private static String timeToString(int time) { diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index d3a749204c9..9871e796633 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -148,13 +148,13 @@ public Stream getFlexEgressTemplates( FlexConfig config ) { // templates will be generated from the first index to the toIndex - int firstIndexInStop = 0; + int firstIndexInTrip = 0; // Find alighting index, also check if alighting is allowed int toIndex = getToIndex(egress); // Check if trip is possible - if (toIndex == INDEX_NOT_FOUND || firstIndexInStop > toIndex) { + if (toIndex == INDEX_NOT_FOUND || firstIndexInTrip > toIndex) { return Stream.empty(); } @@ -162,7 +162,7 @@ public Stream getFlexEgressTemplates( if (stopTimes.length == 1) { indices = IntStream.of(toIndex); } else { - indices = IntStream.range(firstIndexInStop, toIndex + 1); + indices = IntStream.range(firstIndexInTrip, toIndex + 1); } // check for every stop after fromIndex if you can alight, if so return a template return indices diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java index aa9949dae24..f561f2713e1 100644 --- a/src/main/java/org/opentripplanner/model/plan/Leg.java +++ b/src/main/java/org/opentripplanner/model/plan/Leg.java @@ -19,7 +19,6 @@ import org.opentripplanner.routing.alertpatch.TransitAlert; import org.opentripplanner.street.model.note.StreetNote; import org.opentripplanner.transit.model.basic.Accessibility; -import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.model.network.Route; import org.opentripplanner.transit.model.organization.Agency; import org.opentripplanner.transit.model.organization.Operator; From 8df5c71cab8bbf478f330cb6b10200e29eff7481 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Tue, 26 Sep 2023 12:38:46 +0200 Subject: [PATCH 16/20] Add test for boarding and alighting --- .../ext/flex/trip/UnscheduledTripTest.java | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 065daa652ab..87b1fd6e2b5 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.opentripplanner.ext.flex.trip.UnscheduledTrip.isUnscheduledTrip; import static org.opentripplanner.ext.flex.trip.UnscheduledTripTest.TestCase.tc; +import static org.opentripplanner.model.PickDrop.NONE; import static org.opentripplanner.model.StopTime.MISSING_VALUE; import static org.opentripplanner.transit.model._data.TransitModelForTest.id; @@ -33,6 +34,7 @@ import org.opentripplanner.standalone.config.sandbox.FlexConfig; import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.model.site.RegularStop; import org.opentripplanner.transit.model.site.StopLocation; @@ -516,6 +518,31 @@ void testMultipleAreasEarliestDepartureTime(TestCase tc) { ); } + @Test + void boardingAlighting() { + var AREA_STOP1 = TransitModelForTest.areaStopForTest("area-1", Polygons.BERLIN); + var AREA_STOP2 = TransitModelForTest.areaStopForTest("area-2", Polygons.BERLIN); + var AREA_STOP3 = TransitModelForTest.areaStopForTest("area-3", Polygons.BERLIN); + + var first = area(AREA_STOP1, "10:00", "10:05"); + first.setDropOffType(NONE); + var second = area(AREA_STOP2, "10:10", "10:15"); + second.setPickupType(NONE); + var third = area(AREA_STOP3, "10:20", "10:25"); + + var trip = TestCase + .tc(first, third) + .withStopTimes(List.of(first, second, third)) + .build() + .trip(); + + assertTrue(trip.isBoardingPossible(nearbyStop(AREA_STOP1))); + assertFalse(trip.isAlightingPossible(nearbyStop(AREA_STOP1))); + + assertFalse(trip.isBoardingPossible(nearbyStop(AREA_STOP2))); + assertTrue(trip.isAlightingPossible(nearbyStop(AREA_STOP2))); + } + @Nested class FlexTemplates { @@ -556,7 +583,7 @@ void accessTemplates() { @Test void accessTemplatesNoAlighting() { var second = area("10:10", "10:15"); - second.setDropOffType(PickDrop.NONE); + second.setDropOffType(NONE); var trip = trip(List.of(FIRST, second, THIRD, FOURTH)); @@ -609,8 +636,13 @@ private static String timeToString(int time) { } private static StopTime area(String startTime, String endTime) { + return area(AREA_STOP, endTime, startTime); + } + + @Nonnull + private static StopTime area(StopLocation areaStop, String endTime, String startTime) { var stopTime = new StopTime(); - stopTime.setStop(AREA_STOP); + stopTime.setStop(areaStop); stopTime.setFlexWindowStart(TimeUtils.time(startTime)); stopTime.setFlexWindowEnd(TimeUtils.time(endTime)); return stopTime; @@ -632,6 +664,11 @@ private static StopTime regularStopTime(int arrivalTime, int departureTime) { return stopTime; } + @Nonnull + private static NearbyStop nearbyStop(AreaStop stop) { + return new NearbyStop(stop, 1000, List.of(), null); + } + record TestCase( StopTime from, StopTime to, From bbaa303bcf05ed12e2a74e72998f9c3d62236a3e Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Thu, 5 Oct 2023 14:39:13 +0200 Subject: [PATCH 17/20] Add Javadoc to FlexAccessEgressTemplate --- .../ext/flex/template/FlexAccessEgressTemplate.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java index d9f08f512ae..68f490f775f 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java +++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java @@ -27,6 +27,12 @@ import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.TransitService; +/** + * A container for a few pieces of information that can be used to calculate flex accesses, egresses, + * direct flex itineraries or polylines. + *

+ * Please also see Flex.svg for an illustration of how the flex concepts relate to each other. + */ public abstract class FlexAccessEgressTemplate { /** From dd2b2ffad869c520e51134594b51944cd6de0e42 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Wed, 11 Oct 2023 12:19:07 +0200 Subject: [PATCH 18/20] Update documentation --- .../opentripplanner/ext/flex/trip/UnscheduledTrip.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 9871e796633..372d102a235 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -29,10 +29,14 @@ import org.opentripplanner.transit.model.site.StopLocation; /** - * This type of FlexTrip is used when a taxi-type service is modeled, which operates in one or - * between two areas/groups of stops without a set schedule. The travel times are calculated based + * This type of FlexTrip is used when a taxi-type service is modeled, which operates in any number + * od areas/groups of stops without a set schedule. The travel times are calculated based * on the driving time between the stops, with the schedule times being used just for deciding if a * trip is possible. + *

+ * An unscheduled flex trip may visit/drive from one flex stops(areas/group of stop locations) to + * any other stop in the pattern without driving through the stops in between. Only the times in the + * two stops used need to match the path. */ public class UnscheduledTrip extends FlexTrip { From bb38a6c3d62ebcf7b64db26a93dd9f48967a3404 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 13 Oct 2023 23:24:49 +0200 Subject: [PATCH 19/20] Apply review feedback --- .../ext/flex/trip/UnscheduledTripTest.java | 25 ++++++++++++++++++- .../org/opentripplanner/ext/flex/README.md | 12 ++++++++- .../ext/flex/trip/UnscheduledTrip.java | 17 ++++++------- .../org/opentripplanner/model/StopTime.java | 23 ++++++++++------- .../model/impl/OtpTransitServiceImplTest.java | 2 +- 5 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index 87b1fd6e2b5..a9a486aef25 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -59,20 +59,43 @@ class IsUnscheduledTrip { private static final StopTime SCHEDULED_STOP = new StopTime(); private static final StopTime UNSCHEDULED_STOP = new StopTime(); + private static final StopTime CONTINUOUS_PICKUP_STOP = new StopTime(); + private static final StopTime CONTINUOUS_DROP_OFF_STOP = new StopTime(); static { + var trip = TransitModelForTest.trip("flex").build(); SCHEDULED_STOP.setArrivalTime(30); SCHEDULED_STOP.setDepartureTime(60); + SCHEDULED_STOP.setStop(AREA_STOP); + SCHEDULED_STOP.setTrip(trip); UNSCHEDULED_STOP.setFlexWindowStart(30); UNSCHEDULED_STOP.setFlexWindowEnd(300); + UNSCHEDULED_STOP.setStop(AREA_STOP); + UNSCHEDULED_STOP.setTrip(trip); + + CONTINUOUS_PICKUP_STOP.setFlexContinuousPickup(PickDrop.COORDINATE_WITH_DRIVER); + CONTINUOUS_PICKUP_STOP.setFlexWindowStart(30); + CONTINUOUS_PICKUP_STOP.setFlexWindowEnd(300); + CONTINUOUS_PICKUP_STOP.setStop(AREA_STOP); + CONTINUOUS_PICKUP_STOP.setTrip(trip); + + CONTINUOUS_DROP_OFF_STOP.setFlexContinuousDropOff(PickDrop.COORDINATE_WITH_DRIVER); + CONTINUOUS_DROP_OFF_STOP.setFlexWindowStart(100); + CONTINUOUS_DROP_OFF_STOP.setFlexWindowEnd(200); + CONTINUOUS_DROP_OFF_STOP.setStop(AREA_STOP); + CONTINUOUS_DROP_OFF_STOP.setTrip(trip); } static final List> notUnscheduled = List.of( List.of(), List.of(SCHEDULED_STOP), List.of(SCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP) + List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, CONTINUOUS_PICKUP_STOP), + List.of(UNSCHEDULED_STOP, CONTINUOUS_DROP_OFF_STOP), + List.of(CONTINUOUS_PICKUP_STOP, CONTINUOUS_DROP_OFF_STOP) ); @ParameterizedTest diff --git a/src/ext/java/org/opentripplanner/ext/flex/README.md b/src/ext/java/org/opentripplanner/ext/flex/README.md index a6d161c3088..f6db9fba84c 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/README.md +++ b/src/ext/java/org/opentripplanner/ext/flex/README.md @@ -14,7 +14,7 @@ The algorithm runs in two phases. 1. Flex access/egress templates are generated from the places reachable by the access/egress mode. The templates contain information about how to reach the flex service and the service itself. -2. From these templates, flex accesses are generated by alighting at all places after the boarding. +1. From these templates, flex accesses are generated by alighting at all places after the boarding. If the alighting is not at a stop, transfers are generated to all stops reachable by walking distance. For egresses the flow is the opposite. @@ -32,6 +32,16 @@ FlexTrip class, with the required methods. This type of trip consists any number of service areas, with a set operating window. The trip is possible at any time within the set window, and the class can represent multiple potential trips. +You can travel from a stop time to any other stop time that comes later in the order of stop times +but not the other way around (even when the time windows would otherwise allow this). If you want +this to happen then you need to create a trip going the opposite direction. + +Note, that in a case of three or more flex zones the GTFS Flex spec doesn't exactly specify what +the driving duration is to go from the first to the third zone or if indeed the second zone needs to +be passed through. + +This topic is discussed in https://github.com/MobilityData/gtfs-flex/issues/76 + #### ScheduledDeviatedTrip A scheduled deviated trip represents a single trip, which operates not only via stops, but also via diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index 372d102a235..f3a050bb224 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -37,6 +37,8 @@ * An unscheduled flex trip may visit/drive from one flex stops(areas/group of stop locations) to * any other stop in the pattern without driving through the stops in between. Only the times in the * two stops used need to match the path. + *

+ * For a discussion of this behaviour see https://github.com/MobilityData/gtfs-flex/issues/76 */ public class UnscheduledTrip extends FlexTrip { @@ -81,19 +83,16 @@ public static UnscheduledTripBuilder of(FeedScopedId id) { public static boolean isUnscheduledTrip(List stopTimes) { Predicate hasFlexWindow = st -> st.getFlexWindowStart() != MISSING_VALUE || st.getFlexWindowEnd() != MISSING_VALUE; - Predicate notContinuousStop = stopTime -> - stopTime.getFlexContinuousDropOff() == NONE && stopTime.getFlexContinuousPickup() == NONE; - boolean noContinuousStops = stopTimes.stream().allMatch(notContinuousStop); + Predicate hasContinuousStops = stopTime -> + stopTime.getFlexContinuousDropOff() != NONE || stopTime.getFlexContinuousPickup() != NONE; if (stopTimes.isEmpty()) { return false; + } else if (stopTimes.stream().anyMatch(hasContinuousStops)) { + return false; } else if (N_STOPS.contains(stopTimes.size())) { - return ( - N_STOPS.contains(stopTimes.size()) && - stopTimes.stream().anyMatch(hasFlexWindow) && - noContinuousStops - ); + return stopTimes.stream().anyMatch(hasFlexWindow); } else { - return stopTimes.stream().allMatch(hasFlexWindow) && noContinuousStops; + return stopTimes.stream().allMatch(hasFlexWindow); } } diff --git a/src/main/java/org/opentripplanner/model/StopTime.java b/src/main/java/org/opentripplanner/model/StopTime.java index 957cc56285d..2ae04484426 100644 --- a/src/main/java/org/opentripplanner/model/StopTime.java +++ b/src/main/java/org/opentripplanner/model/StopTime.java @@ -3,7 +3,7 @@ import java.util.List; import org.opentripplanner.framework.i18n.I18NString; -import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.framework.time.TimeUtils; import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.model.timetable.StopTimeKey; import org.opentripplanner.transit.model.timetable.Trip; @@ -295,14 +295,19 @@ public void cancel() { @Override public String toString() { - return ToStringBuilder - .of(this.getClass()) - .addNum("seq", stopSequence) - .addObj("stop", stop) - .addObj("trip", trip) - .addServiceTime("arrival", arrivalTime) - .addServiceTime("departure", departureTime) - .toString(); + return ( + "StopTime(seq=" + + getStopSequence() + + " stop=" + + getStop().getId() + + " trip=" + + getTrip().getId() + + " times=" + + TimeUtils.timeToStrLong(getArrivalTime()) + + "-" + + TimeUtils.timeToStrLong(getDepartureTime()) + + ")" + ); } private static int getAvailableTime(int... times) { diff --git a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java index 7a464995ba6..9ecdbbeb451 100644 --- a/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java +++ b/src/test/java/org/opentripplanner/model/impl/OtpTransitServiceImplTest.java @@ -117,7 +117,7 @@ public void testGetAllStopTimes() { assertEquals(88, stopTimes.size()); assertEquals( - "StopTime{seq: 1, stop: RegularStop{F:A A}, trip: Trip{agency:1.1 1}, arrival: 0:00, departure: 0:00}", + "StopTime(seq=1 stop=F:A trip=agency:1.1 times=00:00:00-00:00:00)", first(stopTimes).toString() ); } From 2ba658bdb78d9cd69fd967c0229d945f6d396889 Mon Sep 17 00:00:00 2001 From: Leonard Ehrenfried Date: Fri, 13 Oct 2023 23:33:54 +0200 Subject: [PATCH 20/20] Just use @MethodSource as it doesn't need the Arguments wrapper --- .../ext/flex/trip/UnscheduledTripTest.java | 45 ++++++++++--------- .../ext/flex/trip/UnscheduledTrip.java | 2 +- .../support/VariableArgumentsProvider.java | 26 +---------- 3 files changed, 26 insertions(+), 47 deletions(-) diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java index a9a486aef25..47b9fda6568 100644 --- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java @@ -32,7 +32,6 @@ import org.opentripplanner.model.StopTime; import org.opentripplanner.routing.graphfinder.NearbyStop; import org.opentripplanner.standalone.config.sandbox.FlexConfig; -import org.opentripplanner.test.support.VariableSource; import org.opentripplanner.transit.model._data.TransitModelForTest; import org.opentripplanner.transit.model.site.AreaStop; import org.opentripplanner.transit.model.site.RegularStop; @@ -87,34 +86,38 @@ class IsUnscheduledTrip { CONTINUOUS_DROP_OFF_STOP.setTrip(trip); } - static final List> notUnscheduled = List.of( - List.of(), - List.of(SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, SCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, CONTINUOUS_PICKUP_STOP), - List.of(UNSCHEDULED_STOP, CONTINUOUS_DROP_OFF_STOP), - List.of(CONTINUOUS_PICKUP_STOP, CONTINUOUS_DROP_OFF_STOP) - ); + static List> notUnscheduled() { + return List.of( + List.of(), + List.of(SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, CONTINUOUS_PICKUP_STOP), + List.of(UNSCHEDULED_STOP, CONTINUOUS_DROP_OFF_STOP), + List.of(CONTINUOUS_PICKUP_STOP, CONTINUOUS_DROP_OFF_STOP) + ); + } @ParameterizedTest - @VariableSource("notUnscheduled") + @MethodSource("notUnscheduled") void isNotUnscheduled(List stopTimes) { assertFalse(isUnscheduledTrip(stopTimes)); } - static final List> unscheduled = List.of( - List.of(UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), - List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), - List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), - Collections.nCopies(10, UNSCHEDULED_STOP) - ); + static List> unscheduled() { + return List.of( + List.of(UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, SCHEDULED_STOP), + List.of(SCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP), + List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP), + Collections.nCopies(10, UNSCHEDULED_STOP) + ); + } @ParameterizedTest - @VariableSource("unscheduled") + @MethodSource("unscheduled") void isUnscheduled(List stopTimes) { assertTrue(isUnscheduledTrip(stopTimes)); } diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java index f3a050bb224..6b32917e83e 100644 --- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java +++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java @@ -30,7 +30,7 @@ /** * This type of FlexTrip is used when a taxi-type service is modeled, which operates in any number - * od areas/groups of stops without a set schedule. The travel times are calculated based + * of areas/groups of stops without a set schedule. The travel times are calculated based * on the driving time between the stops, with the schedule times being used just for deciding if a * trip is possible. *

diff --git a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java index 0d4ba1071a3..1ae9fb2af84 100644 --- a/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java +++ b/src/test/java/org/opentripplanner/test/support/VariableArgumentsProvider.java @@ -1,10 +1,7 @@ package org.opentripplanner.test.support; import java.lang.reflect.Field; -import java.util.Collection; -import java.util.function.Function; import java.util.stream.Stream; -import javax.annotation.Nonnull; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; @@ -53,27 +50,6 @@ private Stream getValue(Field field) { field.setAccessible(accessible); - if (value == null) { - return null; - } else if (value instanceof Collection collection) { - return collection.stream().map(toArguments()); - } else if (value instanceof Stream stream) { - return stream.map(toArguments()); - } else { - throw new IllegalArgumentException( - "Cannot convert %s to stream.".formatted(value.getClass()) - ); - } - } - - @Nonnull - private static Function toArguments() { - return val -> { - if (val instanceof Arguments arguments) { - return arguments; - } else { - return Arguments.of(val); - } - }; + return value == null ? null : (Stream) value; } }