diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/AreaStopsToVerticesMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/AreaStopsToVerticesMapperTest.java
index 3b41cf9632d..67eb7e3ed93 100644
--- a/src/ext-test/java/org/opentripplanner/ext/flex/AreaStopsToVerticesMapperTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/AreaStopsToVerticesMapperTest.java
@@ -29,10 +29,10 @@ class AreaStopsToVerticesMapperTest {
private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of();
- private static final AreaStop BERLIN_AREA_STOP = TEST_MODEL.areaStopForTest(
- "berlin",
- Polygons.BERLIN
- );
+ private static final AreaStop BERLIN_AREA_STOP = TEST_MODEL
+ .areaStop("berlin")
+ .withGeometry(Polygons.BERLIN)
+ .build();
public static final StopModel STOP_MODEL = TEST_MODEL
.stopModelBuilder()
.withAreaStop(AreaStopsToVerticesMapperTest.BERLIN_AREA_STOP)
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java
index f414572ecf2..c88439a9e3f 100644
--- a/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java
@@ -17,7 +17,6 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import org.opentripplanner.ConstantsForTests;
import org.opentripplanner.TestOtpModel;
import org.opentripplanner.TestServerContext;
import org.opentripplanner.framework.application.OTPFeature;
@@ -57,14 +56,18 @@ public class FlexIntegrationTest {
@BeforeAll
static void setup() {
OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, true));
- TestOtpModel model = ConstantsForTests.buildOsmGraph(FlexTest.COBB_OSM);
+ TestOtpModel model = FlexIntegrationTestData.cobbOsm();
graph = model.graph();
transitModel = model.transitModel();
addGtfsToGraph(
graph,
transitModel,
- List.of(FlexTest.COBB_BUS_30_GTFS, FlexTest.MARTA_BUS_856_GTFS, FlexTest.COBB_FLEX_GTFS)
+ List.of(
+ FlexIntegrationTestData.COBB_BUS_30_GTFS,
+ FlexIntegrationTestData.MARTA_BUS_856_GTFS,
+ FlexIntegrationTestData.COBB_FLEX_GTFS
+ )
);
service = TestServerContext.createServerContext(graph, transitModel).routingService();
}
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTestData.java b/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTestData.java
new file mode 100644
index 00000000000..e0a85e23794
--- /dev/null
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTestData.java
@@ -0,0 +1,90 @@
+package org.opentripplanner.ext.flex;
+
+import static graphql.Assert.assertTrue;
+
+import gnu.trove.set.hash.TIntHashSet;
+import java.io.File;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.List;
+import java.util.Map;
+import org.opentripplanner.ConstantsForTests;
+import org.opentripplanner.TestOtpModel;
+import org.opentripplanner.ext.flex.flexpathcalculator.DirectFlexPathCalculator;
+import org.opentripplanner.ext.flex.template.FlexServiceDate;
+import org.opentripplanner.framework.application.OTPFeature;
+import org.opentripplanner.gtfs.graphbuilder.GtfsBundle;
+import org.opentripplanner.gtfs.graphbuilder.GtfsModule;
+import org.opentripplanner.model.calendar.ServiceDateInterval;
+import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.test.support.ResourceLoader;
+import org.opentripplanner.transit.model.framework.Deduplicator;
+import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;
+import org.opentripplanner.transit.service.StopModel;
+import org.opentripplanner.transit.service.TransitModel;
+
+public final class FlexIntegrationTestData {
+
+ private static final ResourceLoader RES = ResourceLoader.of(FlexIntegrationTestData.class);
+
+ private static final File ASPEN_GTFS = RES.file("aspen-flex-on-demand.gtfs");
+ static final File COBB_BUS_30_GTFS = RES.file("cobblinc-bus-30-only.gtfs.zip");
+ static final File COBB_FLEX_GTFS = RES.file("cobblinc-scheduled-deviated-flex.gtfs");
+ private static final File COBB_OSM = RES.file("cobb-county.filtered.osm.pbf");
+ private static final File LINCOLN_COUNTY_GTFS = RES.file("lincoln-county-flex.gtfs");
+ static final File MARTA_BUS_856_GTFS = RES.file("marta-bus-856-only.gtfs.zip");
+
+ public static final DirectFlexPathCalculator CALCULATOR = new DirectFlexPathCalculator();
+ private static final LocalDate SERVICE_DATE = LocalDate.of(2021, 4, 11);
+ private static final int SECONDS_SINCE_MIDNIGHT = LocalTime.of(10, 0).toSecondOfDay();
+ public static final FlexServiceDate FLEX_DATE = new FlexServiceDate(
+ SERVICE_DATE,
+ SECONDS_SINCE_MIDNIGHT,
+ RoutingBookingInfo.NOT_SET,
+ new TIntHashSet()
+ );
+
+ public static TestOtpModel aspenGtfs() {
+ return buildFlexGraph(ASPEN_GTFS);
+ }
+
+ public static TestOtpModel cobbFlexGtfs() {
+ return buildFlexGraph(COBB_FLEX_GTFS);
+ }
+
+ public static TestOtpModel cobbBus30Gtfs() {
+ return buildFlexGraph(COBB_BUS_30_GTFS);
+ }
+
+ public static TestOtpModel martaBus856Gtfs() {
+ return buildFlexGraph(MARTA_BUS_856_GTFS);
+ }
+
+ public static TestOtpModel lincolnCountyGtfs() {
+ return buildFlexGraph(LINCOLN_COUNTY_GTFS);
+ }
+
+ public static TestOtpModel cobbOsm() {
+ return ConstantsForTests.buildOsmGraph(COBB_OSM);
+ }
+
+ private static TestOtpModel buildFlexGraph(File file) {
+ var deduplicator = new Deduplicator();
+ var graph = new Graph(deduplicator);
+ var transitModel = new TransitModel(new StopModel(), deduplicator);
+ GtfsBundle gtfsBundle = new GtfsBundle(file);
+ GtfsModule module = new GtfsModule(
+ List.of(gtfsBundle),
+ transitModel,
+ graph,
+ new ServiceDateInterval(LocalDate.of(2021, 1, 1), LocalDate.of(2022, 1, 1))
+ );
+ OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, true));
+ module.buildGraph();
+ transitModel.index();
+ graph.index(transitModel.getStopModel());
+ OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, false));
+ assertTrue(transitModel.hasFlexTrips());
+ return new TestOtpModel(graph, transitModel);
+ }
+}
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java
index d76382999a2..8437a62e6da 100644
--- a/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java
@@ -12,7 +12,10 @@
public class FlexStopTimesForTest {
private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of();
- private static final StopLocation AREA_STOP = TEST_MODEL.areaStopForTest("area", Polygons.BERLIN);
+ private static final StopLocation AREA_STOP = TEST_MODEL
+ .areaStop("area")
+ .withGeometry(Polygons.BERLIN)
+ .build();
private static final RegularStop REGULAR_STOP = TEST_MODEL.stop("stop").build();
public static StopTime area(String startTime, String endTime) {
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/FlexTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/FlexTest.java
deleted file mode 100644
index 26cedae79ee..00000000000
--- a/src/ext-test/java/org/opentripplanner/ext/flex/FlexTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.opentripplanner.ext.flex;
-
-import static graphql.Assert.assertTrue;
-
-import gnu.trove.set.hash.TIntHashSet;
-import java.io.File;
-import java.time.LocalDate;
-import java.time.LocalTime;
-import java.util.List;
-import java.util.Map;
-import org.opentripplanner.TestOtpModel;
-import org.opentripplanner.ext.flex.flexpathcalculator.DirectFlexPathCalculator;
-import org.opentripplanner.framework.application.OTPFeature;
-import org.opentripplanner.gtfs.graphbuilder.GtfsBundle;
-import org.opentripplanner.gtfs.graphbuilder.GtfsModule;
-import org.opentripplanner.model.calendar.ServiceDateInterval;
-import org.opentripplanner.routing.graph.Graph;
-import org.opentripplanner.test.support.ResourceLoader;
-import org.opentripplanner.transit.model.framework.Deduplicator;
-import org.opentripplanner.transit.service.StopModel;
-import org.opentripplanner.transit.service.TransitModel;
-
-public abstract class FlexTest {
-
- private static final ResourceLoader RES = ResourceLoader.of(FlexTest.class);
-
- protected static final File ASPEN_GTFS = RES.file("aspen-flex-on-demand.gtfs");
- protected static final File COBB_FLEX_GTFS = RES.file("cobblinc-scheduled-deviated-flex.gtfs");
- protected static final File COBB_BUS_30_GTFS = RES.file("cobblinc-bus-30-only.gtfs.zip");
- protected static final File MARTA_BUS_856_GTFS = RES.file("marta-bus-856-only.gtfs.zip");
- protected static final File LINCOLN_COUNTY_GTFS = RES.file("lincoln-county-flex.gtfs");
- protected static final File COBB_OSM = RES.file("cobb-county.filtered.osm.pbf");
-
- protected static final DirectFlexPathCalculator calculator = new DirectFlexPathCalculator();
- protected static final LocalDate serviceDate = LocalDate.of(2021, 4, 11);
- protected static final int secondsSinceMidnight = LocalTime.of(10, 0).toSecondOfDay();
- protected static final FlexServiceDate flexDate = new FlexServiceDate(
- serviceDate,
- secondsSinceMidnight,
- new TIntHashSet()
- );
-
- protected static TestOtpModel buildFlexGraph(File file) {
- var deduplicator = new Deduplicator();
- var graph = new Graph(deduplicator);
- var transitModel = new TransitModel(new StopModel(), deduplicator);
- GtfsBundle gtfsBundle = new GtfsBundle(file);
- GtfsModule module = new GtfsModule(
- List.of(gtfsBundle),
- transitModel,
- graph,
- new ServiceDateInterval(LocalDate.of(2021, 1, 1), LocalDate.of(2022, 1, 1))
- );
- OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, true));
- module.buildGraph();
- transitModel.index();
- graph.index(transitModel.getStopModel());
- OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, false));
- assertTrue(transitModel.hasFlexTrips());
- return new TestOtpModel(graph, transitModel);
- }
-}
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 78c4a2f3ddc..e116831e2d3 100644
--- a/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/GtfsFlexTest.java
@@ -3,7 +3,6 @@
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;
@@ -11,26 +10,24 @@
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
+ * 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 {
+class GtfsFlexTest {
private static TransitModel transitModel;
@BeforeAll
static void setup() {
- TestOtpModel model = FlexTest.buildFlexGraph(ASPEN_GTFS);
+ TestOtpModel model = FlexIntegrationTestData.aspenGtfs();
transitModel = model.transitModel();
}
@@ -49,50 +46,8 @@ void parseAspenTaxiAsUnscheduledTrip() {
);
}
- @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/flexpathcalculator/ScheduledFlexPathCalculatorTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java
index 70f39af2420..985ca5a9898 100644
--- a/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java
@@ -29,7 +29,7 @@ class ScheduledFlexPathCalculatorTest {
@Test
void calculateTime() {
- var c = (FlexPathCalculator) (fromv, tov, fromStopIndex, toStopIndex) ->
+ var c = (FlexPathCalculator) (fromv, tov, boardStopPosition, alightStopPosition) ->
new FlexPath(10_000, (int) Duration.ofMinutes(10).toSeconds(), () -> LineStrings.SIMPLE);
var calc = new ScheduledFlexPathCalculator(c, TRIP);
var path = calc.calculateFlexPath(V1, V2, 0, 1);
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/TimePenaltyCalculatorTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/TimePenaltyCalculatorTest.java
index e504f8ac762..15f62038a6f 100644
--- a/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/TimePenaltyCalculatorTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/TimePenaltyCalculatorTest.java
@@ -15,7 +15,7 @@ class TimePenaltyCalculatorTest {
@Test
void calculate() {
- FlexPathCalculator delegate = (fromv, tov, fromStopIndex, toStopIndex) ->
+ FlexPathCalculator delegate = (fromv, tov, boardStopPosition, alightStopPosition) ->
new FlexPath(10_000, THIRTY_MINS_IN_SECONDS, () -> LineStrings.SIMPLE);
var mod = TimePenalty.of(Duration.ofMinutes(10), 1.5f);
@@ -26,7 +26,7 @@ void calculate() {
@Test
void nullValue() {
- FlexPathCalculator delegate = (fromv, tov, fromStopIndex, toStopIndex) -> null;
+ FlexPathCalculator delegate = (fromv, tov, boardStopPosition, alightStopPosition) -> null;
var mod = TimePenalty.of(Duration.ofMinutes(10), 1.5f);
var calc = new TimePenaltyCalculator(delegate, mod);
var path = calc.calculateFlexPath(StreetModelForTest.V1, StreetModelForTest.V2, 0, 5);
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/template/BoardAlight.java b/src/ext-test/java/org/opentripplanner/ext/flex/template/BoardAlight.java
new file mode 100644
index 00000000000..cac09fb24a0
--- /dev/null
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/template/BoardAlight.java
@@ -0,0 +1,7 @@
+package org.opentripplanner.ext.flex.template;
+
+enum BoardAlight {
+ BOARD_ONLY,
+ ALIGHT_ONLY,
+ BOARD_AND_ALIGHT,
+}
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java
new file mode 100644
index 00000000000..8c73d0901ff
--- /dev/null
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/template/FlexTemplateFactoryTest.java
@@ -0,0 +1,374 @@
+package org.opentripplanner.ext.flex.template;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.ext.flex.template.BoardAlight.ALIGHT_ONLY;
+import static org.opentripplanner.ext.flex.template.BoardAlight.BOARD_AND_ALIGHT;
+import static org.opentripplanner.ext.flex.template.BoardAlight.BOARD_ONLY;
+import static org.opentripplanner.framework.time.TimeUtils.time;
+
+import gnu.trove.set.hash.TIntHashSet;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.Month;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import org.junit.jupiter.api.Test;
+import org.locationtech.jts.geom.Coordinate;
+import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
+import org.opentripplanner.ext.flex.flexpathcalculator.ScheduledFlexPathCalculator;
+import org.opentripplanner.ext.flex.flexpathcalculator.StreetFlexPathCalculator;
+import org.opentripplanner.ext.flex.trip.FlexTrip;
+import org.opentripplanner.ext.flex.trip.ScheduledDeviatedTrip;
+import org.opentripplanner.ext.flex.trip.UnscheduledTrip;
+import org.opentripplanner.framework.i18n.I18NString;
+import org.opentripplanner.model.PickDrop;
+import org.opentripplanner.model.StopTime;
+import org.opentripplanner.routing.graphfinder.NearbyStop;
+import org.opentripplanner.street.model.vertex.StreetLocation;
+import org.opentripplanner.street.search.request.StreetSearchRequest;
+import org.opentripplanner.street.search.state.State;
+import org.opentripplanner.transit.model._data.TransitModelForTest;
+import org.opentripplanner.transit.model.site.RegularStop;
+import org.opentripplanner.transit.model.site.StopLocation;
+import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;
+
+class FlexTemplateFactoryTest {
+
+ private static final TransitModelForTest MODEL = TransitModelForTest.of();
+
+ /**
+ * This is pass-through information
+ */
+ private static final Duration MAX_TRANSFER_DURATION = Duration.ofMinutes(10);
+
+ /**
+ * Any calculator will do. The only thing we will test here is that a new scheduled calculator
+ * is created for scheduled-flex-trips.
+ */
+ private static final FlexPathCalculator CALCULATOR = new StreetFlexPathCalculator(
+ false,
+ Duration.ofHours(3)
+ );
+
+ public static final int SERVICE_TIME_OFFSET = 3600 * 2;
+
+ /**
+ * The date is pass-through information in this test, so one date is enough.
+ */
+ private static final FlexServiceDate DATE = new FlexServiceDate(
+ LocalDate.of(2024, Month.MAY, 17),
+ SERVICE_TIME_OFFSET,
+ RoutingBookingInfo.NOT_SET,
+ new TIntHashSet()
+ );
+
+ // Stop A-D is a mix of regular and area stops - it should not matter for this test
+ private static final StopLocation STOP_A = MODEL.stop("A").build();
+ private static final StopLocation STOP_B = MODEL.areaStop("B").build();
+ private static final StopLocation STOP_C = MODEL.areaStop("C").build();
+ private static final StopLocation STOP_D = MODEL.stop("D").build();
+ private static final RegularStop STOP_G1 = MODEL.stop("G1").build();
+ private static final RegularStop STOP_G2 = MODEL.stop("G2").build();
+ private static final RegularStop STOP_G3 = MODEL.stop("G3").build();
+ private static final RegularStop STOP_G4 = MODEL.stop("G4").build();
+ private static final StopLocation GROUP_STOP_12 = MODEL.groupStop("G", STOP_G1, STOP_G2);
+ private static final StopLocation GROUP_STOP_34 = MODEL.groupStop("G", STOP_G3, STOP_G4);
+
+ private static final Trip TRIP = TransitModelForTest.trip("Trip").build();
+ private static final int T_10_00 = time("10:00");
+ private static final int T_10_10 = time("10:10");
+ private static final int T_10_20 = time("10:20");
+ private static final int T_10_30 = time("10:30");
+ private static final int T_10_40 = time("10:40");
+
+ @Test
+ void testCreateAccessTemplateForUnscheduledTripWithTwoStopsAndNoBoardRestrictions() {
+ var flexTrip = unscheduledTrip(
+ "FlexTrip",
+ stopTime(1, STOP_A, BOARD_AND_ALIGHT, T_10_00),
+ stopTime(2, STOP_B, BOARD_AND_ALIGHT, T_10_10)
+ );
+
+ var factory = FlexTemplateFactory.of(CALCULATOR, MAX_TRANSFER_DURATION);
+
+ // Create template with access boarding at stop A
+ var subject = factory.createAccessTemplates(closestTrip(flexTrip, STOP_A, 0));
+
+ var template = subject.get(0);
+ assertEquals(0, template.boardStopPosition);
+ assertEquals(1, template.alightStopPosition);
+ assertSame(CALCULATOR, template.calculator);
+ assertSame(STOP_B, template.transferStop);
+ assertSame(DATE.serviceDate(), template.serviceDate);
+ assertEquals(SERVICE_TIME_OFFSET, template.secondsFromStartOfTime);
+ assertEquals(1, subject.size(), subject::toString);
+
+ // We are not allowed to board and alight at the same stop so boarding the last stop
+ // will result in an empty result
+ subject = factory.createAccessTemplates(closestTrip(flexTrip, STOP_B, 2));
+ assertTrue(subject.isEmpty(), subject::toString);
+
+ // Search for a stop not part of the pattern should result in an empty result
+ subject = factory.createAccessTemplates(closestTrip(flexTrip, STOP_C, 99));
+ assertTrue(subject.isEmpty(), subject::toString);
+ }
+
+ @Test
+ void testCreateEgressTemplateForUnscheduledTripWithTwoStopsAndNoBoardRestrictions() {
+ var flexTrip = unscheduledTrip(
+ "FlexTrip",
+ stopTime(1, STOP_A, BOARD_AND_ALIGHT, T_10_00),
+ stopTime(2, STOP_B, BOARD_AND_ALIGHT, T_10_10)
+ );
+
+ var factory = FlexTemplateFactory.of(CALCULATOR, MAX_TRANSFER_DURATION);
+
+ // Create template with egress alighting at stop B
+ var subject = factory.createEgressTemplates(closestTrip(flexTrip, STOP_B, 1));
+
+ var template = subject.get(0);
+ assertEquals(0, template.boardStopPosition);
+ assertEquals(1, template.alightStopPosition);
+ assertSame(CALCULATOR, template.calculator);
+ assertSame(STOP_A, template.transferStop);
+ assertSame(DATE.serviceDate(), template.serviceDate);
+ assertEquals(SERVICE_TIME_OFFSET, template.secondsFromStartOfTime);
+ assertEquals(1, subject.size(), subject::toString);
+
+ // We are not allowed to board and alight at the same stop so boarding the last stop
+ // will result in an empty result
+ subject = factory.createEgressTemplates(closestTrip(flexTrip, STOP_A, 0));
+ assertTrue(subject.isEmpty(), subject::toString);
+ // TODO This is no longer the responsibility of the template factory, reimplement test
+ // Search for a stop not part of the pattern should result in an empty result
+ // subject = factory.createEgressTemplates(closestTrip(flexTrip, STOP_C, 99));
+ // assertTrue(subject.isEmpty(), subject::toString);
+ }
+
+ @Test
+ void testCreateAccessTemplateForUnscheduledTripWithBoardAndAlightRestrictions() {
+ var flexTrip = unscheduledTrip(
+ "FlexTrip",
+ stopTime(1, STOP_A, BOARD_ONLY, T_10_00),
+ stopTime(2, STOP_B, ALIGHT_ONLY, T_10_10),
+ stopTime(3, STOP_C, BOARD_ONLY, T_10_20),
+ stopTime(4, STOP_D, ALIGHT_ONLY, T_10_30)
+ );
+
+ var factory = FlexTemplateFactory.of(CALCULATOR, MAX_TRANSFER_DURATION);
+
+ // Create template with boarding at stop A
+ var subject = factory.createAccessTemplates(closestTrip(flexTrip, STOP_A, 0));
+
+ var t1 = subject.get(0);
+ var t2 = subject.get(1);
+
+ assertEquals(0, t1.boardStopPosition);
+ assertEquals(0, t2.boardStopPosition);
+ assertEquals(Set.of(1, 3), Set.of(t1.alightStopPosition, t2.alightStopPosition));
+ assertEquals(2, subject.size());
+
+ // Board at stop C
+ subject = factory.createAccessTemplates(closestTrip(flexTrip, STOP_C, 2));
+
+ t1 = subject.get(0);
+ assertEquals(2, t1.boardStopPosition);
+ assertEquals(3, t1.alightStopPosition);
+ assertEquals(1, subject.size());
+ // TODO This is no longer the responsibility of the template factory, reimplement test
+ // We are not allowed to board at stop B, an empty result is expected
+ // subject = factory.createAccessTemplates(closestTrip(flexTrip, STOP_B, 1));
+ // assertTrue(subject.isEmpty(), subject::toString);
+
+ // TODO This is no longer the responsibility of the template factory, reimplement test
+ // Search for a stop not part of the pattern should result in an empty result
+ // subject = factory.createAccessTemplates(closestTrip(flexTrip, STOP_D, 3));
+ // assertTrue(subject.isEmpty(), subject::toString);
+ }
+
+ @Test
+ void testCreateEgressTemplateForUnscheduledTripWithBoardAndAlightRestrictions() {
+ var flexTrip = unscheduledTrip(
+ "FlexTrip",
+ stopTime(1, STOP_A, BOARD_ONLY, T_10_00),
+ stopTime(2, STOP_B, ALIGHT_ONLY, T_10_10),
+ stopTime(3, STOP_C, BOARD_ONLY, T_10_20),
+ stopTime(4, STOP_D, ALIGHT_ONLY, T_10_30)
+ );
+
+ var factory = FlexTemplateFactory.of(CALCULATOR, MAX_TRANSFER_DURATION);
+
+ // Create template with boarding at stop A
+ var subject = factory.createEgressTemplates(closestTrip(flexTrip, STOP_D, 3));
+
+ var t1 = subject.get(0);
+ var t2 = subject.get(1);
+
+ assertEquals(Set.of(0, 2), Set.of(t1.boardStopPosition, t2.boardStopPosition));
+ assertEquals(3, t1.alightStopPosition);
+ assertEquals(3, t2.alightStopPosition);
+ assertEquals(2, subject.size());
+
+ // Board at stop C
+ subject = factory.createEgressTemplates(closestTrip(flexTrip, STOP_B, 1));
+
+ t1 = subject.get(0);
+ assertEquals(0, t1.boardStopPosition);
+ assertEquals(1, t1.alightStopPosition);
+ assertEquals(1, subject.size());
+
+ // TODO This is no longer the responsibility of the template factory, reimplement test
+ // We are not allowed to board and alight at the same stop so boarding the last stop
+ // will result in an empty result
+ // subject = factory.createEgressTemplates(closestTrip(flexTrip, STOP_C, 2));
+ // assertTrue(subject.isEmpty(), subject::toString);
+
+ // Search for a stop not part of the pattern should result in an empty result
+ subject = factory.createEgressTemplates(closestTrip(flexTrip, STOP_A, 0));
+ assertTrue(subject.isEmpty(), subject::toString);
+ }
+
+ @Test
+ void testCreateAccessTemplateForUnscheduledTripWithTwoGroupsStops() {
+ var flexTrip = unscheduledTrip(
+ "FlexTrip",
+ stopTime(1, GROUP_STOP_12, BOARD_ONLY, T_10_00),
+ stopTime(2, GROUP_STOP_34, ALIGHT_ONLY, T_10_20)
+ );
+
+ var factory = FlexTemplateFactory.of(CALCULATOR, MAX_TRANSFER_DURATION);
+
+ // Create template with access boarding at stop A
+ var subject = factory.createAccessTemplates(closestTrip(flexTrip, STOP_G1, 0));
+
+ var t1 = subject.get(0);
+ var t2 = subject.get(1);
+ assertEquals(0, t1.boardStopPosition);
+ assertEquals(0, t2.boardStopPosition);
+ assertEquals(1, t1.alightStopPosition);
+ assertEquals(1, t2.alightStopPosition);
+ assertEquals(Set.of(STOP_G3, STOP_G4), Set.of(t1.transferStop, t2.transferStop));
+ assertEquals(2, subject.size(), subject::toString);
+ }
+
+ @Test
+ void testCreateEgressTemplateForUnscheduledTripWithTwoGroupsStops() {
+ var flexTrip = unscheduledTrip(
+ "FlexTrip",
+ stopTime(1, GROUP_STOP_12, BOARD_ONLY, T_10_00),
+ stopTime(2, GROUP_STOP_34, ALIGHT_ONLY, T_10_20)
+ );
+
+ var factory = FlexTemplateFactory.of(CALCULATOR, MAX_TRANSFER_DURATION);
+
+ // Create template with access boarding at stop A
+ var subject = factory.createEgressTemplates(closestTrip(flexTrip, STOP_G4, 1));
+
+ var t1 = subject.get(0);
+ var t2 = subject.get(1);
+ assertEquals(0, t1.boardStopPosition);
+ assertEquals(0, t2.boardStopPosition);
+ assertEquals(1, t1.alightStopPosition);
+ assertEquals(1, t2.alightStopPosition);
+ assertEquals(Set.of(STOP_G1, STOP_G2), Set.of(t1.transferStop, t2.transferStop));
+ assertEquals(2, subject.size(), subject::toString);
+ }
+
+ @Test
+ void testCreateAccessTemplateForScheduledDeviatedTrip() {
+ var flexTrip = scheduledDeviatedFlexTrip(
+ "FlexTrip",
+ stopTime(1, STOP_A, BOARD_ONLY, T_10_00),
+ stopTime(5, STOP_B, BOARD_AND_ALIGHT, T_10_20),
+ stopTime(10, STOP_C, ALIGHT_ONLY, T_10_30)
+ );
+
+ var factory = FlexTemplateFactory.of(CALCULATOR, MAX_TRANSFER_DURATION);
+
+ // Create template with access boarding at stop A
+ var subject = factory.createAccessTemplates(closestTrip(flexTrip, STOP_B, 1));
+
+ var template = subject.get(0);
+ assertEquals(1, template.boardStopPosition);
+ assertEquals(2, template.alightStopPosition);
+ assertEquals(STOP_C, template.transferStop);
+ assertTrue(template.calculator instanceof ScheduledFlexPathCalculator);
+ assertEquals(1, subject.size(), subject::toString);
+ }
+
+ @Test
+ void testCreateEgressTemplateForScheduledDeviatedTrip() {
+ var flexTrip = scheduledDeviatedFlexTrip(
+ "FlexTrip",
+ stopTime(1, STOP_A, BOARD_ONLY, T_10_00),
+ stopTime(5, STOP_B, BOARD_AND_ALIGHT, T_10_20),
+ stopTime(10, STOP_C, ALIGHT_ONLY, T_10_30)
+ );
+
+ var factory = FlexTemplateFactory.of(CALCULATOR, MAX_TRANSFER_DURATION);
+
+ // Create template with access boarding at stop A
+ var subject = factory.createEgressTemplates(closestTrip(flexTrip, STOP_B, 1));
+
+ var template = subject.get(0);
+ assertEquals(0, template.boardStopPosition);
+ assertEquals(1, template.alightStopPosition);
+ assertEquals(STOP_A, template.transferStop);
+ assertTrue(template.calculator instanceof ScheduledFlexPathCalculator);
+ assertEquals(1, subject.size(), subject::toString);
+ }
+
+ /**
+ * The nearbyStop is pass-through information, except the stop - which defines the "transfer"
+ * point.
+ */
+ private static NearbyStop nearbyStop(StopLocation transferPoint) {
+ var id = "NearbyStop:" + transferPoint.getId().getId();
+ return new NearbyStop(
+ transferPoint,
+ 0,
+ List.of(),
+ new State(
+ new StreetLocation(id, new Coordinate(0, 0), I18NString.of(id)),
+ StreetSearchRequest.of().build()
+ )
+ );
+ }
+
+ private static ScheduledDeviatedTrip scheduledDeviatedFlexTrip(String id, StopTime... stopTimes) {
+ return MODEL.scheduledDeviatedTrip(id, stopTimes);
+ }
+
+ private static UnscheduledTrip unscheduledTrip(String id, StopTime... stopTimes) {
+ return MODEL.unscheduledTrip(id, Arrays.asList(stopTimes));
+ }
+
+ private static ClosestTrip closestTrip(FlexTrip, ?> trip, StopLocation stop, int stopPos) {
+ return new ClosestTrip(nearbyStop(stop), trip, stopPos, DATE);
+ }
+
+ private static StopTime stopTime(
+ int seqNr,
+ StopLocation stop,
+ BoardAlight boardAlight,
+ int startTime
+ ) {
+ var st = MODEL.stopTime(TRIP, seqNr, stop);
+ switch (boardAlight) {
+ case BOARD_ONLY:
+ st.setDropOffType(PickDrop.NONE);
+ break;
+ case ALIGHT_ONLY:
+ st.setPickupType(PickDrop.NONE);
+ break;
+ }
+ st.setFlexWindowStart(startTime);
+ // 5-minute window
+ st.setFlexWindowEnd(startTime + 300);
+ return st;
+ }
+}
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java
index 0d0376bbd32..fad9e98e254 100644
--- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/FlexTripsMapperTest.java
@@ -24,7 +24,7 @@ void defaultTimePenalty() {
var trips = FlexTripsMapper.createFlexTrips(builder, NOOP);
assertEquals("[UnscheduledTrip{F:flex-1}]", trips.toString());
var unscheduled = (UnscheduledTrip) trips.getFirst();
- var unchanged = unscheduled.flexPathCalculator(new DirectFlexPathCalculator());
+ var unchanged = unscheduled.decorateFlexPathCalculator(new DirectFlexPathCalculator());
assertInstanceOf(DirectFlexPathCalculator.class, unchanged);
}
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java
index 143388cac0f..2883394f837 100644
--- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java
@@ -19,8 +19,9 @@
import org.opentripplanner.TestServerContext;
import org.opentripplanner._support.time.ZoneIds;
import org.opentripplanner.ext.fares.DecorateWithFare;
+import org.opentripplanner.ext.flex.FlexIntegrationTestData;
+import org.opentripplanner.ext.flex.FlexParameters;
import org.opentripplanner.ext.flex.FlexRouter;
-import org.opentripplanner.ext.flex.FlexTest;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.geometry.EncodedPolyline;
import org.opentripplanner.framework.i18n.I18NString;
@@ -37,7 +38,6 @@
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graphfinder.NearbyStop;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.street.model.vertex.StreetLocation;
import org.opentripplanner.street.search.request.StreetSearchRequest;
import org.opentripplanner.street.search.state.State;
@@ -53,7 +53,7 @@
*
* Read about the details at: https://www.cobbcounty.org/transportation/cobblinc/routes-and-schedules/flex
*/
-public class ScheduledDeviatedTripTest extends FlexTest {
+class ScheduledDeviatedTripTest {
static Graph graph;
static TransitModel transitModel;
@@ -91,37 +91,6 @@ void parseCobbCountyAsScheduledDeviatedTrip() {
assertEquals(-84.63430143459385, flexZone.getLon(), delta);
}
- @Test
- void calculateAccessTemplate() {
- var trip = getFlexTrip();
- var nearbyStop = getNearbyStop(trip);
-
- var accesses = trip
- .getFlexAccessTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT)
- .toList();
-
- assertEquals(3, accesses.size());
-
- var access = accesses.get(0);
- assertEquals(1, access.fromStopIndex);
- assertEquals(1, access.toStopIndex);
- }
-
- @Test
- void calculateEgressTemplate() {
- var trip = getFlexTrip();
- var nearbyStop = getNearbyStop(trip);
- var egresses = trip
- .getFlexEgressTemplates(nearbyStop, flexDate, calculator, FlexConfig.DEFAULT)
- .toList();
-
- assertEquals(3, egresses.size());
-
- var egress = egresses.get(0);
- assertEquals(2, egress.fromStopIndex);
- assertEquals(2, egress.toStopIndex);
- }
-
@Test
void calculateDirectFare() {
OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, true));
@@ -133,9 +102,9 @@ void calculateDirectFare() {
var router = new FlexRouter(
graph,
new DefaultTransitService(transitModel),
- FlexConfig.DEFAULT,
+ FlexParameters.defaultValues(),
OffsetDateTime.parse("2021-11-12T10:15:24-05:00").toInstant(),
- false,
+ null,
1,
1,
List.of(from),
@@ -144,7 +113,11 @@ void calculateDirectFare() {
var filter = new DecorateWithFare(graph.getFareService());
- var itineraries = router.createFlexOnlyItineraries().stream().peek(filter::decorate).toList();
+ var itineraries = router
+ .createFlexOnlyItineraries(false)
+ .stream()
+ .peek(filter::decorate)
+ .toList();
var itinerary = itineraries.getFirst();
@@ -220,13 +193,13 @@ void shouldNotInterpolateFlexTimes() {
*/
@Test
void parseContinuousPickup() {
- var lincolnGraph = FlexTest.buildFlexGraph(LINCOLN_COUNTY_GTFS);
+ var lincolnGraph = FlexIntegrationTestData.lincolnCountyGtfs();
assertNotNull(lincolnGraph);
}
@BeforeAll
static void setup() {
- TestOtpModel model = FlexTest.buildFlexGraph(COBB_FLEX_GTFS);
+ TestOtpModel model = FlexIntegrationTestData.cobbFlexGtfs();
graph = model.graph();
transitModel = model.transitModel();
}
diff --git a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java
index a4b245b7de8..f19478af629 100644
--- a/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledDrivingDurationTest.java
@@ -17,7 +17,12 @@
class UnscheduledDrivingDurationTest {
- static final FlexPathCalculator STATIC_CALCULATOR = (fromv, tov, fromStopIndex, toStopIndex) ->
+ static final FlexPathCalculator STATIC_CALCULATOR = (
+ fromv,
+ tov,
+ boardStopPosition,
+ alightStopPosition
+ ) ->
new FlexPath(10_000, (int) Duration.ofMinutes(10).toSeconds(), () -> LineStrings.SIMPLE);
private static final StopTime STOP_TIME = FlexStopTimesForTest.area("10:00", "18:00");
@@ -25,7 +30,7 @@ class UnscheduledDrivingDurationTest {
void noPenalty() {
var trip = UnscheduledTrip.of(id("1")).withStopTimes(List.of(STOP_TIME)).build();
- var calculator = trip.flexPathCalculator(STATIC_CALCULATOR);
+ var calculator = trip.decorateFlexPathCalculator(STATIC_CALCULATOR);
var path = calculator.calculateFlexPath(V1, V2, 0, 0);
assertEquals(600, path.durationSeconds);
}
@@ -38,7 +43,7 @@ void withPenalty() {
.withTimePenalty(TimePenalty.of(Duration.ofMinutes(2), 1.5f))
.build();
- var calculator = trip.flexPathCalculator(STATIC_CALCULATOR);
+ var calculator = trip.decorateFlexPathCalculator(STATIC_CALCULATOR);
var path = calculator.calculateFlexPath(V1, V2, 0, 0);
assertEquals(1020, path.durationSeconds);
}
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 80bda31fabf..8bc9d7c8919 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
@@ -12,8 +12,6 @@
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;
@@ -23,18 +21,12 @@
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.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;
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.site.AreaStop;
import org.opentripplanner.transit.model.site.RegularStop;
@@ -51,10 +43,11 @@ class UnscheduledTripTest {
private static final int T15_00 = TimeUtils.hm2time(15, 0);
private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of();
- private static final StopLocation AREA_STOP = TEST_MODEL.areaStopForTest("area", Polygons.BERLIN);
private static final RegularStop REGULAR_STOP = TEST_MODEL.stop("stop").build();
+ private static final StopLocation AREA_STOP = TEST_MODEL.areaStop("area").build();
+
@Nested
class IsUnscheduledTrip {
@@ -548,9 +541,9 @@ void testMultipleAreasEarliestDepartureTime(TestCase tc) {
@Test
void boardingAlighting() {
- var AREA_STOP1 = TEST_MODEL.areaStopForTest("area-1", Polygons.BERLIN);
- var AREA_STOP2 = TEST_MODEL.areaStopForTest("area-2", Polygons.BERLIN);
- var AREA_STOP3 = TEST_MODEL.areaStopForTest("area-3", Polygons.BERLIN);
+ var AREA_STOP1 = TEST_MODEL.areaStop("area-1").build();
+ var AREA_STOP2 = TEST_MODEL.areaStop("area-2").build();
+ var AREA_STOP3 = TEST_MODEL.areaStop("area-3").build();
var first = area(AREA_STOP1, "10:00", "10:05");
first.setDropOffType(NONE);
@@ -564,108 +557,43 @@ void boardingAlighting() {
.build()
.trip();
- assertTrue(trip.isBoardingPossible(nearbyStop(AREA_STOP1)));
- assertFalse(trip.isAlightingPossible(nearbyStop(AREA_STOP1)));
+ assertTrue(trip.isBoardingPossible(AREA_STOP1));
+ assertFalse(trip.isAlightingPossible(AREA_STOP1));
- assertFalse(trip.isBoardingPossible(nearbyStop(AREA_STOP2)));
- assertTrue(trip.isAlightingPossible(nearbyStop(AREA_STOP2)));
+ assertFalse(trip.isBoardingPossible(AREA_STOP2));
+ assertTrue(trip.isAlightingPossible(AREA_STOP2));
}
- @Nested
- 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()
- );
- 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 templates = accessTemplates(trip);
-
- assertEquals(3, templates.size());
-
- List
- .of(0, 1, 2)
- .forEach(index -> {
- var template = templates.get(index);
- assertEquals(0, template.fromStopIndex);
- assertEquals(index + 1, template.toStopIndex);
- });
- }
-
- @Test
- void accessTemplatesNoAlighting() {
- var second = area("10:10", "10:15");
- second.setDropOffType(NONE);
-
- var trip = trip(List.of(FIRST, second, THIRD, FOURTH));
-
- var templates = accessTemplates(trip);
-
- assertEquals(2, templates.size());
- List
- .of(0, 1)
- .forEach(index -> {
- var template = templates.get(index);
- assertEquals(0, template.fromStopIndex);
- assertEquals(index + 2, template.toStopIndex);
- });
- }
-
- @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(0, template.fromStopIndex);
- assertEquals(3, template.toStopIndex);
- }
+ private static String timeToString(int time) {
+ return TimeUtils.timeToStrCompact(time, MISSING_VALUE, "MISSING_VALUE");
+ }
- @Nonnull
- private static UnscheduledTrip trip(List stopTimes) {
- return new TestCase.Builder(FIRST, THIRD).withStopTimes(stopTimes).build().trip();
- }
+ private static StopTime area(String startTime, String endTime) {
+ return area(AREA_STOP, endTime, startTime);
+ }
- @Nonnull
- private static List accessTemplates(UnscheduledTrip trip) {
- return trip
- .getFlexAccessTemplates(NEARBY_STOP, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT)
- .toList();
- }
+ private static StopTime area(StopLocation areaStop, String endTime, String startTime) {
+ var stopTime = new StopTime();
+ stopTime.setStop(areaStop);
+ stopTime.setFlexWindowStart(TimeUtils.time(startTime));
+ stopTime.setFlexWindowEnd(TimeUtils.time(endTime));
+ return stopTime;
+ }
- @Nonnull
- private static List egressTemplates(UnscheduledTrip trip) {
- return trip
- .getFlexEgressTemplates(NEARBY_STOP, FLEX_SERVICE_DATE, CALCULATOR, FlexConfig.DEFAULT)
- .toList();
- }
+ private static StopTime regularDeparture(String departureTime) {
+ return regularStopTime(MISSING_VALUE, TimeUtils.time(departureTime));
}
- private static String timeToString(int time) {
- return TimeUtils.timeToStrCompact(time, MISSING_VALUE, "MISSING_VALUE");
+ private static StopTime regularArrival(String arrivalTime) {
+ return regularStopTime(TimeUtils.time(arrivalTime), MISSING_VALUE);
}
- @Nonnull
- private static NearbyStop nearbyStop(AreaStop stop) {
- return new NearbyStop(stop, 1000, List.of(), null);
+ private static StopTime regularStopTime(int arrivalTime, int departureTime) {
+ var stopTime = new StopTime();
+ stopTime.setStop(REGULAR_STOP);
+ stopTime.setArrivalTime(arrivalTime);
+ stopTime.setDepartureTime(departureTime);
+ return stopTime;
}
record TestCase(
diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapperTest.java
index 2cf0804b630..5387df91bca 100644
--- a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapperTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/areastops/AreaStopPropertyMapperTest.java
@@ -6,7 +6,6 @@
import java.util.List;
import java.util.Locale;
import org.junit.jupiter.api.Test;
-import org.opentripplanner._support.geometry.Polygons;
import org.opentripplanner.transit.model._data.TransitModelForTest;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.site.AreaStop;
@@ -15,7 +14,7 @@
class AreaStopPropertyMapperTest {
private static final TransitModelForTest MODEL = new TransitModelForTest(StopModel.of());
- private static final AreaStop STOP = MODEL.areaStopForTest("123", Polygons.BERLIN);
+ private static final AreaStop STOP = MODEL.areaStop("123").build();
private static final Route ROUTE_WITH_COLOR = TransitModelForTest
.route("123")
.withColor("ffffff")
diff --git a/src/ext/java/org/opentripplanner/ext/flex/FlexAccessEgress.java b/src/ext/java/org/opentripplanner/ext/flex/FlexAccessEgress.java
index 26833f67a6a..f464f1e1907 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/FlexAccessEgress.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/FlexAccessEgress.java
@@ -2,47 +2,49 @@
import static org.opentripplanner.model.StopTime.MISSING_VALUE;
+import java.util.Objects;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.framework.tostring.ToStringBuilder;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.transit.model.site.RegularStop;
+import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;
public final class FlexAccessEgress {
private final RegularStop stop;
private final FlexPathDurations pathDurations;
- private final int fromStopIndex;
- private final int toStopIndex;
- private final FlexTrip trip;
+ private final int boardStopPosition;
+ private final int alightStopPosition;
+ private final FlexTrip, ?> trip;
private final State lastState;
private final boolean stopReachedOnBoard;
+ private final RoutingBookingInfo routingBookingInfo;
public FlexAccessEgress(
RegularStop stop,
FlexPathDurations pathDurations,
- int fromStopIndex,
- int toStopIndex,
- FlexTrip trip,
+ int boardStopPosition,
+ int alightStopPosition,
+ FlexTrip, ?> trip,
State lastState,
- boolean stopReachedOnBoard
+ boolean stopReachedOnBoard,
+ int requestedBookingTime
) {
this.stop = stop;
this.pathDurations = pathDurations;
- this.fromStopIndex = fromStopIndex;
- this.toStopIndex = toStopIndex;
- this.trip = trip;
+ this.boardStopPosition = boardStopPosition;
+ this.alightStopPosition = alightStopPosition;
+ this.trip = Objects.requireNonNull(trip);
this.lastState = lastState;
this.stopReachedOnBoard = stopReachedOnBoard;
+ this.routingBookingInfo =
+ RoutingBookingInfo.of(requestedBookingTime, trip.getPickupBookingInfo(boardStopPosition));
}
public RegularStop stop() {
return stop;
}
- public FlexTrip trip() {
- return trip;
- }
-
public State lastState() {
return lastState;
}
@@ -52,11 +54,15 @@ public boolean stopReachedOnBoard() {
}
public int earliestDepartureTime(int departureTime) {
- int requestedDepartureTime = pathDurations.mapToFlexTripDepartureTime(departureTime);
+ int tripDepartureTime = pathDurations.mapToFlexTripDepartureTime(departureTime);
+
+ // Apply minimum-booking-notice
+ tripDepartureTime = routingBookingInfo.earliestDepartureTime(tripDepartureTime);
+
int earliestDepartureTime = trip.earliestDepartureTime(
- requestedDepartureTime,
- fromStopIndex,
- toStopIndex,
+ tripDepartureTime,
+ boardStopPosition,
+ alightStopPosition,
pathDurations.trip()
);
if (earliestDepartureTime == MISSING_VALUE) {
@@ -66,16 +72,19 @@ public int earliestDepartureTime(int departureTime) {
}
public int latestArrivalTime(int arrivalTime) {
- int requestedArrivalTime = pathDurations.mapToFlexTripArrivalTime(arrivalTime);
+ int tripArrivalTime = pathDurations.mapToFlexTripArrivalTime(arrivalTime);
int latestArrivalTime = trip.latestArrivalTime(
- requestedArrivalTime,
- fromStopIndex,
- toStopIndex,
+ tripArrivalTime,
+ boardStopPosition,
+ alightStopPosition,
pathDurations.trip()
);
if (latestArrivalTime == MISSING_VALUE) {
return MISSING_VALUE;
}
+ if (routingBookingInfo.exceedsMinimumBookingNotice(latestArrivalTime - pathDurations.trip())) {
+ return MISSING_VALUE;
+ }
return pathDurations.mapToRouterArrivalTime(latestArrivalTime);
}
@@ -83,11 +92,11 @@ public int latestArrivalTime(int arrivalTime) {
public String toString() {
return ToStringBuilder
.of(FlexAccessEgress.class)
- .addNum("fromStopIndex", fromStopIndex)
- .addNum("toStopIndex", toStopIndex)
+ .addNum("boardStopPosition", boardStopPosition)
+ .addNum("alightStopPosition", alightStopPosition)
.addObj("durations", pathDurations)
.addObj("stop", stop)
- .addObj("trip", trip)
+ .addObj("trip", trip.getId())
.addObj("lastState", lastState)
.addBoolIfTrue("stopReachedOnBoard", stopReachedOnBoard)
.toString();
diff --git a/src/ext/java/org/opentripplanner/ext/flex/FlexParameters.java b/src/ext/java/org/opentripplanner/ext/flex/FlexParameters.java
new file mode 100644
index 00000000000..564991a94a0
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/flex/FlexParameters.java
@@ -0,0 +1,57 @@
+package org.opentripplanner.ext.flex;
+
+import java.time.Duration;
+
+/**
+ * Define parameters used to configure flex. For further documentation on these parameters, look
+ * at the {@link org.opentripplanner.standalone.config.sandbox.FlexConfig} class which implements
+ * this interface. The flex package does not use all parameters defined here. Some parameters are
+ * passed into the street search(AStar) as part of a flex use-case. We keep them here for
+ * completeness and simplicity (just one interface).
+ */
+public interface FlexParameters {
+ /**
+ * See {@link org.opentripplanner.standalone.config.sandbox.FlexConfig}
+ */
+ Duration maxTransferDuration();
+ /**
+ * See {@link org.opentripplanner.standalone.config.sandbox.FlexConfig}
+ */
+ Duration maxFlexTripDuration();
+ /**
+ * See {@link org.opentripplanner.standalone.config.sandbox.FlexConfig}
+ */
+ Duration maxAccessWalkDuration();
+ /**
+ * See {@link org.opentripplanner.standalone.config.sandbox.FlexConfig}
+ */
+ Duration maxEgressWalkDuration();
+
+ /**
+ * This defines the default values. This will be used by the OTP configuration and by tests,
+ * avoid using this directly.
+ */
+ static FlexParameters defaultValues() {
+ return new FlexParameters() {
+ @Override
+ public Duration maxTransferDuration() {
+ return Duration.ofMinutes(5);
+ }
+
+ @Override
+ public Duration maxFlexTripDuration() {
+ return Duration.ofMinutes(45);
+ }
+
+ @Override
+ public Duration maxAccessWalkDuration() {
+ return Duration.ofMinutes(45);
+ }
+
+ @Override
+ public Duration maxEgressWalkDuration() {
+ return Duration.ofMinutes(45);
+ }
+ };
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java b/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java
index c84cb8823a7..84098db9dc9 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java
@@ -1,33 +1,35 @@
package org.opentripplanner.ext.flex;
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
-import java.util.Comparator;
import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
+import javax.annotation.Nullable;
+import org.opentripplanner.astar.model.GraphPath;
import org.opentripplanner.ext.flex.flexpathcalculator.DirectFlexPathCalculator;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
import org.opentripplanner.ext.flex.flexpathcalculator.StreetFlexPathCalculator;
-import org.opentripplanner.ext.flex.template.FlexAccessTemplate;
-import org.opentripplanner.ext.flex.template.FlexEgressTemplate;
+import org.opentripplanner.ext.flex.template.DirectFlexPath;
+import org.opentripplanner.ext.flex.template.FlexAccessEgressCallbackAdapter;
+import org.opentripplanner.ext.flex.template.FlexAccessFactory;
+import org.opentripplanner.ext.flex.template.FlexDirectPathFactory;
+import org.opentripplanner.ext.flex.template.FlexEgressFactory;
+import org.opentripplanner.ext.flex.template.FlexServiceDate;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.framework.application.OTPRequestTimeoutException;
import org.opentripplanner.framework.time.ServiceDateUtils;
+import org.opentripplanner.model.PathTransfer;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.routing.algorithm.mapping.GraphPathToItineraryMapper;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graphfinder.NearbyStop;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
+import org.opentripplanner.street.model.vertex.TransitStopVertex;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.site.StopLocation;
+import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;
import org.opentripplanner.transit.service.TransitService;
public class FlexRouter {
@@ -36,31 +38,27 @@ public class FlexRouter {
private final Graph graph;
private final TransitService transitService;
- private final FlexConfig config;
+ private final FlexParameters flexParameters;
private final Collection streetAccesses;
private final Collection streetEgresses;
private final FlexIndex flexIndex;
private final FlexPathCalculator accessFlexPathCalculator;
private final FlexPathCalculator egressFlexPathCalculator;
private final GraphPathToItineraryMapper graphPathToItineraryMapper;
+ private final FlexAccessEgressCallbackAdapter callbackService;
/* Request data */
private final ZonedDateTime startOfTime;
- private final int departureTime;
- private final boolean arriveBy;
-
- private final FlexServiceDate[] dates;
-
- /* State */
- private List flexAccessTemplates = null;
- private List flexEgressTemplates = null;
+ private final int requestedTime;
+ private final int requestedBookingTime;
+ private final List dates;
public FlexRouter(
Graph graph,
TransitService transitService,
- FlexConfig config,
- Instant searchInstant,
- boolean arriveBy,
+ FlexParameters flexParameters,
+ Instant requestedTime,
+ @Nullable Instant requestedBookingTime,
int additionalPastSearchDays,
int additionalFutureSearchDays,
Collection streetAccesses,
@@ -68,10 +66,11 @@ public FlexRouter(
) {
this.graph = graph;
this.transitService = transitService;
- this.config = config;
+ this.flexParameters = flexParameters;
this.streetAccesses = streetAccesses;
this.streetEgresses = egressTransfers;
this.flexIndex = transitService.getFlexIndex();
+ this.callbackService = new CallbackAdapter();
this.graphPathToItineraryMapper =
new GraphPathToItineraryMapper(
transitService.getTimeZone(),
@@ -81,9 +80,9 @@ public FlexRouter(
if (graph.hasStreets) {
this.accessFlexPathCalculator =
- new StreetFlexPathCalculator(false, config.maxFlexTripDuration());
+ new StreetFlexPathCalculator(false, flexParameters.maxFlexTripDuration());
this.egressFlexPathCalculator =
- new StreetFlexPathCalculator(true, config.maxFlexTripDuration());
+ new StreetFlexPathCalculator(true, flexParameters.maxFlexTripDuration());
} else {
// this is only really useful in tests. in real world scenarios you're unlikely to get useful
// results if you don't have streets
@@ -92,161 +91,124 @@ public FlexRouter(
}
ZoneId tz = transitService.getTimeZone();
- LocalDate searchDate = LocalDate.ofInstant(searchInstant, tz);
+ LocalDate searchDate = LocalDate.ofInstant(requestedTime, tz);
this.startOfTime = ServiceDateUtils.asStartOfService(searchDate, tz);
- this.departureTime = ServiceDateUtils.secondsSinceStartOfTime(startOfTime, searchInstant);
- this.arriveBy = arriveBy;
+ this.requestedTime = ServiceDateUtils.secondsSinceStartOfTime(startOfTime, requestedTime);
+ this.requestedBookingTime =
+ requestedBookingTime == null
+ ? RoutingBookingInfo.NOT_SET
+ : ServiceDateUtils.secondsSinceStartOfTime(startOfTime, requestedBookingTime);
+ this.dates =
+ createFlexServiceDates(
+ transitService,
+ additionalPastSearchDays,
+ additionalFutureSearchDays,
+ searchDate
+ );
+ }
- int totalDays = additionalPastSearchDays + 1 + additionalFutureSearchDays;
+ public List createFlexOnlyItineraries(boolean arriveBy) {
+ OTPRequestTimeoutException.checkForTimeout();
- this.dates = new FlexServiceDate[totalDays];
+ var directFlexPaths = new FlexDirectPathFactory(
+ callbackService,
+ accessFlexPathCalculator,
+ egressFlexPathCalculator,
+ flexParameters.maxTransferDuration()
+ )
+ .calculateDirectFlexPaths(streetAccesses, streetEgresses, dates, requestedTime, arriveBy);
- for (int d = -additionalPastSearchDays; d <= additionalFutureSearchDays; ++d) {
- LocalDate date = searchDate.plusDays(d);
- int index = d + additionalPastSearchDays;
- dates[index] =
- new FlexServiceDate(
- date,
- ServiceDateUtils.secondsSinceStartOfTime(startOfTime, date),
- transitService.getServiceCodesRunningForDate(date)
- );
- }
- }
+ var itineraries = new ArrayList();
- public Collection createFlexOnlyItineraries() {
- OTPRequestTimeoutException.checkForTimeout();
- calculateFlexAccessTemplates();
- calculateFlexEgressTemplates();
-
- Multimap streetEgressByStop = HashMultimap.create();
- streetEgresses.forEach(it -> streetEgressByStop.put(it.stop, it));
-
- Collection itineraries = new ArrayList<>();
-
- for (FlexAccessTemplate template : this.flexAccessTemplates) {
- StopLocation transferStop = template.getTransferStop();
- if (
- this.flexEgressTemplates.stream()
- .anyMatch(t -> t.getAccessEgressStop().equals(transferStop))
- ) {
- for (NearbyStop egress : streetEgressByStop.get(transferStop)) {
- Itinerary itinerary = template.createDirectGraphPath(
- egress,
- arriveBy,
- departureTime,
- startOfTime,
- graphPathToItineraryMapper
- );
- if (itinerary != null) {
- itineraries.add(itinerary);
- }
- }
+ for (DirectFlexPath it : directFlexPaths) {
+ var startTime = startOfTime.plusSeconds(it.startTime());
+ var itinerary = graphPathToItineraryMapper
+ .generateItinerary(new GraphPath<>(it.state()))
+ .withTimeShiftToStartAt(startTime);
+
+ if (itinerary != null) {
+ itineraries.add(itinerary);
}
}
-
return itineraries;
}
public Collection createFlexAccesses() {
OTPRequestTimeoutException.checkForTimeout();
- calculateFlexAccessTemplates();
- return this.flexAccessTemplates.stream()
- .flatMap(template -> template.createFlexAccessEgressStream(graph, transitService))
- .toList();
+ return new FlexAccessFactory(
+ callbackService,
+ accessFlexPathCalculator,
+ flexParameters.maxTransferDuration()
+ )
+ .createFlexAccesses(streetAccesses, dates);
}
public Collection createFlexEgresses() {
OTPRequestTimeoutException.checkForTimeout();
- calculateFlexEgressTemplates();
-
- return this.flexEgressTemplates.stream()
- .flatMap(template -> template.createFlexAccessEgressStream(graph, transitService))
- .toList();
+ return new FlexEgressFactory(
+ callbackService,
+ egressFlexPathCalculator,
+ flexParameters.maxTransferDuration()
+ )
+ .createFlexEgresses(streetEgresses, dates);
}
- private void calculateFlexAccessTemplates() {
- if (this.flexAccessTemplates != null) {
- return;
- }
+ private List createFlexServiceDates(
+ TransitService transitService,
+ int additionalPastSearchDays,
+ int additionalFutureSearchDays,
+ LocalDate searchDate
+ ) {
+ final List dates = new ArrayList<>();
- // Fetch the closest flexTrips reachable from the access stops
- this.flexAccessTemplates =
- getClosestFlexTrips(streetAccesses, true)
- // For each date the router has data for
- .flatMap(it ->
- Arrays
- .stream(dates)
- // Discard if service is not running on date
- .filter(date -> date.isFlexTripRunning(it.flexTrip(), this.transitService))
- // Create templates from trip, boarding at the nearbyStop
- .flatMap(date ->
- it
- .flexTrip()
- .getFlexAccessTemplates(it.accessEgress(), date, accessFlexPathCalculator, config)
- )
+ // TODO - This code id not DRY, the same logic is in RaptorRoutingRequestTransitDataCreator
+ for (int d = -additionalPastSearchDays; d <= additionalFutureSearchDays; ++d) {
+ LocalDate date = searchDate.plusDays(d);
+ dates.add(
+ new FlexServiceDate(
+ date,
+ ServiceDateUtils.secondsSinceStartOfTime(startOfTime, date),
+ requestedBookingTime,
+ transitService.getServiceCodesRunningForDate(date)
)
- .toList();
+ );
+ }
+ return List.copyOf(dates);
}
- private void calculateFlexEgressTemplates() {
- if (this.flexEgressTemplates != null) {
- return;
+ /**
+ * This class work as an adaptor around OTP services. This allows us to pass in this instance
+ * and not the implementations (graph, transitService, flexIndex). We can easily mock this in
+ * unit-tests. This also serves as documentation of which services the flex access/egress
+ * generation logic needs.
+ */
+ private class CallbackAdapter implements FlexAccessEgressCallbackAdapter {
+
+ @Override
+ public TransitStopVertex getStopVertexForStopId(FeedScopedId stopId) {
+ return graph.getStopVertexForStopId(stopId);
}
- // Fetch the closest flexTrips reachable from the egress stops
- this.flexEgressTemplates =
- getClosestFlexTrips(streetEgresses, false)
- // For each date the router has data for
- .flatMap(it ->
- Arrays
- .stream(dates)
- // Discard if service is not running on date
- .filter(date -> date.isFlexTripRunning(it.flexTrip(), this.transitService))
- // Create templates from trip, alighting at the nearbyStop
- .flatMap(date ->
- it
- .flexTrip()
- .getFlexEgressTemplates(it.accessEgress(), date, egressFlexPathCalculator, config)
- )
- )
- .toList();
- }
+ @Override
+ public Collection getTransfersFromStop(StopLocation stop) {
+ return transitService.getTransfersByStop(stop);
+ }
- private Stream getClosestFlexTrips(
- Collection nearbyStops,
- boolean pickup
- ) {
- // Find all trips reachable from the nearbyStops
- Stream flexTripsReachableFromNearbyStops = nearbyStops
- .stream()
- .flatMap(accessEgress ->
- flexIndex
- .getFlexTripsByStop(accessEgress.stop)
- .stream()
- .filter(flexTrip ->
- pickup
- ? flexTrip.isBoardingPossible(accessEgress)
- : flexTrip.isAlightingPossible(accessEgress)
- )
- .map(flexTrip -> new AccessEgressAndNearbyStop(accessEgress, flexTrip))
- );
+ @Override
+ public Collection getTransfersToStop(StopLocation stop) {
+ return transitService.getFlexIndex().getTransfersToStop(stop);
+ }
- // Group all (NearbyStop, FlexTrip) tuples by flexTrip
- Collection> groupedReachableFlexTrips = flexTripsReachableFromNearbyStops
- .collect(Collectors.groupingBy(AccessEgressAndNearbyStop::flexTrip))
- .values();
-
- // Get the tuple with least walking time from each group
- return groupedReachableFlexTrips
- .stream()
- .map(t2s ->
- t2s
- .stream()
- .min(Comparator.comparingLong(t2 -> t2.accessEgress().state.getElapsedTimeSeconds()))
- )
- .flatMap(Optional::stream);
- }
+ @Override
+ public Collection> getFlexTripsByStop(StopLocation stopLocation) {
+ return flexIndex.getFlexTripsByStop(stopLocation);
+ }
- private record AccessEgressAndNearbyStop(NearbyStop accessEgress, FlexTrip, ?> flexTrip) {}
+ @Override
+ public boolean isDateActive(FlexServiceDate date, FlexTrip, ?> trip) {
+ int serviceCode = transitService.getServiceCodeForId(trip.getTrip().getServiceId());
+ return date.isTripServiceRunning(serviceCode);
+ }
+ }
}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/FlexServiceDate.java b/src/ext/java/org/opentripplanner/ext/flex/FlexServiceDate.java
deleted file mode 100644
index d2ead1c80be..00000000000
--- a/src/ext/java/org/opentripplanner/ext/flex/FlexServiceDate.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package org.opentripplanner.ext.flex;
-
-import gnu.trove.set.TIntSet;
-import java.time.LocalDate;
-import org.opentripplanner.ext.flex.trip.FlexTrip;
-import org.opentripplanner.transit.service.TransitService;
-
-/**
- * This class contains information used in a flex router, and depends on the date the search was
- * made on.
- */
-public class FlexServiceDate {
-
- /** The local date */
- public final LocalDate serviceDate;
-
- /**
- * How many seconds does this date's "midnight" (12 hours before noon) differ from the "midnight"
- * of the date for the search.
- */
- public final int secondsFromStartOfTime;
-
- /** Which services are running on the date. */
- public final TIntSet servicesRunning;
-
- public FlexServiceDate(
- LocalDate serviceDate,
- int secondsFromStartOfTime,
- TIntSet servicesRunning
- ) {
- this.serviceDate = serviceDate;
- this.secondsFromStartOfTime = secondsFromStartOfTime;
- this.servicesRunning = servicesRunning;
- }
-
- boolean isFlexTripRunning(FlexTrip flexTrip, TransitService transitService) {
- return (
- servicesRunning != null &&
- servicesRunning.contains(
- transitService.getServiceCodeForId(flexTrip.getTrip().getServiceId())
- )
- );
- }
-}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java b/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java
index b9e10e29214..0544a46da72 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/FlexibleTransitLeg.java
@@ -12,7 +12,6 @@
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.lang.DoubleUtils;
import org.opentripplanner.framework.tostring.ToStringBuilder;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.fare.FareProductUse;
import org.opentripplanner.model.plan.Leg;
@@ -28,6 +27,7 @@
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.organization.Operator;
import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* One leg of a trip -- that is, a temporally continuous piece of the journey that takes place on a
@@ -133,17 +133,17 @@ public I18NString getHeadsign() {
@Override
public LocalDate getServiceDate() {
- return edge.flexTemplate.serviceDate;
+ return edge.serviceDate();
}
@Override
public Place getFrom() {
- return Place.forFlexStop(edge.s1, edge.getFromVertex());
+ return Place.forFlexStop(edge.s1(), edge.getFromVertex());
}
@Override
public Place getTo() {
- return Place.forFlexStop(edge.s2, edge.getToVertex());
+ return Place.forFlexStop(edge.s2(), edge.getToVertex());
}
@Override
@@ -173,22 +173,22 @@ public PickDrop getAlightRule() {
@Override
public BookingInfo getDropOffBookingInfo() {
- return edge.getFlexTrip().getDropOffBookingInfo(getBoardStopPosInPattern());
+ return edge.getFlexTrip().getDropOffBookingInfo(getAlightStopPosInPattern());
}
@Override
public BookingInfo getPickupBookingInfo() {
- return edge.getFlexTrip().getPickupBookingInfo(getAlightStopPosInPattern());
+ return edge.getFlexTrip().getPickupBookingInfo(getBoardStopPosInPattern());
}
@Override
public Integer getBoardStopPosInPattern() {
- return edge.flexTemplate.fromStopIndex;
+ return edge.boardStopPosInPattern();
}
@Override
public Integer getAlightStopPosInPattern() {
- return edge.flexTemplate.toStopIndex;
+ return edge.alightStopPosInPattern();
}
@Override
diff --git a/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java b/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java
index acf2f8f95dc..40201295d7b 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/edgetype/FlexTripEdge.java
@@ -1,10 +1,10 @@
package org.opentripplanner.ext.flex.edgetype;
+import java.time.LocalDate;
import java.util.Objects;
import javax.annotation.Nonnull;
import org.locationtech.jts.geom.LineString;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPath;
-import org.opentripplanner.ext.flex.template.FlexAccessEgressTemplate;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.street.model.edge.Edge;
@@ -14,63 +14,66 @@
import org.opentripplanner.street.search.state.StateEditor;
import org.opentripplanner.transit.model.site.StopLocation;
+/**
+ * Flex trips edges are not connected to the graph.
+ */
public class FlexTripEdge extends Edge {
- private final FlexTrip trip;
- public final StopLocation s1;
- public final StopLocation s2;
- public final FlexAccessEgressTemplate flexTemplate;
- public final FlexPath flexPath;
+ private final StopLocation s1;
+ private final StopLocation s2;
+ private final FlexTrip, ?> trip;
+ private final int boardStopPosInPattern;
+ private final int alightStopPosInPattern;
+ private final LocalDate serviceDate;
+ private final FlexPath flexPath;
- private FlexTripEdge(
+ public FlexTripEdge(
Vertex v1,
Vertex v2,
StopLocation s1,
StopLocation s2,
- FlexTrip trip,
- FlexAccessEgressTemplate flexTemplate,
+ FlexTrip, ?> trip,
+ int boardStopPosInPattern,
+ int alightStopPosInPattern,
+ LocalDate serviceDate,
FlexPath flexPath
) {
super(v1, v2);
this.s1 = s1;
this.s2 = s2;
this.trip = trip;
- this.flexTemplate = flexTemplate;
+ this.boardStopPosInPattern = boardStopPosInPattern;
+ this.alightStopPosInPattern = alightStopPosInPattern;
+ this.serviceDate = serviceDate;
this.flexPath = Objects.requireNonNull(flexPath);
}
- /**
- * Create a Flex Trip.
- * Flex trips are not connected to the graph.
- */
- public static FlexTripEdge createFlexTripEdge(
- Vertex v1,
- Vertex v2,
- StopLocation s1,
- StopLocation s2,
- FlexTrip trip,
- FlexAccessEgressTemplate flexTemplate,
- FlexPath flexPath
- ) {
- return new FlexTripEdge(v1, v2, s1, s2, trip, flexTemplate, flexPath);
+ public StopLocation s1() {
+ return s1;
+ }
+
+ public StopLocation s2() {
+ return s2;
+ }
+
+ public int boardStopPosInPattern() {
+ return boardStopPosInPattern;
+ }
+
+ public int alightStopPosInPattern() {
+ return alightStopPosInPattern;
+ }
+
+ public LocalDate serviceDate() {
+ return serviceDate;
}
public int getTimeInSeconds() {
return flexPath.durationSeconds;
}
- @Override
- @Nonnull
- public State[] traverse(State s0) {
- StateEditor editor = s0.edit(this);
- editor.setBackMode(TraverseMode.FLEX);
- // TODO: decide good value
- editor.incrementWeight(10 * 60);
- int timeInSeconds = getTimeInSeconds();
- editor.incrementTimeInSeconds(timeInSeconds);
- editor.incrementWeight(timeInSeconds);
- editor.resetEnteredNoThroughTrafficArea();
- return editor.makeStateArray();
+ public FlexTrip, ?> getFlexTrip() {
+ return trip;
}
@Override
@@ -88,7 +91,17 @@ public double getDistanceMeters() {
return flexPath.distanceMeters;
}
- public FlexTrip getFlexTrip() {
- return trip;
+ @Override
+ @Nonnull
+ public State[] traverse(State s0) {
+ StateEditor editor = s0.edit(this);
+ editor.setBackMode(TraverseMode.FLEX);
+ // TODO: decide good value
+ editor.incrementWeight(10 * 60);
+ int timeInSeconds = getTimeInSeconds();
+ editor.incrementTimeInSeconds(timeInSeconds);
+ editor.incrementWeight(timeInSeconds);
+ editor.resetEnteredNoThroughTrafficArea();
+ return editor.makeStateArray();
}
}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/DirectFlexPathCalculator.java b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/DirectFlexPathCalculator.java
index 9a5b71257d0..793cd112444 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/DirectFlexPathCalculator.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/DirectFlexPathCalculator.java
@@ -22,7 +22,12 @@ public DirectFlexPathCalculator() {
}
@Override
- public FlexPath calculateFlexPath(Vertex fromv, Vertex tov, int fromStopIndex, int toStopIndex) {
+ public FlexPath calculateFlexPath(
+ Vertex fromv,
+ Vertex tov,
+ int boardStopPosition,
+ int alightStopPosition
+ ) {
double distance = SphericalDistanceLibrary.distance(fromv.getCoordinate(), tov.getCoordinate());
LineString geometry = GeometryUtils
.getGeometryFactory()
diff --git a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/FlexPathCalculator.java b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/FlexPathCalculator.java
index 98e218a1c0d..77dbc703424 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/FlexPathCalculator.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/FlexPathCalculator.java
@@ -8,5 +8,10 @@
*/
public interface FlexPathCalculator {
@Nullable
- FlexPath calculateFlexPath(Vertex fromv, Vertex tov, int fromStopIndex, int toStopIndex);
+ FlexPath calculateFlexPath(
+ Vertex fromv,
+ Vertex tov,
+ int boardStopPosition,
+ int alightStopPosition
+ );
}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculator.java b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculator.java
index cd5228dada5..7b4f6ecaf8b 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculator.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculator.java
@@ -13,26 +13,31 @@ public class ScheduledFlexPathCalculator implements FlexPathCalculator {
private final FlexPathCalculator flexPathCalculator;
private final FlexTrip trip;
- public ScheduledFlexPathCalculator(FlexPathCalculator flexPathCalculator, FlexTrip trip) {
+ public ScheduledFlexPathCalculator(FlexPathCalculator flexPathCalculator, FlexTrip, ?> trip) {
this.flexPathCalculator = flexPathCalculator;
this.trip = trip;
}
@Override
- public FlexPath calculateFlexPath(Vertex fromv, Vertex tov, int fromStopIndex, int toStopIndex) {
+ public FlexPath calculateFlexPath(
+ Vertex fromv,
+ Vertex tov,
+ int boardStopPosition,
+ int alightStopPosition
+ ) {
final var flexPath = flexPathCalculator.calculateFlexPath(
fromv,
tov,
- fromStopIndex,
- toStopIndex
+ boardStopPosition,
+ alightStopPosition
);
if (flexPath == null) {
return null;
}
int departureTime = trip.earliestDepartureTime(
Integer.MIN_VALUE,
- fromStopIndex,
- toStopIndex,
+ boardStopPosition,
+ alightStopPosition,
0
);
@@ -40,7 +45,12 @@ public FlexPath calculateFlexPath(Vertex fromv, Vertex tov, int fromStopIndex, i
return null;
}
- int arrivalTime = trip.latestArrivalTime(Integer.MAX_VALUE, fromStopIndex, toStopIndex, 0);
+ int arrivalTime = trip.latestArrivalTime(
+ Integer.MAX_VALUE,
+ boardStopPosition,
+ alightStopPosition,
+ 0
+ );
if (arrivalTime == MISSING_VALUE) {
return null;
diff --git a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/StreetFlexPathCalculator.java b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/StreetFlexPathCalculator.java
index 9870f7013a5..597afd9b17a 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/StreetFlexPathCalculator.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/StreetFlexPathCalculator.java
@@ -42,7 +42,12 @@ public StreetFlexPathCalculator(boolean reverseDirection, Duration maxFlexTripDu
}
@Override
- public FlexPath calculateFlexPath(Vertex fromv, Vertex tov, int fromStopIndex, int toStopIndex) {
+ public FlexPath calculateFlexPath(
+ Vertex fromv,
+ Vertex tov,
+ int boardStopPosition,
+ int alightStopPosition
+ ) {
// These are the origin and destination vertices from the perspective of the one-to-many search,
// which may be reversed
Vertex originVertex = reverseDirection ? tov : fromv;
diff --git a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/TimePenaltyCalculator.java b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/TimePenaltyCalculator.java
index a2252f3fec8..e7a26ec1697 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/TimePenaltyCalculator.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/flexpathcalculator/TimePenaltyCalculator.java
@@ -20,8 +20,13 @@ public TimePenaltyCalculator(FlexPathCalculator delegate, TimePenalty penalty) {
@Nullable
@Override
- public FlexPath calculateFlexPath(Vertex fromv, Vertex tov, int fromStopIndex, int toStopIndex) {
- var path = delegate.calculateFlexPath(fromv, tov, fromStopIndex, toStopIndex);
+ public FlexPath calculateFlexPath(
+ Vertex fromv,
+ Vertex tov,
+ int boardStopPosition,
+ int alightStopPosition
+ ) {
+ var path = delegate.calculateFlexPath(fromv, tov, boardStopPosition, alightStopPosition);
if (path == null) {
return null;
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java b/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java
similarity index 63%
rename from src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java
rename to src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java
index 68f490f775f..9ce9cf8a4c4 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressTemplate.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java
@@ -1,5 +1,6 @@
package org.opentripplanner.ext.flex.template;
+import java.time.Duration;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
@@ -9,23 +10,18 @@
import javax.annotation.Nullable;
import org.opentripplanner.ext.flex.FlexAccessEgress;
import org.opentripplanner.ext.flex.FlexPathDurations;
-import org.opentripplanner.ext.flex.FlexServiceDate;
import org.opentripplanner.ext.flex.edgetype.FlexTripEdge;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.framework.tostring.ToStringBuilder;
import org.opentripplanner.model.PathTransfer;
-import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graphfinder.NearbyStop;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.street.model.edge.Edge;
-import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.state.EdgeTraverser;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.transit.model.site.RegularStop;
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,
@@ -33,90 +29,96 @@
*
* Please also see Flex.svg for an illustration of how the flex concepts relate to each other.
*/
-public abstract class FlexAccessEgressTemplate {
+abstract class AbstractFlexTemplate {
/**
* We do not want extremely short flex trips, they will normally be dominated in the
* routing later. We set an absolute min duration to 10 seconds (167m with 60 km/h).
*/
private static final int MIN_FLEX_TRIP_DURATION_SECONDS = 10;
+
+ // TODO - This is confusing, and not following OO principles. The from/to stop
+ // - changes type for access/egress, move them down into child class.
+ // - this apply to transferStop as well.
protected final NearbyStop accessEgress;
- protected final FlexTrip trip;
- public final int fromStopIndex;
- public final int toStopIndex;
+ protected final FlexTrip, ?> trip;
+ protected final int boardStopPosition;
+ protected final int alightStopPosition;
protected final StopLocation transferStop;
protected final int secondsFromStartOfTime;
- public final LocalDate serviceDate;
+ protected final LocalDate serviceDate;
+ protected final int requestedBookingTime;
protected final FlexPathCalculator calculator;
- private final FlexConfig flexConfig;
+ private final Duration maxTransferDuration;
/**
- * @param accessEgress Path from origin to the point of boarding for this flex trip
- * @param trip The FlexTrip used for this Template
- * @param fromStopIndex Stop sequence index where this FlexTrip is boarded
- * @param toStopIndex The stop where this FlexTrip alights
- * @param transferStop The stop location where this FlexTrip alights
- * @param date The service date of this FlexTrip
- * @param calculator Calculates the path and duration of the FlexTrip
+ * @param trip The FlexTrip used for this template
+ * @param accessEgress Path from origin/destination to the point of boarding/alighting for
+ * this flex trip
+ * @param transferStop The stop location where this FlexTrip transfers to another transit
+ * service.
+ * @param boardStopPosition The stop-board-position in the trip pattern
+ * @param alightStopPosition The stop-alight-position in the trip pattern
+ * @param date The service date of this FlexTrip
+ * @param calculator Calculates the path and duration of the FlexTrip
+ * @param maxTransferDuration The limit for how long a transfer is allowed to be
*/
- FlexAccessEgressTemplate(
+ AbstractFlexTemplate(
+ FlexTrip, ?> trip,
NearbyStop accessEgress,
- FlexTrip trip,
- int fromStopIndex,
- int toStopIndex,
StopLocation transferStop,
+ int boardStopPosition,
+ int alightStopPosition,
FlexServiceDate date,
FlexPathCalculator calculator,
- FlexConfig config
+ Duration maxTransferDuration
) {
this.accessEgress = accessEgress;
this.trip = trip;
- this.fromStopIndex = fromStopIndex;
- this.toStopIndex = toStopIndex;
+ this.boardStopPosition = boardStopPosition;
+ this.alightStopPosition = alightStopPosition;
this.transferStop = transferStop;
- this.secondsFromStartOfTime = date.secondsFromStartOfTime;
- this.serviceDate = date.serviceDate;
+ this.secondsFromStartOfTime = date.secondsFromStartOfTime();
+ this.serviceDate = date.serviceDate();
+ this.requestedBookingTime = date.requestedBookingTime();
this.calculator = calculator;
- this.flexConfig = config;
+ this.maxTransferDuration = maxTransferDuration;
}
- public StopLocation getTransferStop() {
+ StopLocation getTransferStop() {
return transferStop;
}
- public StopLocation getAccessEgressStop() {
+ StopLocation getAccessEgressStop() {
return accessEgress.stop;
}
/**
- * This method is very much the hot code path in the flex access/egress search so any optimization
- * here will lead to noticeable speedups.
+ * This method is very much the hot code path in the flex access/egress search, so any
+ * optimization here will lead to noticeable speedups.
*/
- public Stream createFlexAccessEgressStream(
- Graph graph,
- TransitService transitService
- ) {
+ Stream createFlexAccessEgressStream(FlexAccessEgressCallbackAdapter callback) {
if (transferStop instanceof RegularStop stop) {
- TransitStopVertex flexVertex = graph.getStopVertexForStopId(stop.getId());
+ var flexVertex = callback.getStopVertexForStopId(stop.getId());
return Stream
- .of(getFlexAccessEgress(new ArrayList<>(), flexVertex, (RegularStop) transferStop))
+ .of(createFlexAccessEgress(new ArrayList<>(), flexVertex, stop))
.filter(Objects::nonNull);
}
// transferStop is Location Area/Line
else {
double maxDistanceMeters =
- flexConfig.maxTransferDuration().getSeconds() *
+ maxTransferDuration.getSeconds() *
accessEgress.state.getRequest().preferences().walk().speed();
- return getTransfersFromTransferStop(transitService)
+ return getTransfersFromTransferStop(callback)
.stream()
.filter(pathTransfer -> pathTransfer.getDistanceMeters() <= maxDistanceMeters)
.filter(transfer -> getFinalStop(transfer) != null)
.map(transfer -> {
- List edges = getTransferEdges(transfer);
- Vertex flexVertex = getFlexVertex(edges.get(0));
- RegularStop finalStop = getFinalStop(transfer);
- return getFlexAccessEgress(edges, flexVertex, finalStop);
+ var edges = getTransferEdges(transfer);
+ var flexVertex = getFlexVertex(edges.get(0));
+ var finalStop = getFinalStop(transfer);
+ return createFlexAccessEgress(edges, flexVertex, finalStop);
})
.filter(Objects::nonNull);
}
@@ -125,16 +127,16 @@ public Stream createFlexAccessEgressStream(
@Override
public String toString() {
return ToStringBuilder
- .of(FlexAccessEgressTemplate.class)
+ .of(AbstractFlexTemplate.class)
.addObj("accessEgress", accessEgress)
.addObj("trip", trip)
- .addNum("fromStopIndex", fromStopIndex)
- .addNum("toStopIndex", toStopIndex)
+ .addNum("boardStopPosition", boardStopPosition)
+ .addNum("alightStopPosition", alightStopPosition)
.addObj("transferStop", transferStop)
.addServiceTime("secondsFromStartOfTime", secondsFromStartOfTime)
.addDate("serviceDate", serviceDate)
.addObj("calculator", calculator)
- .addObj("flexConfig", flexConfig)
+ .addDuration("maxTransferDuration", maxTransferDuration)
.toString();
}
@@ -154,7 +156,7 @@ public String toString() {
* flex ride for the access/egress.
*/
protected abstract Collection getTransfersFromTransferStop(
- TransitService transitService
+ FlexAccessEgressCallbackAdapter callback
);
/**
@@ -177,21 +179,21 @@ protected abstract FlexPathDurations calculateFlexPathDurations(
protected abstract FlexTripEdge getFlexEdge(Vertex flexFromVertex, StopLocation transferStop);
@Nullable
- protected FlexAccessEgress getFlexAccessEgress(
+ private FlexAccessEgress createFlexAccessEgress(
List transferEdges,
Vertex flexVertex,
RegularStop stop
) {
var flexEdge = getFlexEdge(flexVertex, transferStop);
- // Drop none routable and very short(<10s) trips
+ // Drop non-routable and very short(<10s) trips
if (flexEdge == null || flexEdge.getTimeInSeconds() < MIN_FLEX_TRIP_DURATION_SECONDS) {
return null;
}
// this code is a little repetitive but needed as a performance improvement. previously
// the flex path was checked before this method was called. this meant that every path
- // was traversed twice leading to a noticeable slowdown.
+ // was traversed twice, leading to a noticeable slowdown.
final var afterFlexState = flexEdge.traverse(accessEgress.state);
if (State.isEmpty(afterFlexState)) {
return null;
@@ -206,11 +208,12 @@ protected FlexAccessEgress getFlexAccessEgress(
return new FlexAccessEgress(
stop,
durations,
- fromStopIndex,
- toStopIndex,
+ boardStopPosition,
+ alightStopPosition,
trip,
finalState,
- transferEdges.isEmpty()
+ transferEdges.isEmpty(),
+ requestedBookingTime
);
})
.orElse(null);
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java b/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java
new file mode 100644
index 00000000000..a30ebacc497
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/ClosestTrip.java
@@ -0,0 +1,134 @@
+package org.opentripplanner.ext.flex.template;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import org.opentripplanner.ext.flex.trip.FlexTrip;
+import org.opentripplanner.framework.lang.IntUtils;
+import org.opentripplanner.routing.graphfinder.NearbyStop;
+import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;
+
+/**
+ * The combination of the closest stop, trip and trip active date.
+ */
+record ClosestTrip(
+ NearbyStop nearbyStop,
+ FlexTrip, ?> flexTrip,
+ int stopPos,
+ FlexServiceDate activeDate
+) {
+ ClosestTrip(
+ NearbyStop nearbyStop,
+ FlexTrip, ?> flexTrip,
+ int stopPos,
+ FlexServiceDate activeDate
+ ) {
+ this.nearbyStop = Objects.requireNonNull(nearbyStop);
+ this.flexTrip = Objects.requireNonNull(flexTrip);
+ this.stopPos = IntUtils.requireNotNegative(stopPos, "stopPos");
+ this.activeDate = activeDate;
+ }
+
+ /**
+ * Create a temporary closest-trip without an active-date
+ */
+ private ClosestTrip(NearbyStop nearbyStop, FlexTrip, ?> flexTrip, int stopPos) {
+ this(nearbyStop, flexTrip, stopPos, null);
+ }
+
+ private ClosestTrip(ClosestTrip original, FlexServiceDate activeDate) {
+ this(original.nearbyStop, original.flexTrip, original.stopPos, activeDate);
+ }
+
+ /**
+ * Create a set of the closest trips running on the dates provided. Only the
+ * combination of the closest nearby-stop and trip is kept. For each combination,
+ * the set of dates is checked, and an instance with each active date is returned.
+ */
+ static Collection of(
+ FlexAccessEgressCallbackAdapter callbackService,
+ Collection nearbyStops,
+ List dates,
+ boolean pickup
+ ) {
+ var closestTrips = findAllTripsReachableFromNearbyStop(callbackService, nearbyStops, pickup);
+ return findActiveDatesForTripAndDecorateResult(callbackService, dates, closestTrips, true);
+ }
+
+ @Override
+ public FlexServiceDate activeDate() {
+ // The active date is not required as an internal "trick" to create closest-trips
+ // in two steps, but the instance is not valid before the active-date is added. This
+ // method should not be used inside this class, only on fully constructed valid instances;
+ // Hence the active-date should not be null.
+ return Objects.requireNonNull(activeDate);
+ }
+
+ private static Map, ClosestTrip> findAllTripsReachableFromNearbyStop(
+ FlexAccessEgressCallbackAdapter callbackService,
+ Collection nearbyStops,
+ boolean pickup
+ ) {
+ var map = new HashMap, ClosestTrip>();
+ for (NearbyStop nearbyStop : nearbyStops) {
+ var stop = nearbyStop.stop;
+ for (var trip : callbackService.getFlexTripsByStop(stop)) {
+ int stopPos = pickup ? trip.findBoardIndex(stop) : trip.findAlightIndex(stop);
+ if (stopPos != FlexTrip.STOP_INDEX_NOT_FOUND) {
+ var existing = map.get(trip);
+ if (existing == null || nearbyStop.isBetter(existing.nearbyStop())) {
+ map.put(trip, new ClosestTrip(nearbyStop, trip, stopPos));
+ }
+ }
+ }
+ }
+ return map;
+ }
+
+ private static ArrayList findActiveDatesForTripAndDecorateResult(
+ FlexAccessEgressCallbackAdapter callbackService,
+ List dates,
+ Map, ClosestTrip> map,
+ boolean pickup
+ ) {
+ var result = new ArrayList();
+ // Add active dates
+ for (Map.Entry, ClosestTrip> e : map.entrySet()) {
+ var trip = e.getKey();
+ var closestTrip = e.getValue();
+ // Include dates where the service is running
+ for (FlexServiceDate date : dates) {
+ // Filter away boardings early. This needs to be done for egress as well when the
+ // board stop is known (not known here).
+ if (pickup && exceedsLatestBookingTime(trip, date, closestTrip.stopPos())) {
+ continue;
+ }
+ if (callbackService.isDateActive(date, trip)) {
+ result.add(closestTrip.withDate(date));
+ }
+ }
+ }
+ return result;
+ }
+
+ private ClosestTrip withDate(FlexServiceDate date) {
+ Objects.requireNonNull(date);
+ return new ClosestTrip(this, date);
+ }
+
+ /**
+ * Check if the trip can be booked at the given date and boarding stop position.
+ */
+ private static boolean exceedsLatestBookingTime(
+ FlexTrip, ?> trip,
+ FlexServiceDate date,
+ int stopPos
+ ) {
+ return RoutingBookingInfo
+ .of(date.requestedBookingTime(), trip.getPickupBookingInfo(stopPos))
+ .exceedsLatestBookingTime();
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/DirectFlexPath.java b/src/ext/java/org/opentripplanner/ext/flex/template/DirectFlexPath.java
new file mode 100644
index 00000000000..3ca6225c2ec
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/DirectFlexPath.java
@@ -0,0 +1,11 @@
+package org.opentripplanner.ext.flex.template;
+
+import org.opentripplanner.street.search.state.State;
+
+/**
+ * This is the result of a direct flex search. It only contains the start-time and
+ * the AStar state. It is used by the FlexRouter to build an itinerary.
+ *
+ * This is a simple data-transfer-object (design pattern).
+ */
+public record DirectFlexPath(int startTime, State state) {}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java
new file mode 100644
index 00000000000..792213cd2c8
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessEgressCallbackAdapter.java
@@ -0,0 +1,39 @@
+package org.opentripplanner.ext.flex.template;
+
+import java.util.Collection;
+import org.opentripplanner.ext.flex.trip.FlexTrip;
+import org.opentripplanner.model.PathTransfer;
+import org.opentripplanner.street.model.vertex.TransitStopVertex;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.transit.model.site.StopLocation;
+
+/**
+ * To perform access/egress/direct flex searches, this module (this package) needs these
+ * services. We do not want to inject the implementations here and create unnecessary
+ * hard dependencies. By doing this, we explicitly list all external services needed and make
+ * testing easier. This also serves as documentation.
+ *
+ * The implementation of this interface will for the most part just delegate to the implementing
+ * OTP service - look in these services for the documentation.
+ */
+public interface FlexAccessEgressCallbackAdapter {
+ /** Adapter, look at implementing service for documentation. */
+ TransitStopVertex getStopVertexForStopId(FeedScopedId id);
+
+ /** Adapter, look at implementing service for documentation. */
+ Collection getTransfersFromStop(StopLocation stop);
+
+ /** Adapter, look at implementing service for documentation. */
+ Collection getTransfersToStop(StopLocation stop);
+
+ /** Adapter, look at implementing service for documentation. */
+ Collection> getFlexTripsByStop(StopLocation stopLocation);
+
+ /**
+ * Return true if date is an active service date for the given trip, and can be used for
+ * the given boarding stop position. The implementation should check that the trip is in
+ * service for the given date. It should check other restrictions as well, like booking
+ * arrangement constraints.
+ */
+ boolean isDateActive(FlexServiceDate date, FlexTrip, ?> trip);
+}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessFactory.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessFactory.java
new file mode 100644
index 00000000000..a4d348e6603
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessFactory.java
@@ -0,0 +1,46 @@
+package org.opentripplanner.ext.flex.template;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.List;
+import org.opentripplanner.ext.flex.FlexAccessEgress;
+import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
+import org.opentripplanner.routing.graphfinder.NearbyStop;
+
+public class FlexAccessFactory {
+
+ private final FlexAccessEgressCallbackAdapter callbackService;
+ private final FlexTemplateFactory templateFactory;
+
+ public FlexAccessFactory(
+ FlexAccessEgressCallbackAdapter callbackService,
+ FlexPathCalculator pathCalculator,
+ Duration maxTransferDuration
+ ) {
+ this.callbackService = callbackService;
+ this.templateFactory = FlexTemplateFactory.of(pathCalculator, maxTransferDuration);
+ }
+
+ public List createFlexAccesses(
+ Collection streetAccesses,
+ List dates
+ ) {
+ var flexAccessTemplates = calculateFlexAccessTemplates(streetAccesses, dates);
+
+ return flexAccessTemplates
+ .stream()
+ .flatMap(template -> template.createFlexAccessEgressStream(callbackService))
+ .toList();
+ }
+
+ List calculateFlexAccessTemplates(
+ Collection streetAccesses,
+ List dates
+ ) {
+ var closestFlexTrips = ClosestTrip.of(callbackService, streetAccesses, dates, true);
+ return closestFlexTrips
+ .stream()
+ .flatMap(it -> templateFactory.createAccessTemplates(it).stream())
+ .toList();
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java
index 39963bcf69e..08d130ecec0 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexAccessTemplate.java
@@ -1,113 +1,42 @@
package org.opentripplanner.ext.flex.template;
-import static org.opentripplanner.model.StopTime.MISSING_VALUE;
-
-import java.time.ZonedDateTime;
+import java.time.Duration;
import java.util.Collection;
import java.util.List;
-import org.opentripplanner.astar.model.GraphPath;
import org.opentripplanner.ext.flex.FlexPathDurations;
-import org.opentripplanner.ext.flex.FlexServiceDate;
import org.opentripplanner.ext.flex.edgetype.FlexTripEdge;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.model.PathTransfer;
-import org.opentripplanner.model.plan.Itinerary;
-import org.opentripplanner.routing.algorithm.mapping.GraphPathToItineraryMapper;
import org.opentripplanner.routing.graphfinder.NearbyStop;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.vertex.Vertex;
-import org.opentripplanner.street.search.state.EdgeTraverser;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
-import org.opentripplanner.transit.service.TransitService;
-public class FlexAccessTemplate extends FlexAccessEgressTemplate {
+class FlexAccessTemplate extends AbstractFlexTemplate {
- public FlexAccessTemplate(
- NearbyStop accessEgress,
- FlexTrip trip,
- int fromStopTime,
- int toStopTime,
- StopLocation transferStop,
+ FlexAccessTemplate(
+ FlexTrip, ?> trip,
+ NearbyStop boardStop,
+ int boardStopPosition,
+ StopLocation alightStop,
+ int alightStopPosition,
FlexServiceDate date,
FlexPathCalculator calculator,
- FlexConfig config
- ) {
- super(accessEgress, trip, fromStopTime, toStopTime, transferStop, date, calculator, config);
- }
-
- public Itinerary createDirectGraphPath(
- NearbyStop egress,
- boolean arriveBy,
- int departureTime,
- ZonedDateTime startOfTime,
- GraphPathToItineraryMapper graphPathToItineraryMapper
+ Duration maxTransferDuration
) {
- List egressEdges = egress.edges;
-
- Vertex flexToVertex = egress.state.getVertex();
-
- if (!isRouteable(flexToVertex)) {
- return null;
- }
-
- var flexEdge = getFlexEdge(flexToVertex, egress.stop);
-
- if (flexEdge == null) {
- return null;
- }
-
- final State[] afterFlexState = flexEdge.traverse(accessEgress.state);
-
- var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState[0], egressEdges);
-
- return finalStateOpt
- .map(finalState -> {
- var flexDurations = calculateFlexPathDurations(flexEdge, finalState);
-
- int timeShift;
-
- if (arriveBy) {
- int lastStopArrivalTime = flexDurations.mapToFlexTripArrivalTime(departureTime);
- int latestArrivalTime = trip.latestArrivalTime(
- lastStopArrivalTime,
- fromStopIndex,
- toStopIndex,
- flexDurations.trip()
- );
-
- if (latestArrivalTime == MISSING_VALUE) {
- return null;
- }
-
- // Shift from departing at departureTime to arriving at departureTime
- timeShift =
- flexDurations.mapToRouterArrivalTime(latestArrivalTime) - flexDurations.total();
- } else {
- int firstStopDepartureTime = flexDurations.mapToFlexTripDepartureTime(departureTime);
- int earliestDepartureTime = trip.earliestDepartureTime(
- firstStopDepartureTime,
- fromStopIndex,
- toStopIndex,
- flexDurations.trip()
- );
-
- if (earliestDepartureTime == MISSING_VALUE) {
- return null;
- }
- timeShift = flexDurations.mapToRouterDepartureTime(earliestDepartureTime);
- }
-
- ZonedDateTime startTime = startOfTime.plusSeconds(timeShift);
-
- return graphPathToItineraryMapper
- .generateItinerary(new GraphPath<>(finalState))
- .withTimeShiftToStartAt(startTime);
- })
- .orElse(null);
+ super(
+ trip,
+ boardStop,
+ alightStop,
+ boardStopPosition,
+ alightStopPosition,
+ date,
+ calculator,
+ maxTransferDuration
+ );
}
protected List getTransferEdges(PathTransfer transfer) {
@@ -118,8 +47,10 @@ protected RegularStop getFinalStop(PathTransfer transfer) {
return transfer.to instanceof RegularStop ? (RegularStop) transfer.to : null;
}
- protected Collection getTransfersFromTransferStop(TransitService transitService) {
- return transitService.getTransfersByStop(transferStop);
+ protected Collection getTransfersFromTransferStop(
+ FlexAccessEgressCallbackAdapter callback
+ ) {
+ return callback.getTransfersFromStop(transferStop);
}
protected Vertex getFlexVertex(Edge edge) {
@@ -142,36 +73,24 @@ protected FlexTripEdge getFlexEdge(Vertex flexToVertex, StopLocation transferSto
var flexPath = calculator.calculateFlexPath(
accessEgress.state.getVertex(),
flexToVertex,
- fromStopIndex,
- toStopIndex
+ boardStopPosition,
+ alightStopPosition
);
if (flexPath == null) {
return null;
}
- return FlexTripEdge.createFlexTripEdge(
+ return new FlexTripEdge(
accessEgress.state.getVertex(),
flexToVertex,
accessEgress.stop,
transferStop,
trip,
- this,
+ boardStopPosition,
+ alightStopPosition,
+ serviceDate,
flexPath
);
}
-
- protected boolean isRouteable(Vertex flexVertex) {
- if (accessEgress.state.getVertex() == flexVertex) {
- return false;
- } else return (
- calculator.calculateFlexPath(
- accessEgress.state.getVertex(),
- flexVertex,
- fromStopIndex,
- toStopIndex
- ) !=
- null
- );
- }
}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java
new file mode 100644
index 00000000000..f27a502911f
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java
@@ -0,0 +1,192 @@
+package org.opentripplanner.ext.flex.template;
+
+import static org.opentripplanner.model.StopTime.MISSING_VALUE;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
+import org.opentripplanner.routing.graphfinder.NearbyStop;
+import org.opentripplanner.street.model.vertex.Vertex;
+import org.opentripplanner.street.search.state.EdgeTraverser;
+import org.opentripplanner.street.search.state.State;
+import org.opentripplanner.transit.model.site.StopLocation;
+import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;
+
+public class FlexDirectPathFactory {
+
+ private final FlexAccessEgressCallbackAdapter callbackService;
+ private final FlexPathCalculator accessPathCalculator;
+ private final FlexPathCalculator egressPathCalculator;
+ private final Duration maxTransferDuration;
+
+ public FlexDirectPathFactory(
+ FlexAccessEgressCallbackAdapter callbackService,
+ FlexPathCalculator accessPathCalculator,
+ FlexPathCalculator egressPathCalculator,
+ Duration maxTransferDuration
+ ) {
+ this.callbackService = callbackService;
+ this.accessPathCalculator = accessPathCalculator;
+ this.egressPathCalculator = egressPathCalculator;
+ this.maxTransferDuration = maxTransferDuration;
+ }
+
+ public Collection calculateDirectFlexPaths(
+ Collection streetAccesses,
+ Collection streetEgresses,
+ List dates,
+ int requestTime,
+ boolean arriveBy
+ ) {
+ Collection directFlexPaths = new ArrayList<>();
+
+ var flexAccessTemplates = new FlexAccessFactory(
+ callbackService,
+ accessPathCalculator,
+ maxTransferDuration
+ )
+ .calculateFlexAccessTemplates(streetAccesses, dates);
+
+ var flexEgressTemplates = new FlexEgressFactory(
+ callbackService,
+ egressPathCalculator,
+ maxTransferDuration
+ )
+ .calculateFlexEgressTemplates(streetEgresses, dates);
+
+ Multimap streetEgressByStop = HashMultimap.create();
+ streetEgresses.forEach(it -> streetEgressByStop.put(it.stop, it));
+
+ for (FlexAccessTemplate template : flexAccessTemplates) {
+ StopLocation transferStop = template.getTransferStop();
+
+ // TODO: Document or reimplement this. Why are we using the egress to see if the
+ // access-transfer-stop (last-stop) is used by at least one egress-template?
+ // Is it because:
+ // - of the group-stop expansion?
+ // - of the alight-restriction check?
+ // - nearest stop to trip match?
+ // Fix: Find out why and refactor out the business logic and reuse it.
+ // Problem: Any asymmetrical restriction witch apply/do not apply to the egress,
+ // but do not apply/apply to the access, like booking-notice.
+ if (
+ flexEgressTemplates.stream().anyMatch(t -> t.getAccessEgressStop().equals(transferStop))
+ ) {
+ for (NearbyStop egress : streetEgressByStop.get(transferStop)) {
+ createDirectGraphPath(template, egress, arriveBy, requestTime)
+ .ifPresent(directFlexPaths::add);
+ }
+ }
+ }
+
+ return directFlexPaths;
+ }
+
+ private Optional createDirectGraphPath(
+ FlexAccessTemplate accessTemplate,
+ NearbyStop egress,
+ boolean arriveBy,
+ int requestTime
+ ) {
+ var accessNearbyStop = accessTemplate.accessEgress;
+ var trip = accessTemplate.trip;
+ int accessBoardStopPosition = accessTemplate.boardStopPosition;
+ int accessAlightStopPosition = accessTemplate.alightStopPosition;
+ int requestedBookingTime = accessTemplate.requestedBookingTime;
+
+ var flexToVertex = egress.state.getVertex();
+
+ if (!isRouteable(accessTemplate, flexToVertex)) {
+ return Optional.empty();
+ }
+
+ var flexEdge = accessTemplate.getFlexEdge(flexToVertex, egress.stop);
+
+ if (flexEdge == null) {
+ return Optional.empty();
+ }
+
+ final State[] afterFlexState = flexEdge.traverse(accessNearbyStop.state);
+
+ var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState[0], egress.edges);
+
+ if (finalStateOpt.isEmpty()) {
+ return Optional.empty();
+ }
+
+ var finalState = finalStateOpt.get();
+ var flexDurations = accessTemplate.calculateFlexPathDurations(flexEdge, finalState);
+
+ int timeShift;
+
+ if (arriveBy) {
+ int lastStopArrivalTime = flexDurations.mapToFlexTripArrivalTime(requestTime);
+ int latestArrivalTime = trip.latestArrivalTime(
+ lastStopArrivalTime,
+ accessBoardStopPosition,
+ accessAlightStopPosition,
+ flexDurations.trip()
+ );
+
+ if (latestArrivalTime == MISSING_VALUE) {
+ return Optional.empty();
+ }
+
+ // No need to time-shift latestArrivalTime for meeting the min-booking notice restriction,
+ // the time is already as-late-as-possible
+ var bookingInfo = RoutingBookingInfo.of(
+ requestedBookingTime,
+ trip.getPickupBookingInfo(accessTemplate.boardStopPosition)
+ );
+ if (bookingInfo.exceedsMinimumBookingNotice(latestArrivalTime)) {
+ return Optional.empty();
+ }
+
+ // Shift from departing at departureTime to arriving at departureTime
+ timeShift = flexDurations.mapToRouterArrivalTime(latestArrivalTime) - flexDurations.total();
+ } else {
+ int firstStopDepartureTime = flexDurations.mapToFlexTripDepartureTime(requestTime);
+
+ // Time-shift departure so the minimum-booking-notice restriction is honored.
+ var bookingInfo = trip.getPickupBookingInfo(accessBoardStopPosition);
+ firstStopDepartureTime =
+ RoutingBookingInfo
+ .of(requestedBookingTime, bookingInfo)
+ .earliestDepartureTime(firstStopDepartureTime);
+
+ int earliestDepartureTime = trip.earliestDepartureTime(
+ firstStopDepartureTime,
+ accessBoardStopPosition,
+ accessAlightStopPosition,
+ flexDurations.trip()
+ );
+
+ if (earliestDepartureTime == MISSING_VALUE) {
+ return Optional.empty();
+ }
+
+ timeShift = flexDurations.mapToRouterDepartureTime(earliestDepartureTime);
+ }
+
+ return Optional.of(new DirectFlexPath(timeShift, finalState));
+ }
+
+ protected boolean isRouteable(FlexAccessTemplate accessTemplate, Vertex flexVertex) {
+ if (accessTemplate.accessEgress.state.getVertex() == flexVertex) {
+ return false;
+ } else return (
+ accessTemplate.calculator.calculateFlexPath(
+ accessTemplate.accessEgress.state.getVertex(),
+ flexVertex,
+ accessTemplate.boardStopPosition,
+ accessTemplate.alightStopPosition
+ ) !=
+ null
+ );
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressFactory.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressFactory.java
new file mode 100644
index 00000000000..28908cb7e7b
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressFactory.java
@@ -0,0 +1,46 @@
+package org.opentripplanner.ext.flex.template;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.List;
+import org.opentripplanner.ext.flex.FlexAccessEgress;
+import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
+import org.opentripplanner.routing.graphfinder.NearbyStop;
+
+public class FlexEgressFactory {
+
+ private final FlexAccessEgressCallbackAdapter callbackService;
+ private final FlexTemplateFactory templateFactory;
+
+ public FlexEgressFactory(
+ FlexAccessEgressCallbackAdapter callbackService,
+ FlexPathCalculator pathCalculator,
+ Duration maxTransferDuration
+ ) {
+ this.callbackService = callbackService;
+ this.templateFactory = FlexTemplateFactory.of(pathCalculator, maxTransferDuration);
+ }
+
+ public List createFlexEgresses(
+ Collection streetEgresses,
+ List dates
+ ) {
+ var flexEgressTemplates = calculateFlexEgressTemplates(streetEgresses, dates);
+
+ return flexEgressTemplates
+ .stream()
+ .flatMap(template -> template.createFlexAccessEgressStream(callbackService))
+ .toList();
+ }
+
+ List calculateFlexEgressTemplates(
+ Collection streetEgresses,
+ List dates
+ ) {
+ var closestFlexTrips = ClosestTrip.of(callbackService, streetEgresses, dates, false);
+ return closestFlexTrips
+ .stream()
+ .flatMap(it -> templateFactory.createEgressTemplates(it).stream())
+ .toList();
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java
index 27e9cd2f009..2ee5d4382ae 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexEgressTemplate.java
@@ -1,36 +1,43 @@
package org.opentripplanner.ext.flex.template;
import com.google.common.collect.Lists;
+import java.time.Duration;
import java.util.Collection;
import java.util.List;
import org.opentripplanner.ext.flex.FlexPathDurations;
-import org.opentripplanner.ext.flex.FlexServiceDate;
import org.opentripplanner.ext.flex.edgetype.FlexTripEdge;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.model.PathTransfer;
import org.opentripplanner.routing.graphfinder.NearbyStop;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
-import org.opentripplanner.transit.service.TransitService;
-public class FlexEgressTemplate extends FlexAccessEgressTemplate {
+class FlexEgressTemplate extends AbstractFlexTemplate {
- public FlexEgressTemplate(
- NearbyStop accessEgress,
- FlexTrip trip,
- int fromStopIndex,
- int toStopIndex,
- StopLocation transferStop,
+ FlexEgressTemplate(
+ FlexTrip, ?> trip,
+ StopLocation boardStop,
+ int boardStopPosition,
+ NearbyStop alightStop,
+ int alightStopPosition,
FlexServiceDate date,
FlexPathCalculator calculator,
- FlexConfig config
+ Duration maxTransferDuration
) {
- super(accessEgress, trip, fromStopIndex, toStopIndex, transferStop, date, calculator, config);
+ super(
+ trip,
+ alightStop,
+ boardStop,
+ boardStopPosition,
+ alightStopPosition,
+ date,
+ calculator,
+ maxTransferDuration
+ );
}
protected List getTransferEdges(PathTransfer transfer) {
@@ -41,8 +48,10 @@ protected RegularStop getFinalStop(PathTransfer transfer) {
return transfer.from instanceof RegularStop regularStop ? regularStop : null;
}
- protected Collection getTransfersFromTransferStop(TransitService transitService) {
- return transitService.getFlexIndex().getTransfersToStop(transferStop);
+ protected Collection getTransfersFromTransferStop(
+ FlexAccessEgressCallbackAdapter callback
+ ) {
+ return callback.getTransfersToStop(transferStop);
}
protected Vertex getFlexVertex(Edge edge) {
@@ -65,36 +74,24 @@ protected FlexTripEdge getFlexEdge(Vertex flexFromVertex, StopLocation transferS
var flexPath = calculator.calculateFlexPath(
flexFromVertex,
accessEgress.state.getVertex(),
- fromStopIndex,
- toStopIndex
+ boardStopPosition,
+ alightStopPosition
);
if (flexPath == null) {
return null;
}
- return FlexTripEdge.createFlexTripEdge(
+ return new FlexTripEdge(
flexFromVertex,
accessEgress.state.getVertex(),
transferStop,
accessEgress.stop,
trip,
- this,
+ boardStopPosition,
+ alightStopPosition,
+ serviceDate,
flexPath
);
}
-
- protected boolean isRouteable(Vertex flexVertex) {
- if (accessEgress.state.getVertex() == flexVertex) {
- return false;
- } else return (
- calculator.calculateFlexPath(
- flexVertex,
- accessEgress.state.getVertex(),
- fromStopIndex,
- toStopIndex
- ) !=
- null
- );
- }
}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexServiceDate.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexServiceDate.java
new file mode 100644
index 00000000000..ee85cf77b03
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexServiceDate.java
@@ -0,0 +1,56 @@
+package org.opentripplanner.ext.flex.template;
+
+import gnu.trove.set.TIntSet;
+import java.time.LocalDate;
+
+/**
+ * This class contains information used in a flex router, and depends on the date the search was
+ * made on.
+ */
+public class FlexServiceDate {
+
+ /** The local date */
+ private final LocalDate serviceDate;
+
+ /**
+ * How many seconds does this date's "midnight" (12 hours before noon) differ from the "midnight"
+ * of the date for the search.
+ */
+ private final int secondsFromStartOfTime;
+
+ /** Which services are running on the date. */
+ private final TIntSet servicesRunning;
+
+ private final int requestedBookingTime;
+
+ public FlexServiceDate(
+ LocalDate serviceDate,
+ int secondsFromStartOfTime,
+ int requestedBookingTime,
+ TIntSet servicesRunning
+ ) {
+ this.serviceDate = serviceDate;
+ this.secondsFromStartOfTime = secondsFromStartOfTime;
+ this.requestedBookingTime = requestedBookingTime;
+ this.servicesRunning = servicesRunning;
+ }
+
+ LocalDate serviceDate() {
+ return serviceDate;
+ }
+
+ int secondsFromStartOfTime() {
+ return secondsFromStartOfTime;
+ }
+
+ int requestedBookingTime() {
+ return requestedBookingTime;
+ }
+
+ /**
+ * Return true if the given {@code serviceCode} is active and running.
+ */
+ public boolean isTripServiceRunning(int serviceCode) {
+ return servicesRunning != null && servicesRunning.contains(serviceCode);
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/template/FlexTemplateFactory.java b/src/ext/java/org/opentripplanner/ext/flex/template/FlexTemplateFactory.java
new file mode 100644
index 00000000000..b7a9408af4a
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/flex/template/FlexTemplateFactory.java
@@ -0,0 +1,160 @@
+package org.opentripplanner.ext.flex.template;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
+import org.opentripplanner.ext.flex.trip.FlexTrip;
+import org.opentripplanner.routing.graphfinder.NearbyStop;
+import org.opentripplanner.transit.model.site.GroupStop;
+import org.opentripplanner.transit.model.site.StopLocation;
+import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;
+
+/**
+ * The factory is used to create flex trip templates.
+ */
+class FlexTemplateFactory {
+
+ private final FlexPathCalculator calculator;
+ private final Duration maxTransferDuration;
+ private NearbyStop nearbyStop;
+ private int stopPos;
+ private FlexTrip, ?> trip;
+ private FlexServiceDate date;
+
+ private FlexTemplateFactory(FlexPathCalculator calculator, Duration maxTransferDuration) {
+ this.calculator = Objects.requireNonNull(calculator);
+ this.maxTransferDuration = Objects.requireNonNull(maxTransferDuration);
+ }
+
+ static FlexTemplateFactory of(FlexPathCalculator calculator, Duration maxTransferDuration) {
+ return new FlexTemplateFactory(calculator, maxTransferDuration);
+ }
+
+ List createAccessTemplates(ClosestTrip closestTrip) {
+ return with(closestTrip).createAccessTemplates();
+ }
+
+ List createEgressTemplates(ClosestTrip closestTrip) {
+ return with(closestTrip).createEgressTemplates();
+ }
+
+ /**
+ * Add required parameters to the factory before calling the create methods.
+ */
+ private FlexTemplateFactory with(ClosestTrip closestTrip) {
+ this.nearbyStop = closestTrip.nearbyStop();
+ this.stopPos = closestTrip.stopPos();
+ this.trip = closestTrip.flexTrip();
+ this.date = closestTrip.activeDate();
+ return this;
+ }
+
+ private List createAccessTemplates() {
+ int boardStopPos = stopPos;
+
+ var result = new ArrayList();
+ int alightStopPos = isBoardingAndAlightingAtSameStopPositionAllowed()
+ ? boardStopPos
+ : boardStopPos + 1;
+
+ for (; alightStopPos < trip.numberOfStops(); alightStopPos++) {
+ if (trip.getAlightRule(alightStopPos).isRoutable()) {
+ for (var stop : expandStopsAt(trip, alightStopPos)) {
+ result.add(createAccessTemplate(trip, boardStopPos, stop, alightStopPos));
+ }
+ }
+ }
+ return result;
+ }
+
+ private List createEgressTemplates() {
+ var alightStopPos = stopPos;
+
+ var result = new ArrayList();
+ int end = isBoardingAndAlightingAtSameStopPositionAllowed() ? alightStopPos : alightStopPos - 1;
+
+ for (int boardStopPos = 0; boardStopPos <= end; boardStopPos++) {
+ if (isAllowedToBoardAt(boardStopPos)) {
+ for (var stop : expandStopsAt(trip, boardStopPos)) {
+ result.add(createEgressTemplate(trip, stop, boardStopPos, alightStopPos));
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Check if stop position is routable and that the latest-booking time criteria is met.
+ */
+ private boolean isAllowedToBoardAt(int boardStopPosition) {
+ return (
+ trip.getBoardRule(boardStopPosition).isRoutable() &&
+ !RoutingBookingInfo
+ .of(date.requestedBookingTime(), trip.getPickupBookingInfo(boardStopPosition))
+ .exceedsLatestBookingTime()
+ );
+ }
+
+ /**
+ * With respect to one journey/itinerary this method retuns {@code true} if a passenger can
+ * board and alight at the same stop in the journey pattern. This is not allowed for regular
+ * stops, but it would make sense to allow it for area stops or group stops.
+ *
+ * In NeTEx this is not allowed.
+ *
+ * In GTFS this is no longer allowed according to specification. But it was allowed earlier.
+ *
+ * This method simply returns {@code false}, but we keep it here for documentation. If requested,
+ * we can add code to be backward compatible with the old GTFS version here.
+ */
+ private boolean isBoardingAndAlightingAtSameStopPositionAllowed() {
+ return false;
+ }
+
+ private static List expandStopsAt(FlexTrip, ?> flexTrip, int index) {
+ var stop = flexTrip.getStop(index);
+ return stop instanceof GroupStop groupStop ? groupStop.getChildLocations() : List.of(stop);
+ }
+
+ private FlexAccessTemplate createAccessTemplate(
+ FlexTrip, ?> flexTrip,
+ int boardStopPosition,
+ StopLocation alightStop,
+ int alightStopPosition
+ ) {
+ return new FlexAccessTemplate(
+ flexTrip,
+ nearbyStop,
+ boardStopPosition,
+ alightStop,
+ alightStopPosition,
+ date,
+ setupCalculator(flexTrip),
+ maxTransferDuration
+ );
+ }
+
+ private FlexEgressTemplate createEgressTemplate(
+ FlexTrip, ?> flexTrip,
+ StopLocation boardStop,
+ int boardStopPosition,
+ int alightStopPosition
+ ) {
+ return new FlexEgressTemplate(
+ flexTrip,
+ boardStop,
+ boardStopPosition,
+ nearbyStop,
+ alightStopPosition,
+ date,
+ setupCalculator(flexTrip),
+ maxTransferDuration
+ );
+ }
+
+ private FlexPathCalculator setupCalculator(FlexTrip, ?> flexTrip) {
+ return flexTrip.decorateFlexPathCalculator(calculator);
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java b/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java
index f12ce78d06d..8fdb1b8fd5c 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/trip/FlexTrip.java
@@ -3,22 +3,16 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.stream.Stream;
import javax.annotation.Nonnull;
-import org.opentripplanner.ext.flex.FlexServiceDate;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
-import org.opentripplanner.ext.flex.template.FlexAccessTemplate;
-import org.opentripplanner.ext.flex.template.FlexEgressTemplate;
-import org.opentripplanner.model.BookingInfo;
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.framework.AbstractTransitEntity;
import org.opentripplanner.transit.model.site.AreaStop;
import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* This class represents the different variations of what is considered flexible transit, and its
@@ -28,6 +22,8 @@
public abstract class FlexTrip, B extends FlexTripBuilder>
extends AbstractTransitEntity {
+ public static int STOP_INDEX_NOT_FOUND = -1;
+
private final Trip trip;
FlexTrip(FlexTripBuilder builder) {
@@ -43,60 +39,51 @@ public static boolean isFlexStop(StopLocation stop) {
return stop instanceof GroupStop || stop instanceof AreaStop;
}
- public abstract Stream getFlexAccessTemplates(
- NearbyStop access,
- FlexServiceDate date,
- FlexPathCalculator calculator,
- FlexConfig config
- );
-
- public abstract Stream getFlexEgressTemplates(
- NearbyStop egress,
- FlexServiceDate date,
- FlexPathCalculator calculator,
- FlexConfig config
- );
-
/**
- * Earliest departure time from fromStopIndex to toStopIndex, which departs after departureTime,
+ * Earliest departure time from boardStopPosition to alightStopPosition, which departs after departureTime,
* and for which the flex trip has a duration of flexTime seconds.
*
* @return {@link StopTime#MISSING_VALUE} is returned if a departure does not exist.
*/
public abstract int earliestDepartureTime(
int departureTime,
- int fromStopIndex,
- int toStopIndex,
+ int boardStopPosition,
+ int alightStopPosition,
int flexTripDurationSeconds
);
/**
- * Earliest departure time from fromStopIndex.
+ * Earliest departure time from boardStopPosition.
*
* @return {@link StopTime#MISSING_VALUE} is returned if a departure does not exist.
*/
public abstract int earliestDepartureTime(int stopIndex);
/**
- * Latest arrival time to toStopIndex from fromStopIndex, which arrives before arrivalTime,
+ * Latest arrival time to alightStopPosition from boardStopPosition, which arrives before arrivalTime,
* and for which the flex trip has a duration of flexTime seconds.
*
* @return {@link StopTime#MISSING_VALUE} is returned if a departure does not exist.
*/
public abstract int latestArrivalTime(
int arrivalTime,
- int fromStopIndex,
- int toStopIndex,
+ int boardStopPosition,
+ int alightStopPosition,
int tripDurationSeconds
);
/**
- * Latest arrival time to toStopIndex.
+ * Latest arrival time to alightStopPosition.
*
* @return {@link StopTime#MISSING_VALUE} is returned if a departure does not exist.
*/
public abstract int latestArrivalTime(int stopIndex);
+ /**
+ * Return number-of-stops this trip visit.
+ */
+ public abstract int numberOfStops();
+
/**
* Returns all the stops that are in this trip.
*
@@ -107,6 +94,12 @@ public abstract int latestArrivalTime(
*/
public abstract Set getStops();
+ /**
+ * Return a stop at given stop-index. Note! The visited order may not be the same as the
+ * indexing order.
+ */
+ public abstract StopLocation getStop(int stopIndex);
+
public Trip getTrip() {
return trip;
}
@@ -119,9 +112,32 @@ public Trip getTrip() {
public abstract PickDrop getAlightRule(int i);
- public abstract boolean isBoardingPossible(NearbyStop stop);
+ public abstract boolean isBoardingPossible(StopLocation stop);
+
+ public abstract boolean isAlightingPossible(StopLocation stop);
- public abstract boolean isAlightingPossible(NearbyStop stop);
+ /**
+ * Find the first stop-position matching the given {@code fromStop} where
+ * boarding is allowed.
+ *
+ * @return stop position in the pattern or {@link #STOP_INDEX_NOT_FOUND} if not found.
+ */
+ public abstract int findBoardIndex(StopLocation fromStop);
+
+ /**
+ * Find the first stop-position matching the given {@code toStop} where
+ * alighting is allowed.
+ *
+ * @return the stop position in the pattern or {@link #STOP_INDEX_NOT_FOUND} if not found.
+ */
+ public abstract int findAlightIndex(StopLocation toStop);
+
+ /**
+ * Allow each FlexTrip type to decorate or replace the router defaultCalculator.
+ */
+ public abstract FlexPathCalculator decorateFlexPathCalculator(
+ FlexPathCalculator defaultCalculator
+ );
@Override
public boolean sameAs(@Nonnull T other) {
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 e16e1e5e1f7..5ea0cb9fb91 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java
@@ -4,31 +4,22 @@
import static org.opentripplanner.model.StopTime.MISSING_VALUE;
import java.io.Serializable;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
import javax.annotation.Nonnull;
-import org.opentripplanner.ext.flex.FlexServiceDate;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
import org.opentripplanner.ext.flex.flexpathcalculator.ScheduledFlexPathCalculator;
-import org.opentripplanner.ext.flex.template.FlexAccessTemplate;
-import org.opentripplanner.ext.flex.template.FlexEgressTemplate;
-import org.opentripplanner.model.BookingInfo;
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.framework.FeedScopedId;
import org.opentripplanner.transit.model.framework.TransitBuilder;
import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* A scheduled deviated trip is similar to a regular scheduled trip, except that it contains stop
@@ -74,95 +65,15 @@ public static boolean isScheduledFlexTrip(List stopTimes) {
);
}
- @Override
- public Stream getFlexAccessTemplates(
- NearbyStop access,
- FlexServiceDate date,
- FlexPathCalculator calculator,
- FlexConfig config
- ) {
- FlexPathCalculator scheduledCalculator = new ScheduledFlexPathCalculator(calculator, this);
-
- int fromIndex = getFromIndex(access);
-
- if (fromIndex == -1) {
- return Stream.empty();
- }
-
- ArrayList res = new ArrayList<>();
-
- for (int toIndex = fromIndex; toIndex < stopTimes.length; toIndex++) {
- if (getAlightRule(toIndex).isNotRoutable()) {
- continue;
- }
- for (StopLocation stop : expandStops(stopTimes[toIndex].stop)) {
- res.add(
- new FlexAccessTemplate(
- access,
- this,
- fromIndex,
- toIndex,
- stop,
- date,
- scheduledCalculator,
- config
- )
- );
- }
- }
-
- return res.stream();
- }
-
- @Override
- public Stream getFlexEgressTemplates(
- NearbyStop egress,
- FlexServiceDate date,
- FlexPathCalculator calculator,
- FlexConfig config
- ) {
- FlexPathCalculator scheduledCalculator = new ScheduledFlexPathCalculator(calculator, this);
-
- int toIndex = getToIndex(egress);
-
- if (toIndex == -1) {
- return Stream.empty();
- }
-
- ArrayList res = new ArrayList<>();
-
- for (int fromIndex = toIndex; fromIndex >= 0; fromIndex--) {
- if (getBoardRule(fromIndex).isNotRoutable()) {
- continue;
- }
- for (StopLocation stop : expandStops(stopTimes[fromIndex].stop)) {
- res.add(
- new FlexEgressTemplate(
- egress,
- this,
- fromIndex,
- toIndex,
- stop,
- date,
- scheduledCalculator,
- config
- )
- );
- }
- }
-
- return res.stream();
- }
-
@Override
public int earliestDepartureTime(
int departureTime,
- int fromStopIndex,
- int toStopIndex,
+ int boardStopPosition,
+ int alightStopPosition,
int flexTripDurationSeconds
) {
int stopTime = MISSING_VALUE;
- for (int i = fromStopIndex; stopTime == MISSING_VALUE && i >= 0; i--) {
+ for (int i = boardStopPosition; stopTime == MISSING_VALUE && i >= 0; i--) {
stopTime = stopTimes[i].departureTime;
}
return stopTime >= departureTime ? stopTime : MISSING_VALUE;
@@ -176,12 +87,12 @@ public int earliestDepartureTime(int stopIndex) {
@Override
public int latestArrivalTime(
int arrivalTime,
- int fromStopIndex,
- int toStopIndex,
+ int boardStopPosition,
+ int alightStopPosition,
int flexTripDurationSeconds
) {
int stopTime = MISSING_VALUE;
- for (int i = toStopIndex; stopTime == MISSING_VALUE && i < stopTimes.length; i++) {
+ for (int i = alightStopPosition; stopTime == MISSING_VALUE && i < stopTimes.length; i++) {
stopTime = stopTimes[i].arrivalTime;
}
return stopTime <= arrivalTime ? stopTime : MISSING_VALUE;
@@ -192,6 +103,11 @@ public int latestArrivalTime(int stopIndex) {
return stopTimes[stopIndex].arrivalTime;
}
+ @Override
+ public int numberOfStops() {
+ return stopTimes.length;
+ }
+
@Override
public Set getStops() {
return Arrays
@@ -200,6 +116,11 @@ public Set getStops() {
.collect(Collectors.toSet());
}
+ @Override
+ public StopLocation getStop(int stopIndex) {
+ return stopTimes[stopIndex].stop;
+ }
+
@Override
public BookingInfo getDropOffBookingInfo(int i) {
return dropOffBookingInfos[i];
@@ -221,13 +142,13 @@ public PickDrop getAlightRule(int i) {
}
@Override
- public boolean isBoardingPossible(NearbyStop stop) {
- return getFromIndex(stop) != -1;
+ public boolean isBoardingPossible(StopLocation fromStop) {
+ return findBoardIndex(fromStop) != STOP_INDEX_NOT_FOUND;
}
@Override
- public boolean isAlightingPossible(NearbyStop stop) {
- return getToIndex(stop) != -1;
+ public boolean isAlightingPossible(StopLocation toStop) {
+ return findAlightIndex(toStop) != STOP_INDEX_NOT_FOUND;
}
@Override
@@ -246,48 +167,49 @@ public TransitBuilder copy(
return new ScheduledDeviatedTripBuilder(this);
}
- private Collection expandStops(StopLocation stop) {
- return stop instanceof GroupStop groupStop
- ? groupStop.getChildLocations()
- : Collections.singleton(stop);
- }
-
- private int getFromIndex(NearbyStop accessEgress) {
+ @Override
+ public int findBoardIndex(StopLocation fromStop) {
for (int i = 0; i < stopTimes.length; i++) {
if (getBoardRule(i).isNotRoutable()) {
continue;
}
StopLocation stop = stopTimes[i].stop;
if (stop instanceof GroupStop groupStop) {
- if (groupStop.getChildLocations().contains(accessEgress.stop)) {
+ if (groupStop.getChildLocations().contains(fromStop)) {
return i;
}
} else {
- if (stop.equals(accessEgress.stop)) {
+ if (stop.equals(fromStop)) {
return i;
}
}
}
- return -1;
+ return STOP_INDEX_NOT_FOUND;
}
- private int getToIndex(NearbyStop accessEgress) {
+ @Override
+ public int findAlightIndex(StopLocation toStop) {
for (int i = stopTimes.length - 1; i >= 0; i--) {
if (getAlightRule(i).isNotRoutable()) {
continue;
}
StopLocation stop = stopTimes[i].stop;
if (stop instanceof GroupStop groupStop) {
- if (groupStop.getChildLocations().contains(accessEgress.stop)) {
+ if (groupStop.getChildLocations().contains(toStop)) {
return i;
}
} else {
- if (stop.equals(accessEgress.stop)) {
+ if (stop.equals(toStop)) {
return i;
}
}
}
- return -1;
+ return STOP_INDEX_NOT_FOUND;
+ }
+
+ @Override
+ public FlexPathCalculator decorateFlexPathCalculator(FlexPathCalculator defaultCalculator) {
+ return new ScheduledFlexPathCalculator(defaultCalculator, this);
}
private static class ScheduledDeviatedStopTime implements Serializable {
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 a4c8d9568ca..20a77bb94ed 100644
--- a/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java
+++ b/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java
@@ -8,30 +8,22 @@
import java.util.Objects;
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;
-import java.util.stream.Stream;
import javax.annotation.Nonnull;
-import org.opentripplanner.ext.flex.FlexServiceDate;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
import org.opentripplanner.ext.flex.flexpathcalculator.TimePenaltyCalculator;
-import org.opentripplanner.ext.flex.template.FlexAccessTemplate;
-import org.opentripplanner.ext.flex.template.FlexEgressTemplate;
import org.opentripplanner.framework.lang.DoubleUtils;
import org.opentripplanner.framework.lang.IntRange;
import org.opentripplanner.framework.time.DurationUtils;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.routing.api.request.framework.TimePenalty;
-import org.opentripplanner.routing.graphfinder.NearbyStop;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.framework.TransitBuilder;
import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.StopLocation;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* This type of FlexTrip is used when a taxi-type service is modeled, which operates in any number
@@ -48,7 +40,6 @@
public class UnscheduledTrip extends FlexTrip {
private static final Set N_STOPS = Set.of(1, 2);
- private static final int INDEX_NOT_FOUND = -1;
private final StopTimeWindow[] stopTimes;
@@ -104,125 +95,16 @@ public static boolean isUnscheduledTrip(List stopTimes) {
}
}
- @Override
- public Stream getFlexAccessTemplates(
- NearbyStop access,
- FlexServiceDate date,
- FlexPathCalculator calculator,
- FlexConfig config
- ) {
- // Find boarding index, also check if it's boardable
- final int fromIndex = getFromIndex(access);
-
- // templates will be generated from the boardingIndex to the end of the trip
- final int lastIndexInTrip = stopTimes.length - 1;
-
- // Check if trip is possible
- if (fromIndex == INDEX_NOT_FOUND || fromIndex > lastIndexInTrip) {
- return Stream.empty();
- }
-
- IntStream indices;
- if (stopTimes.length == 1) {
- indices = IntStream.of(fromIndex);
- } else {
- indices = IntStream.range(fromIndex + 1, lastIndexInTrip + 1);
- }
-
- final var updatedCalculator = flexPathCalculator(calculator);
-
- // check for every stop after fromIndex if you can alight, if so return a template
- return indices
- // 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(Function.identity())
- // create template
- .map(alightStop ->
- new FlexAccessTemplate(
- access,
- this,
- fromIndex,
- alightStop.index,
- alightStop.stop,
- date,
- updatedCalculator,
- config
- )
- );
- }
-
- /**
- * Get the correct {@link FlexPathCalculator} depending on the {@code timePenalty}.
- * If the penalty would not change the result, we return the regular calculator.
- */
- protected FlexPathCalculator flexPathCalculator(FlexPathCalculator calculator) {
- if (timePenalty.modifies()) {
- return new TimePenaltyCalculator(calculator, timePenalty);
- } else {
- return calculator;
- }
- }
-
- @Override
- public Stream getFlexEgressTemplates(
- NearbyStop egress,
- FlexServiceDate date,
- FlexPathCalculator calculator,
- FlexConfig config
- ) {
- // templates will be generated from the first index to the toIndex
- 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 || firstIndexInTrip > toIndex) {
- return Stream.empty();
- }
-
- IntStream indices;
- if (stopTimes.length == 1) {
- indices = IntStream.of(toIndex);
- } else {
- indices = IntStream.range(firstIndexInTrip, 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,
- boardStop.index,
- toIndex,
- boardStop.stop,
- date,
- calculator,
- config
- )
- );
- }
-
@Override
public int earliestDepartureTime(
int requestedDepartureTime,
- int fromStopIndex,
- int toStopIndex,
+ int boardStopPosition,
+ int alightStopPosition,
int tripDurationSeconds
) {
var optionalDepartureTimeWindow = departureTimeWindow(
- fromStopIndex,
- toStopIndex,
+ boardStopPosition,
+ alightStopPosition,
tripDurationSeconds
);
@@ -244,13 +126,13 @@ public int earliestDepartureTime(int stopIndex) {
@Override
public int latestArrivalTime(
int requestedArrivalTime,
- int fromStopIndex,
- int toStopIndex,
+ int boardStopPosition,
+ int alightStopPosition,
int tripDurationSeconds
) {
var optionalArrivalTimeWindow = arrivalTimeWindow(
- fromStopIndex,
- toStopIndex,
+ boardStopPosition,
+ alightStopPosition,
tripDurationSeconds
);
@@ -269,39 +151,49 @@ public int latestArrivalTime(int stopIndex) {
return stopTimes[stopIndex].end();
}
+ @Override
+ public int numberOfStops() {
+ return stopTimes.length;
+ }
+
@Override
public Set getStops() {
return Arrays.stream(stopTimes).map(StopTimeWindow::stop).collect(Collectors.toSet());
}
@Override
- public BookingInfo getDropOffBookingInfo(int i) {
- return dropOffBookingInfos[i];
+ public StopLocation getStop(int stopIndex) {
+ return stopTimes[stopIndex].stop();
+ }
+
+ @Override
+ public BookingInfo getDropOffBookingInfo(int stopIndex) {
+ return dropOffBookingInfos[stopIndex];
}
@Override
- public BookingInfo getPickupBookingInfo(int i) {
- return pickupBookingInfos[i];
+ public BookingInfo getPickupBookingInfo(int stopIndex) {
+ return pickupBookingInfos[stopIndex];
}
@Override
- public PickDrop getBoardRule(int i) {
- return stopTimes[i].pickupType();
+ public PickDrop getBoardRule(int stopIndex) {
+ return stopTimes[stopIndex].pickupType();
}
@Override
- public PickDrop getAlightRule(int i) {
- return stopTimes[i].dropOffType();
+ public PickDrop getAlightRule(int stopIndex) {
+ return stopTimes[stopIndex].dropOffType();
}
@Override
- public boolean isBoardingPossible(NearbyStop stop) {
- return getFromIndex(stop) != INDEX_NOT_FOUND;
+ public boolean isBoardingPossible(StopLocation stop) {
+ return findBoardIndex(stop) != STOP_INDEX_NOT_FOUND;
}
@Override
- public boolean isAlightingPossible(NearbyStop stop) {
- return getToIndex(stop) != INDEX_NOT_FOUND;
+ public boolean isAlightingPossible(StopLocation stop) {
+ return findAlightIndex(stop) != STOP_INDEX_NOT_FOUND;
}
@Override
@@ -320,59 +212,65 @@ public TransitBuilder copy() {
return new UnscheduledTripBuilder(this);
}
- private Stream expandStops(int index) {
- var stop = stopTimes[index].stop();
- return stop instanceof GroupStop groupStop
- ? groupStop.getChildLocations().stream().map(s -> new IndexedStopLocation(index, s))
- : Stream.of(new IndexedStopLocation(index, stop));
- }
-
- private int getFromIndex(NearbyStop accessEgress) {
+ @Override
+ public int findBoardIndex(StopLocation fromStop) {
for (int i = 0; i < stopTimes.length; i++) {
if (getBoardRule(i).isNotRoutable()) {
continue;
}
StopLocation stop = stopTimes[i].stop();
if (stop instanceof GroupStop groupStop) {
- if (groupStop.getChildLocations().contains(accessEgress.stop)) {
+ if (groupStop.getChildLocations().contains(fromStop)) {
return i;
}
} else {
- if (stop.equals(accessEgress.stop)) {
+ if (stop.equals(fromStop)) {
return i;
}
}
}
- return INDEX_NOT_FOUND;
+ return FlexTrip.STOP_INDEX_NOT_FOUND;
}
- private int getToIndex(NearbyStop accessEgress) {
+ @Override
+ public int findAlightIndex(StopLocation toStop) {
for (int i = stopTimes.length - 1; i >= 0; i--) {
if (getAlightRule(i).isNotRoutable()) {
continue;
}
StopLocation stop = stopTimes[i].stop();
if (stop instanceof GroupStop groupStop) {
- if (groupStop.getChildLocations().contains(accessEgress.stop)) {
+ if (groupStop.getChildLocations().contains(toStop)) {
return i;
}
} else {
- if (stop.equals(accessEgress.stop)) {
+ if (stop.equals(toStop)) {
return i;
}
}
}
- return INDEX_NOT_FOUND;
+ return FlexTrip.STOP_INDEX_NOT_FOUND;
+ }
+
+ @Override
+ public FlexPathCalculator decorateFlexPathCalculator(FlexPathCalculator defaultCalculator) {
+ // Get the correct {@link FlexPathCalculator} depending on the {@code timePenalty}.
+ // If the modifier does not change the result, we return the regular calculator.
+ if (timePenalty.modifies()) {
+ return new TimePenaltyCalculator(defaultCalculator, timePenalty);
+ } else {
+ return defaultCalculator;
+ }
}
private Optional departureTimeWindow(
- int fromStopIndex,
- int toStopIndex,
+ int boardStopPosition,
+ int alightStopPosition,
int tripDurationSeconds
) {
// Align the from and to time-windows by subtracting the trip-duration from the to-time-window.
- var fromTime = stopTimes[fromStopIndex].timeWindow();
- var toTimeShifted = stopTimes[toStopIndex].timeWindow().minus(tripDurationSeconds);
+ var fromTime = stopTimes[boardStopPosition].timeWindow();
+ var toTimeShifted = stopTimes[alightStopPosition].timeWindow().minus(tripDurationSeconds);
// Then take the intersection of the aligned windows to find the window where the
// requested-departure-time must be within
@@ -380,13 +278,13 @@ private Optional departureTimeWindow(
}
private Optional arrivalTimeWindow(
- int fromStopIndex,
- int toStopIndex,
+ int boardStopPosition,
+ int alightStopPosition,
int tripDurationSeconds
) {
// Align the from and to time-windows by adding the trip-duration to the from-time-window.
- var fromTimeShifted = stopTimes[fromStopIndex].timeWindow().plus(tripDurationSeconds);
- var toTime = stopTimes[toStopIndex].timeWindow();
+ var fromTimeShifted = stopTimes[boardStopPosition].timeWindow().plus(tripDurationSeconds);
+ var toTime = stopTimes[alightStopPosition].timeWindow();
// Then take the intersection of the aligned windows to find the window where the
// requested-arrival-time must be within
diff --git a/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingInfoMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingInfoMapper.java
index 4aa8b4a46b1..b4e3292b435 100644
--- a/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingInfoMapper.java
+++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingInfoMapper.java
@@ -1,11 +1,23 @@
package org.opentripplanner.ext.restapi.mapping;
import org.opentripplanner.ext.restapi.model.ApiBookingInfo;
-import org.opentripplanner.model.BookingInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
public class BookingInfoMapper {
- static ApiBookingInfo mapBookingInfo(BookingInfo info, boolean isPickup) {
+ static ApiBookingInfo mapBookingInfoForPickup(BookingInfo info) {
+ return mapBookingInfo(info, true);
+ }
+
+ static ApiBookingInfo mapBookingInfoForDropOff(BookingInfo info) {
+ return mapBookingInfo(info, false);
+ }
+
+ /**
+ * @param isPickup either pickup or dropOff message must be set, not both. We only want to show
+ * the pick-up message for pickups, and the drop-off message for drop-offs.
+ */
+ private static ApiBookingInfo mapBookingInfo(BookingInfo info, boolean isPickup) {
if (info == null) {
return null;
}
@@ -18,9 +30,7 @@ static ApiBookingInfo mapBookingInfo(BookingInfo info, boolean isPickup) {
info.getMinimumBookingNotice(),
info.getMaximumBookingNotice(),
info.getMessage(),
- // we only want to show the pick up message for pickups
isPickup ? info.getPickupMessage() : null,
- // and only the drop off message for drop offs
!isPickup ? info.getDropOffMessage() : null
);
}
diff --git a/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingMethodMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingMethodMapper.java
index 9b61ff39543..0ab743e1cb2 100644
--- a/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingMethodMapper.java
+++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingMethodMapper.java
@@ -3,7 +3,7 @@
import java.util.EnumSet;
import java.util.Set;
import java.util.stream.Collectors;
-import org.opentripplanner.model.BookingMethod;
+import org.opentripplanner.transit.model.timetable.booking.BookingMethod;
public class BookingMethodMapper {
diff --git a/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingTimeMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingTimeMapper.java
index e40309e9137..8b3034532fc 100644
--- a/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingTimeMapper.java
+++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/BookingTimeMapper.java
@@ -1,7 +1,7 @@
package org.opentripplanner.ext.restapi.mapping;
import org.opentripplanner.ext.restapi.model.ApiBookingTime;
-import org.opentripplanner.model.BookingTime;
+import org.opentripplanner.transit.model.timetable.booking.BookingTime;
public class BookingTimeMapper {
diff --git a/src/ext/java/org/opentripplanner/ext/restapi/mapping/LegMapper.java b/src/ext/java/org/opentripplanner/ext/restapi/mapping/LegMapper.java
index 766262bca89..b642426cd6d 100644
--- a/src/ext/java/org/opentripplanner/ext/restapi/mapping/LegMapper.java
+++ b/src/ext/java/org/opentripplanner/ext/restapi/mapping/LegMapper.java
@@ -142,9 +142,10 @@ public ApiLeg mapLeg(
api.boardRule = getBoardAlightMessage(domain.getBoardRule());
api.alightRule = getBoardAlightMessage(domain.getAlightRule());
- api.pickupBookingInfo = BookingInfoMapper.mapBookingInfo(domain.getPickupBookingInfo(), true);
+ api.pickupBookingInfo =
+ BookingInfoMapper.mapBookingInfoForPickup(domain.getPickupBookingInfo());
api.dropOffBookingInfo =
- BookingInfoMapper.mapBookingInfo(domain.getDropOffBookingInfo(), false);
+ BookingInfoMapper.mapBookingInfoForDropOff(domain.getDropOffBookingInfo());
api.rentedBike = domain.getRentedVehicle();
api.walkingBike = domain.getWalkingBike();
diff --git a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java
index 1d985f0e555..3ae029fdb2d 100644
--- a/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java
+++ b/src/ext/java/org/opentripplanner/ext/restapi/resources/RoutingResource.java
@@ -8,6 +8,7 @@
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MultivaluedMap;
import java.time.Duration;
+import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
@@ -171,6 +172,9 @@ public abstract class RoutingResource {
@QueryParam("wheelchair")
protected Boolean wheelchair;
+ @QueryParam("bookingTime")
+ protected String bookingTime;
+
/**
* The maximum time (in seconds) of pre-transit travel when using drive-to-transit (park and ride
* or kiss and ride). Defaults to unlimited.
@@ -728,6 +732,10 @@ protected RouteRequest buildRequest(MultivaluedMap queryParamete
} else {
request.setDateTime(date, time, tz);
}
+
+ if (bookingTime != null) {
+ request.setBookingTime(LocalDateTime.parse(bookingTime).atZone(tz).toInstant());
+ }
}
final Duration swDuration = DurationUtils.parseSecondsOrDuration(searchWindow).orElse(null);
diff --git a/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java b/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java
index beba8570b8b..257cf0a7ca5 100644
--- a/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java
+++ b/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessAdapter.java
@@ -3,6 +3,7 @@
import java.time.Duration;
import org.opentripplanner.framework.model.TimeAndCost;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
/**
* This class is used to adapt the ride hailing accesses (not egresses) into a time-dependent
@@ -12,7 +13,7 @@ public final class RideHailingAccessAdapter extends DefaultAccessEgress {
private final Duration arrival;
- public RideHailingAccessAdapter(DefaultAccessEgress access, Duration arrival) {
+ public RideHailingAccessAdapter(RoutingAccessEgress access, Duration arrival) {
super(access.stop(), access.getLastState());
this.arrival = arrival;
}
@@ -49,7 +50,7 @@ public String openingHoursToString() {
}
@Override
- public DefaultAccessEgress withPenalty(TimeAndCost penalty) {
+ public RoutingAccessEgress withPenalty(TimeAndCost penalty) {
return new RideHailingAccessAdapter(this, penalty);
}
diff --git a/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java b/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java
index b4a75c26aad..5e4eee09920 100644
--- a/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java
+++ b/src/ext/java/org/opentripplanner/ext/ridehailing/RideHailingAccessShifter.java
@@ -9,7 +9,7 @@
import java.util.stream.Collectors;
import org.opentripplanner.ext.ridehailing.model.ArrivalTime;
import org.opentripplanner.framework.geometry.WgsCoordinate;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.transit.model.framework.Result;
@@ -29,13 +29,13 @@ public class RideHailingAccessShifter {
private static final Duration MAX_DURATION_FROM_NOW = Duration.ofMinutes(30);
/**
- * Given a list of {@link DefaultAccessEgress} shift the access ones which contain driving
+ * Given a list of {@link RoutingAccessEgress}, shift the access ones that contain driving
* so that they only start at the time when the ride hailing vehicle can actually be there
* to pick up passengers.
*/
- public static List shiftAccesses(
+ public static List shiftAccesses(
boolean isAccess,
- List results,
+ List results,
List services,
RouteRequest request,
Instant now
diff --git a/src/ext/java/org/opentripplanner/ext/traveltime/TravelTimeResource.java b/src/ext/java/org/opentripplanner/ext/traveltime/TravelTimeResource.java
index 67d8bb0d6cb..88742aebeb1 100644
--- a/src/ext/java/org/opentripplanner/ext/traveltime/TravelTimeResource.java
+++ b/src/ext/java/org/opentripplanner/ext/traveltime/TravelTimeResource.java
@@ -34,7 +34,7 @@
import org.opentripplanner.raptor.api.response.RaptorResponse;
import org.opentripplanner.raptor.api.response.StopArrivals;
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressRouter;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.AccessEgressMapper;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRoutingRequestTransitData;
@@ -178,7 +178,7 @@ private ZSampleGrid getSampleGrid() {
}
}
- private Collection getAccess(TemporaryVerticesContainer temporaryVertices) {
+ private Collection getAccess(TemporaryVerticesContainer temporaryVertices) {
final Collection accessStops = AccessEgressRouter.streetSearch(
routingRequest,
temporaryVertices,
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/BookingInfoImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/BookingInfoImpl.java
index 9ba2c21f62d..44ee0985542 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/BookingInfoImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/BookingInfoImpl.java
@@ -3,9 +3,9 @@
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers;
-import org.opentripplanner.model.BookingInfo;
-import org.opentripplanner.model.BookingTime;
import org.opentripplanner.transit.model.organization.ContactInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingTime;
public class BookingInfoImpl implements GraphQLDataFetchers.GraphQLBookingInfo {
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/BookingTimeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/BookingTimeImpl.java
index 8c6e581eba4..9d6cd00642d 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/BookingTimeImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/BookingTimeImpl.java
@@ -4,7 +4,7 @@
import graphql.schema.DataFetchingEnvironment;
import java.time.format.DateTimeFormatter;
import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers;
-import org.opentripplanner.model.BookingTime;
+import org.opentripplanner.transit.model.timetable.booking.BookingTime;
public class BookingTimeImpl implements GraphQLDataFetchers.GraphQLBookingTime {
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java
index 975d8d56831..9ec83a4bf67 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java
@@ -14,7 +14,6 @@
import org.opentripplanner.ext.ridehailing.model.RideEstimate;
import org.opentripplanner.ext.ridehailing.model.RideHailingLeg;
import org.opentripplanner.framework.graphql.GraphQLUtils;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.fare.FareProductUse;
import org.opentripplanner.model.plan.Leg;
@@ -30,6 +29,7 @@
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
public class LegImpl implements GraphQLDataFetchers.GraphQLLeg {
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java
index fbfc4965c62..103534689a9 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java
@@ -62,6 +62,8 @@
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingTime;
public class GraphQLDataFetchers {
@@ -211,9 +213,9 @@ public interface GraphQLBookingInfo {
public DataFetcher dropOffMessage();
- public DataFetcher earliestBookingTime();
+ public DataFetcher earliestBookingTime();
- public DataFetcher latestBookingTime();
+ public DataFetcher latestBookingTime();
public DataFetcher maximumBookingNoticeSeconds();
@@ -449,7 +451,7 @@ public interface GraphQLLeg {
public DataFetcher distance();
- public DataFetcher dropOffBookingInfo();
+ public DataFetcher dropOffBookingInfo();
public DataFetcher dropoffType();
@@ -481,7 +483,7 @@ public interface GraphQLLeg {
public DataFetcher> nextLegs();
- public DataFetcher pickupBookingInfo();
+ public DataFetcher pickupBookingInfo();
public DataFetcher pickupType();
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml
index 29c2bff0257..70455ec3dad 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml
+++ b/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml
@@ -45,8 +45,8 @@ config:
RentalVehicle: org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle#VehicleRentalVehicle
VehicleRentalUris: org.opentripplanner.service.vehiclerental.model.VehicleRentalStationUris#VehicleRentalStationUris
BikesAllowed: org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLBikesAllowed#GraphQLBikesAllowed
- BookingInfo: org.opentripplanner.model.BookingInfo
- BookingTime: org.opentripplanner.model.BookingTime
+ BookingInfo: org.opentripplanner.transit.model.timetable.booking.BookingInfo#BookingInfo
+ BookingTime: org.opentripplanner.transit.model.timetable.booking.BookingTime#BookingTime
CarPark: org.opentripplanner.routing.vehicle_parking.VehicleParking#VehicleParking
ContactInfo: org.opentripplanner.transit.model.organization.ContactInfo
Cluster: Object
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java
index 5c579501254..72cd5fc6260 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapper.java
@@ -52,6 +52,12 @@ public static RouteRequest createRequest(DataFetchingEnvironment environment) {
"dateTime",
millisSinceEpoch -> request.setDateTime(Instant.ofEpochMilli((long) millisSinceEpoch))
);
+
+ callWith.argument(
+ "bookingTime",
+ millisSinceEpoch -> request.setBookingTime(Instant.ofEpochMilli((long) millisSinceEpoch))
+ );
+
callWith.argument(
"searchWindow",
(Integer m) -> request.setSearchWindow(Duration.ofMinutes(m))
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java b/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java
index b85d4ce19b9..6c173ba815f 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/model/EnumTypes.java
@@ -6,7 +6,6 @@
import java.util.List;
import java.util.function.Function;
import org.opentripplanner.framework.doc.DocumentedEnum;
-import org.opentripplanner.model.BookingMethod;
import org.opentripplanner.model.plan.AbsoluteDirection;
import org.opentripplanner.model.plan.RelativeDirection;
import org.opentripplanner.model.plan.VertexType;
@@ -27,6 +26,7 @@
import org.opentripplanner.transit.model.timetable.OccupancyStatus;
import org.opentripplanner.transit.model.timetable.RealTimeState;
import org.opentripplanner.transit.model.timetable.TripAlteration;
+import org.opentripplanner.transit.model.timetable.booking.BookingMethod;
public class EnumTypes {
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java
index bf34d9928e4..5ed3264a4fd 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/model/plan/TripQuery.java
@@ -51,9 +51,26 @@ public static GraphQLFieldDefinition create(
.newArgument()
.name("dateTime")
.description(
- "Date and time for the earliest time the user is willing to start the journey " +
- "(if arriveBy=false/not set) or the latest acceptable time of arriving " +
- "(arriveBy=true). Defaults to now"
+ "The date and time for the earliest time the user is willing to start the journey " +
+ "(if `false` or not set) or the latest acceptable time of arriving " +
+ "(`true`). Defaults to now."
+ )
+ .type(gqlUtil.dateTimeScalar)
+ .build()
+ )
+ .argument(
+ GraphQLArgument
+ .newArgument()
+ .name("bookingTime")
+ .description(
+ """
+ The date and time for the latest time the user is expected to book the journey.
+ Normally this is when the search is performed (now), plus a small grace period to
+ complete the booking. Services which must be booked before this time is excluded. The
+ `latestBookingTime` and `minimumBookingPeriod` in `BookingArrangement` (flexible
+ services only) is used to enforce this. If this parameter is _not set_, no booking-time
+ restrictions are applied - all journeys are listed.
+ """
)
.type(gqlUtil.dateTimeScalar)
.build()
diff --git a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java
index c2a11441695..de0f86b1166 100644
--- a/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java
+++ b/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/BookingArrangementType.java
@@ -8,9 +8,9 @@
import graphql.schema.GraphQLOutputType;
import org.opentripplanner.apis.transmodel.model.EnumTypes;
import org.opentripplanner.apis.transmodel.support.GqlUtil;
-import org.opentripplanner.model.BookingInfo;
-import org.opentripplanner.model.BookingTime;
import org.opentripplanner.transit.model.organization.ContactInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingTime;
public class BookingArrangementType {
@@ -127,7 +127,8 @@ public static GraphQLObjectType create(GqlUtil gqlUtil) {
return "other";
}
} else if (
- earliestBookingTime.getDaysPrior() == 0 && latestBookingTime.getDaysPrior() == 0
+ earliestBookingTime.getDaysPrior() == 0 &&
+ (latestBookingTime == null || latestBookingTime.getDaysPrior() == 0)
) {
return "dayOfTravelOnly";
} else {
diff --git a/src/main/java/org/opentripplanner/framework/io/OtpHttpClientException.java b/src/main/java/org/opentripplanner/framework/io/OtpHttpClientException.java
index a56fdcaa7f0..ff300cc7e3c 100644
--- a/src/main/java/org/opentripplanner/framework/io/OtpHttpClientException.java
+++ b/src/main/java/org/opentripplanner/framework/io/OtpHttpClientException.java
@@ -3,7 +3,7 @@
public class OtpHttpClientException extends RuntimeException {
public OtpHttpClientException(Throwable cause) {
- super(cause);
+ super(cause.getMessage(), cause);
}
public OtpHttpClientException(String message) {
diff --git a/src/main/java/org/opentripplanner/framework/time/TimeUtils.java b/src/main/java/org/opentripplanner/framework/time/TimeUtils.java
index 61549eeced3..9cc5594cb76 100644
--- a/src/main/java/org/opentripplanner/framework/time/TimeUtils.java
+++ b/src/main/java/org/opentripplanner/framework/time/TimeUtils.java
@@ -2,10 +2,12 @@
import java.security.SecureRandom;
import java.time.Duration;
+import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Locale;
import java.util.Random;
@@ -246,4 +248,14 @@ public static long busyWait(int waitMs) {
}
return value;
}
+
+ /**
+ * Calculate the relative time in seconds with the given {@code transitSearchTimeZero} as the
+ * base. There is no restriction on the returned time, it can be in the past(negative) and
+ * many days ahead of the base. This method can be used to translate an API instance of time
+ * into the OTP internal transit model time, when the search zero-point-in-time is known.
+ */
+ public static int toTransitTimeSeconds(ZonedDateTime transitSearchTimeZero, Instant time) {
+ return (int) ChronoUnit.SECONDS.between(transitSearchTimeZero.toInstant(), time);
+ }
}
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/AddTransitModelEntitiesToGraph.java b/src/main/java/org/opentripplanner/graph_builder/module/AddTransitModelEntitiesToGraph.java
index 8b5653a4f55..a53730b104c 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/AddTransitModelEntitiesToGraph.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/AddTransitModelEntitiesToGraph.java
@@ -27,7 +27,6 @@
import org.opentripplanner.street.model.vertex.TransitEntranceVertex;
import org.opentripplanner.street.model.vertex.TransitPathwayNodeVertex;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
import org.opentripplanner.street.model.vertex.VertexFactory;
import org.opentripplanner.transit.model.basic.Accessibility;
import org.opentripplanner.transit.model.basic.TransitMode;
@@ -124,7 +123,7 @@ private void addStopsToGraphAndGenerateStopVertexes(TransitModel transitModel) {
for (RegularStop stop : otpTransitService.stopModel().listRegularStops()) {
Set modes = stopModeMap.get(stop);
TransitStopVertex stopVertex = vertexFactory.transitStop(
- new TransitStopVertexBuilder().withStop(stop).withModes(modes)
+ TransitStopVertex.of().withStop(stop).withModes(modes)
);
if (modes != null && modes.contains(TransitMode.SUBWAY)) {
diff --git a/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java b/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java
index 7386b60b452..aca6e8a94cf 100644
--- a/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java
+++ b/src/main/java/org/opentripplanner/graph_builder/module/NearbyStopFinder.java
@@ -133,8 +133,8 @@ public Set findNearbyStopsConsideringPatterns(
for (FlexTrip, ?> trip : transitService.getFlexIndex().getFlexTripsByStop(ts1)) {
if (
reverseDirection
- ? trip.isAlightingPossible(nearbyStop)
- : trip.isBoardingPossible(nearbyStop)
+ ? trip.isAlightingPossible(nearbyStop.stop)
+ : trip.isBoardingPossible(nearbyStop.stop)
) {
closestStopForFlexTrip.putMin(trip, nearbyStop);
}
diff --git a/src/main/java/org/opentripplanner/gtfs/mapping/BookingRuleMapper.java b/src/main/java/org/opentripplanner/gtfs/mapping/BookingRuleMapper.java
index 54203bb91c1..74efdd52202 100644
--- a/src/main/java/org/opentripplanner/gtfs/mapping/BookingRuleMapper.java
+++ b/src/main/java/org/opentripplanner/gtfs/mapping/BookingRuleMapper.java
@@ -7,10 +7,10 @@
import java.util.Map;
import org.onebusaway.gtfs.model.AgencyAndId;
import org.onebusaway.gtfs.model.BookingRule;
-import org.opentripplanner.model.BookingInfo;
-import org.opentripplanner.model.BookingMethod;
-import org.opentripplanner.model.BookingTime;
import org.opentripplanner.transit.model.organization.ContactInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingMethod;
+import org.opentripplanner.transit.model.timetable.booking.BookingTime;
/** Responsible for mapping GTFS BookingRule into the OTP model. */
class BookingRuleMapper {
@@ -26,17 +26,18 @@ BookingInfo map(BookingRule rule) {
return cachedBookingInfos.computeIfAbsent(
rule.getId(),
k ->
- new BookingInfo(
- contactInfo(rule),
- bookingMethods(),
- earliestBookingTime(rule),
- latestBookingTime(rule),
- minimumBookingNotice(rule),
- maximumBookingNotice(rule),
- message(rule),
- pickupMessage(rule),
- dropOffMessage(rule)
- )
+ BookingInfo
+ .of()
+ .withContactInfo(contactInfo(rule))
+ .withBookingMethods(bookingMethods())
+ .withEarliestBookingTime(earliestBookingTime(rule))
+ .withLatestBookingTime(latestBookingTime(rule))
+ .withMinimumBookingNotice(minimumBookingNotice(rule))
+ .withMaximumBookingNotice(maximumBookingNotice(rule))
+ .withMessage(message(rule))
+ .withPickupMessage(pickupMessage(rule))
+ .withDropOffMessage(dropOffMessage(rule))
+ .build()
);
}
diff --git a/src/main/java/org/opentripplanner/model/BookingTime.java b/src/main/java/org/opentripplanner/model/BookingTime.java
deleted file mode 100644
index 2582752f3c1..00000000000
--- a/src/main/java/org/opentripplanner/model/BookingTime.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package org.opentripplanner.model;
-
-import java.io.Serializable;
-import java.time.LocalTime;
-
-/**
- * Represents either an earliest or latest time a trip can be booked relative to the departure day
- * of the trip.
- */
-public class BookingTime implements Serializable {
-
- private final LocalTime time;
-
- private final int daysPrior;
-
- public BookingTime(LocalTime time, int daysPrior) {
- this.time = time;
- this.daysPrior = daysPrior;
- }
-
- public LocalTime getTime() {
- return time;
- }
-
- public int getDaysPrior() {
- return daysPrior;
- }
-}
diff --git a/src/main/java/org/opentripplanner/model/StopTime.java b/src/main/java/org/opentripplanner/model/StopTime.java
index e753b8d2885..edd5fbdb52d 100644
--- a/src/main/java/org/opentripplanner/model/StopTime.java
+++ b/src/main/java/org/opentripplanner/model/StopTime.java
@@ -7,6 +7,7 @@
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.StopTimeKey;
import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* This class is TEMPORALLY used during mapping of GTFS and Netex into the internal Model, it is not
diff --git a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java
index 9ba85d6b532..1bfb0184138 100644
--- a/src/main/java/org/opentripplanner/model/TripTimeOnDate.java
+++ b/src/main/java/org/opentripplanner/model/TripTimeOnDate.java
@@ -13,6 +13,7 @@
import org.opentripplanner.transit.model.timetable.StopTimeKey;
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.model.timetable.TripTimes;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/src/main/java/org/opentripplanner/model/plan/Leg.java b/src/main/java/org/opentripplanner/model/plan/Leg.java
index 5a032def979..1ee72761d66 100644
--- a/src/main/java/org/opentripplanner/model/plan/Leg.java
+++ b/src/main/java/org/opentripplanner/model/plan/Leg.java
@@ -11,7 +11,6 @@
import org.locationtech.jts.geom.LineString;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.lang.Sandbox;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.fare.FareProductUse;
import org.opentripplanner.model.plan.legreference.LegReference;
@@ -25,6 +24,7 @@
import org.opentripplanner.transit.model.site.FareZone;
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* One leg of a trip -- that is, a temporally continuous piece of the journey that takes place on a
diff --git a/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java b/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java
index 6bf39d5aa4c..d94ec1895c2 100644
--- a/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java
+++ b/src/main/java/org/opentripplanner/model/plan/ScheduledTransitLeg.java
@@ -20,7 +20,6 @@
import org.opentripplanner.framework.lang.DoubleUtils;
import org.opentripplanner.framework.time.ServiceDateUtils;
import org.opentripplanner.framework.tostring.ToStringBuilder;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.fare.FareProductUse;
import org.opentripplanner.model.plan.legreference.LegReference;
@@ -38,6 +37,7 @@
import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
import org.opentripplanner.transit.model.timetable.TripTimes;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* One leg of a trip -- that is, a temporally continuous piece of the journey that takes place on a
diff --git a/src/main/java/org/opentripplanner/netex/mapping/BookingInfoMapper.java b/src/main/java/org/opentripplanner/netex/mapping/BookingInfoMapper.java
index afb9742fd85..d6c81c76e3d 100644
--- a/src/main/java/org/opentripplanner/netex/mapping/BookingInfoMapper.java
+++ b/src/main/java/org/opentripplanner/netex/mapping/BookingInfoMapper.java
@@ -9,10 +9,10 @@
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
-import org.opentripplanner.model.BookingInfo;
-import org.opentripplanner.model.BookingMethod;
-import org.opentripplanner.model.BookingTime;
import org.opentripplanner.transit.model.organization.ContactInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingMethod;
+import org.opentripplanner.transit.model.timetable.booking.BookingTime;
import org.rutebanken.netex.model.BookingArrangementsStructure;
import org.rutebanken.netex.model.BookingMethodEnumeration;
import org.rutebanken.netex.model.ContactStructure;
@@ -45,14 +45,14 @@ BookingInfo map(
ServiceJourney serviceJourney,
FlexibleLine flexibleLine
) {
- return new BookingInfoBuilder()
+ return new NetexBookingInfoBuilder()
.withFlexibleLine(flexibleLine)
.withServiceJourney(serviceJourney)
.withStopPoint(stopPoint)
.build();
}
- private class BookingInfoBuilder {
+ private class NetexBookingInfoBuilder {
private ContactStructure bookingContact;
private List bookingMethods = new ArrayList<>();
@@ -65,7 +65,7 @@ private class BookingInfoBuilder {
private String serviceJourneyRef;
private String stopPointRef;
- private BookingInfoBuilder withFlexibleLine(FlexibleLine flexibleLine) {
+ private NetexBookingInfoBuilder withFlexibleLine(FlexibleLine flexibleLine) {
if (flexibleLine != null) {
this.hasBookingInfo = true;
this.flexibleLineRef = ref("FlexibleLine", flexibleLine);
@@ -81,7 +81,7 @@ private BookingInfoBuilder withFlexibleLine(FlexibleLine flexibleLine) {
return this;
}
- private BookingInfoBuilder withServiceJourney(ServiceJourney serviceJourney) {
+ private NetexBookingInfoBuilder withServiceJourney(ServiceJourney serviceJourney) {
if (serviceJourney != null && serviceJourney.getFlexibleServiceProperties() != null) {
this.hasBookingInfo = true;
this.serviceJourneyRef = ref("ServiceJourney", serviceJourney);
@@ -98,7 +98,7 @@ private BookingInfoBuilder withServiceJourney(ServiceJourney serviceJourney) {
return this;
}
- private BookingInfoBuilder withStopPoint(StopPointInJourneyPattern stopPoint) {
+ private NetexBookingInfoBuilder withStopPoint(StopPointInJourneyPattern stopPoint) {
BookingArrangementsStructure bookingArrangements = stopPoint.getBookingArrangements();
if (bookingArrangements != null) {
this.hasBookingInfo = true;
@@ -220,17 +220,16 @@ private BookingInfo build(
}
String bookingInfoMessage = bookingNote != null ? bookingNote.getValue() : null;
- return new BookingInfo(
- contactInfo,
- filteredBookingMethods,
- otpEarliestBookingTime,
- otpLatestBookingTime,
- minimumBookingNotice,
- Duration.ZERO,
- bookingInfoMessage,
- null,
- null
- );
+ return BookingInfo
+ .of()
+ .withContactInfo(contactInfo)
+ .withBookingMethods(filteredBookingMethods)
+ .withEarliestBookingTime(otpEarliestBookingTime)
+ .withLatestBookingTime(otpLatestBookingTime)
+ .withMinimumBookingNotice(minimumBookingNotice)
+ .withMaximumBookingNotice(Duration.ZERO)
+ .withMessage(bookingInfoMessage)
+ .build();
}
private void setIfNotEmpty(
@@ -241,24 +240,18 @@ private void setIfNotEmpty(
Duration minimumBookingPeriod,
MultilingualString bookingNote
) {
- if (bookingContact != null) {
- this.bookingContact = bookingContact;
- }
if (bookingMethods != null && !bookingMethods.isEmpty()) {
this.bookingMethods = bookingMethods;
}
- if (latestBookingTime != null) {
- this.latestBookingTime = latestBookingTime;
- }
- if (bookWhen != null) {
- this.bookWhen = bookWhen;
- }
- if (minimumBookingPeriod != null) {
- this.minimumBookingPeriod = minimumBookingPeriod;
- }
- if (bookingNote != null) {
- this.bookingNote = bookingNote;
- }
+ this.bookingContact = getOrDefault(bookingContact, this.bookingContact);
+ this.minimumBookingPeriod = getOrDefault(minimumBookingPeriod, this.minimumBookingPeriod);
+ this.latestBookingTime = getOrDefault(latestBookingTime, this.latestBookingTime);
+ this.bookWhen = getOrDefault(bookWhen, this.bookWhen);
+ this.bookingNote = getOrDefault(bookingNote, this.bookingNote);
}
}
+
+ private static T getOrDefault(T value, T defaultValue) {
+ return value == null ? defaultValue : value;
+ }
}
diff --git a/src/main/java/org/opentripplanner/netex/mapping/BookingMethodMapper.java b/src/main/java/org/opentripplanner/netex/mapping/BookingMethodMapper.java
index 6514667598b..4449913605b 100644
--- a/src/main/java/org/opentripplanner/netex/mapping/BookingMethodMapper.java
+++ b/src/main/java/org/opentripplanner/netex/mapping/BookingMethodMapper.java
@@ -1,6 +1,6 @@
package org.opentripplanner.netex.mapping;
-import org.opentripplanner.model.BookingMethod;
+import org.opentripplanner.transit.model.timetable.booking.BookingMethod;
import org.rutebanken.netex.model.BookingMethodEnumeration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/src/main/java/org/opentripplanner/netex/mapping/StopTimesMapper.java b/src/main/java/org/opentripplanner/netex/mapping/StopTimesMapper.java
index 06de1623337..676ab053da4 100644
--- a/src/main/java/org/opentripplanner/netex/mapping/StopTimesMapper.java
+++ b/src/main/java/org/opentripplanner/netex/mapping/StopTimesMapper.java
@@ -15,7 +15,6 @@
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.i18n.NonLocalizedString;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMap;
import org.opentripplanner.netex.index.api.ReadOnlyHierarchicalMapById;
@@ -27,6 +26,7 @@
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
import org.rutebanken.netex.model.DestinationDisplay;
import org.rutebanken.netex.model.DestinationDisplay_VersionStructure;
import org.rutebanken.netex.model.FlexibleLine;
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistance.java b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistance.java
index 7ab605fb792..ee67e4f76d0 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistance.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/filterchain/framework/groupids/GroupByDistance.java
@@ -10,7 +10,7 @@
import org.opentripplanner.routing.algorithm.filterchain.framework.spi.GroupId;
/**
- * This class create a group identifier for an itinerary based on the longest legs which together
+ * This class creates a group identifier for an itinerary based on the longest legs which together
* account for more than 'p' part of the total distance. Transit legs must overlap and ride the
* same trip, while street-legs only need to have the same mode. We call the set of legs the
* 'key-set-of-legs' or just 'key-set'.
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
index 2780cc8ba14..d7098c20661 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
@@ -24,8 +24,8 @@
import org.opentripplanner.raptor.api.path.RaptorPath;
import org.opentripplanner.raptor.api.path.TransferPathLeg;
import org.opentripplanner.raptor.api.path.TransitPathLeg;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultRaptorTransfer;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.Transfer;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule;
@@ -146,12 +146,12 @@ else if (pathLeg.isTransferLeg()) {
}
var penaltyCost = 0;
- if (accessPathLeg.access() instanceof DefaultAccessEgress ae) {
+ if (accessPathLeg.access() instanceof RoutingAccessEgress ae) {
itinerary.setAccessPenalty(ae.penalty());
penaltyCost += ae.penalty().cost().toSeconds();
}
- if (egressPathLeg.egress() instanceof DefaultAccessEgress ae) {
+ if (egressPathLeg.egress() instanceof RoutingAccessEgress ae) {
itinerary.setEgressPenalty(ae.penalty());
penaltyCost += ae.penalty().cost().toSeconds();
}
@@ -169,7 +169,7 @@ private List mapAccessLeg(AccessPathLeg accessPathLeg) {
return List.of();
}
- DefaultAccessEgress accessPath = (DefaultAccessEgress) accessPathLeg.access();
+ RoutingAccessEgress accessPath = (RoutingAccessEgress) accessPathLeg.access();
var graphPath = new GraphPath<>(accessPath.getLastState());
@@ -279,7 +279,7 @@ private Itinerary mapEgressLeg(EgressPathLeg egressPathLeg) {
return null;
}
- DefaultAccessEgress egressPath = (DefaultAccessEgress) egressPathLeg.egress();
+ RoutingAccessEgress egressPath = (RoutingAccessEgress) egressPathLeg.egress();
var graphPath = new GraphPath<>(egressPath.getLastState());
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
index 0a9e46d2fe3..6a1404c3039 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/TransitRouter.java
@@ -23,7 +23,7 @@
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgressType;
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.AccessEgresses;
import org.opentripplanner.routing.algorithm.raptoradapter.router.street.FlexAccessEgressRouter;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers.AccessEgressMapper;
@@ -171,8 +171,8 @@ private TransitRouterResult route() {
}
private AccessEgresses fetchAccessEgresses() {
- final var asyncAccessList = new ArrayList();
- final var asyncEgressList = new ArrayList();
+ final var accessList = new ArrayList();
+ final var egressList = new ArrayList();
if (OTPFeature.ParallelRouting.isOn()) {
try {
@@ -180,19 +180,19 @@ private AccessEgresses fetchAccessEgresses() {
// log-trace-parameters-propagation and graceful timeout handling here.
CompletableFuture
.allOf(
- CompletableFuture.runAsync(() -> asyncAccessList.addAll(fetchAccess())),
- CompletableFuture.runAsync(() -> asyncEgressList.addAll(fetchEgress()))
+ CompletableFuture.runAsync(() -> accessList.addAll(fetchAccess())),
+ CompletableFuture.runAsync(() -> egressList.addAll(fetchEgress()))
)
.join();
} catch (CompletionException e) {
RoutingValidationException.unwrapAndRethrowCompletionException(e);
}
} else {
- asyncAccessList.addAll(fetchAccess());
- asyncEgressList.addAll(fetchEgress());
+ accessList.addAll(fetchAccess());
+ egressList.addAll(fetchEgress());
}
- verifyAccessEgress(asyncAccessList, asyncEgressList);
+ verifyAccessEgress(accessList, egressList);
// Decorate access/egress with a penalty to make it less favourable than transit
var penaltyDecorator = new AccessEgressPenaltyDecorator(
@@ -201,27 +201,27 @@ private AccessEgresses fetchAccessEgresses() {
request.preferences().street().accessEgress().penalty()
);
- var accessList = penaltyDecorator.decorateAccess(asyncAccessList);
- var egressList = penaltyDecorator.decorateEgress(asyncEgressList);
+ var accessListWithPenalty = penaltyDecorator.decorateAccess(accessList);
+ var egressListWithPenalty = penaltyDecorator.decorateEgress(egressList);
- return new AccessEgresses(accessList, egressList);
+ return new AccessEgresses(accessListWithPenalty, egressListWithPenalty);
}
- private Collection fetchAccess() {
+ private Collection extends RoutingAccessEgress> fetchAccess() {
debugTimingAggregator.startedAccessCalculating();
var list = fetchAccessEgresses(ACCESS);
debugTimingAggregator.finishedAccessCalculating();
return list;
}
- private Collection fetchEgress() {
+ private Collection extends RoutingAccessEgress> fetchEgress() {
debugTimingAggregator.startedEgressCalculating();
var list = fetchAccessEgresses(EGRESS);
debugTimingAggregator.finishedEgressCalculating();
return list;
}
- private Collection fetchAccessEgresses(AccessEgressType type) {
+ private Collection extends RoutingAccessEgress> fetchAccessEgresses(AccessEgressType type) {
var streetRequest = type.isAccess() ? request.journey().access() : request.journey().egress();
// Prepare access/egress lists
@@ -255,7 +255,7 @@ private Collection fetchAccessEgresses(AccessEgressType typ
stopCountLimit
);
- List results = new ArrayList<>(
+ List results = new ArrayList<>(
AccessEgressMapper.mapNearbyStops(nearbyStops, type.isEgress())
);
results = timeshiftRideHailing(streetRequest, type, results);
@@ -267,7 +267,7 @@ private Collection fetchAccessEgresses(AccessEgressType typ
temporaryVerticesContainer,
serverContext,
additionalSearchDays,
- serverContext.flexConfig(),
+ serverContext.flexParameters(),
serverContext.dataOverlayContext(accessRequest),
type.isEgress()
);
@@ -288,10 +288,10 @@ private Collection fetchAccessEgresses(AccessEgressType typ
* This method is a good candidate to be moved to the access/egress filter chain when that has
* been added.
*/
- private List timeshiftRideHailing(
+ private List timeshiftRideHailing(
StreetRequest streetRequest,
AccessEgressType type,
- List accessEgressList
+ List accessEgressList
) {
if (streetRequest.mode() != StreetMode.CAR_HAILING) {
return accessEgressList;
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecorator.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecorator.java
index 8f8d73c1acd..5adb1fdc001 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecorator.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecorator.java
@@ -1,7 +1,7 @@
package org.opentripplanner.routing.algorithm.raptoradapter.router.street;
import java.util.Collection;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.request.framework.TimeAndCostPenaltyForEnum;
@@ -25,19 +25,23 @@ public AccessEgressPenaltyDecorator(
this.penalty = penalty;
}
- public Collection decorateAccess(Collection list) {
+ public Collection extends RoutingAccessEgress> decorateAccess(
+ Collection list
+ ) {
return decorate(list, accessMode);
}
- public Collection decorateEgress(Collection list) {
+ public Collection extends RoutingAccessEgress> decorateEgress(
+ Collection list
+ ) {
return decorate(list, egressMode);
}
/**
* Decorate each access-egress with a penalty according to the specified street-mode.
*/
- private Collection decorate(
- Collection input,
+ private Collection extends RoutingAccessEgress> decorate(
+ Collection input,
StreetMode requestedMode
) {
if (input.isEmpty()) {
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgresses.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgresses.java
index f2e8fe6c9f1..50b4f528c5b 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgresses.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgresses.java
@@ -1,26 +1,26 @@
package org.opentripplanner.routing.algorithm.raptoradapter.router.street;
import java.util.Collection;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
public class AccessEgresses {
- private final Collection accesses;
- private final Collection egresses;
+ private final Collection extends RoutingAccessEgress> accesses;
+ private final Collection extends RoutingAccessEgress> egresses;
public AccessEgresses(
- Collection accesses,
- Collection egresses
+ Collection extends RoutingAccessEgress> accesses,
+ Collection extends RoutingAccessEgress> egresses
) {
this.accesses = accesses;
this.egresses = egresses;
}
- public Collection getAccesses() {
+ public Collection extends RoutingAccessEgress> getAccesses() {
return accesses;
}
- public Collection getEgresses() {
+ public Collection extends RoutingAccessEgress> getEgresses() {
return egresses;
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java
index 5a4892ee96b..ff60138d77d 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/DirectFlexRouter.java
@@ -41,7 +41,7 @@ public static List route(
request.journey().direct(),
serverContext.dataOverlayContext(request),
false,
- serverContext.flexConfig().maxAccessWalkDuration(),
+ serverContext.flexParameters().maxAccessWalkDuration(),
0
);
Collection egressStops = AccessEgressRouter.streetSearch(
@@ -51,23 +51,23 @@ public static List route(
request.journey().direct(),
serverContext.dataOverlayContext(request),
true,
- serverContext.flexConfig().maxEgressWalkDuration(),
+ serverContext.flexParameters().maxEgressWalkDuration(),
0
);
- FlexRouter flexRouter = new FlexRouter(
+ var flexRouter = new FlexRouter(
serverContext.graph(),
serverContext.transitService(),
- serverContext.flexConfig(),
+ serverContext.flexParameters(),
request.dateTime(),
- request.arriveBy(),
+ request.bookingTime(),
additionalSearchDays.additionalSearchDaysInPast(),
additionalSearchDays.additionalSearchDaysInFuture(),
accessStops,
egressStops
);
- return new ArrayList<>(flexRouter.createFlexOnlyItineraries());
+ return new ArrayList<>(flexRouter.createFlexOnlyItineraries(request.arriveBy()));
}
}
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java
index 639a6dbb997..5023e595678 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/FlexAccessEgressRouter.java
@@ -4,6 +4,7 @@
import java.util.List;
import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext;
import org.opentripplanner.ext.flex.FlexAccessEgress;
+import org.opentripplanner.ext.flex.FlexParameters;
import org.opentripplanner.ext.flex.FlexRouter;
import org.opentripplanner.framework.application.OTPRequestTimeoutException;
import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays;
@@ -12,7 +13,6 @@
import org.opentripplanner.routing.api.request.request.StreetRequest;
import org.opentripplanner.routing.graphfinder.NearbyStop;
import org.opentripplanner.standalone.api.OtpServerRequestContext;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.street.search.TemporaryVerticesContainer;
import org.opentripplanner.transit.service.TransitService;
@@ -25,7 +25,7 @@ public static Collection routeAccessEgress(
TemporaryVerticesContainer verticesContainer,
OtpServerRequestContext serverContext,
AdditionalSearchDays searchDays,
- FlexConfig config,
+ FlexParameters config,
DataOverlayContext dataOverlayContext,
boolean isEgress
) {
@@ -41,7 +41,7 @@ public static Collection routeAccessEgress(
new StreetRequest(StreetMode.WALK),
dataOverlayContext,
false,
- serverContext.flexConfig().maxAccessWalkDuration(),
+ serverContext.flexParameters().maxAccessWalkDuration(),
0
)
: List.of();
@@ -54,7 +54,7 @@ public static Collection routeAccessEgress(
new StreetRequest(StreetMode.WALK),
dataOverlayContext,
true,
- serverContext.flexConfig().maxEgressWalkDuration(),
+ serverContext.flexParameters().maxEgressWalkDuration(),
0
)
: List.of();
@@ -64,7 +64,7 @@ public static Collection routeAccessEgress(
transitService,
config,
request.dateTime(),
- request.arriveBy(),
+ request.bookingTime(),
searchDays.additionalSearchDaysInPast(),
searchDays.additionalSearchDaysInFuture(),
accessStops,
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java
index 35987c0af7d..3f68d91321e 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java
@@ -2,7 +2,6 @@
import java.util.Objects;
import org.opentripplanner.framework.model.TimeAndCost;
-import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
import org.opentripplanner.raptor.api.model.RaptorConstants;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
import org.opentripplanner.street.search.state.State;
@@ -10,7 +9,7 @@
/**
* Default implementation of the RaptorAccessEgress interface.
*/
-public class DefaultAccessEgress implements RaptorAccessEgress {
+public class DefaultAccessEgress implements RoutingAccessEgress {
private final int stop;
private final int durationInSeconds;
@@ -34,7 +33,7 @@ public DefaultAccessEgress(int stop, State lastState) {
this.penalty = TimeAndCost.ZERO;
}
- protected DefaultAccessEgress(DefaultAccessEgress other, TimeAndCost penalty) {
+ protected DefaultAccessEgress(RoutingAccessEgress other, TimeAndCost penalty) {
if (other.hasPenalty()) {
throw new IllegalStateException("Can not add penalty twice...");
}
@@ -49,15 +48,6 @@ protected DefaultAccessEgress(DefaultAccessEgress other, TimeAndCost penalty) {
this.lastState = other.getLastState();
}
- /**
- * Return a new copy of this with the requested penalty.
- *
- * OVERRIDE THIS IF KEEPING THE TYPE IS IMPORTANT!
- */
- public DefaultAccessEgress withPenalty(TimeAndCost penalty) {
- return new DefaultAccessEgress(this, penalty);
- }
-
@Override
public int durationInSeconds() {
return durationInSeconds;
@@ -83,22 +73,36 @@ public boolean hasOpeningHours() {
return false;
}
+ @Override
public State getLastState() {
return lastState;
}
+ @Override
public boolean isWalkOnly() {
return lastState.containsOnlyWalkMode();
}
+ @Override
public boolean hasPenalty() {
return !penalty.isZero();
}
+ @Override
public TimeAndCost penalty() {
return penalty;
}
+ /**
+ * Return a new copy of this with the requested penalty.
+ *
+ * OVERRIDE THIS IF KEEPING THE TYPE IS IMPORTANT!
+ */
+ @Override
+ public RoutingAccessEgress withPenalty(TimeAndCost penalty) {
+ return new DefaultAccessEgress(this, penalty);
+ }
+
@Override
public int earliestDepartureTime(int requestedDepartureTime) {
return requestedDepartureTime;
@@ -121,7 +125,7 @@ public final boolean equals(Object o) {
}
// We check the contract of DefaultAccessEgress used for routing for equality, we do not care
// if the entries are different implementation or have different AStar paths(lastState).
- if (!(o instanceof DefaultAccessEgress that)) {
+ if (!(o instanceof RoutingAccessEgress that)) {
return false;
}
return (
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java
index 0cf95b544f4..04f51e76681 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/FlexAccessEgressAdapter.java
@@ -59,7 +59,7 @@ public boolean isWalkOnly() {
}
@Override
- public DefaultAccessEgress withPenalty(TimeAndCost penalty) {
+ public RoutingAccessEgress withPenalty(TimeAndCost penalty) {
return new FlexAccessEgressAdapter(this, penalty);
}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java
new file mode 100644
index 00000000000..d22ec0f71f9
--- /dev/null
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/RoutingAccessEgress.java
@@ -0,0 +1,33 @@
+package org.opentripplanner.routing.algorithm.raptoradapter.transit;
+
+import org.opentripplanner.framework.model.TimeAndCost;
+import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
+import org.opentripplanner.street.search.state.State;
+
+/**
+ * Encapsulate information about an access or egress path. This interface extends
+ * {@link RaptorAccessEgress} with methods relevant only to street routing and
+ * access/egress filtering.
+ */
+public interface RoutingAccessEgress extends RaptorAccessEgress {
+ /**
+ * Return a new copy of this with the requested penalty.
+ *
+ * OVERRIDE THIS IF KEEPING THE TYPE IS IMPORTANT!
+ */
+ RoutingAccessEgress withPenalty(TimeAndCost penalty);
+
+ /**
+ * Return the last state both in the case of access and egress.
+ */
+ State getLastState();
+
+ /**
+ * Return true if all edges are traversed on foot.
+ */
+ boolean isWalkOnly();
+
+ boolean hasPenalty();
+
+ TimeAndCost penalty();
+}
diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java
index 6cef677f5a9..fd7619ac297 100644
--- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java
+++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/AccessEgressMapper.java
@@ -7,12 +7,13 @@
import org.opentripplanner.ext.flex.FlexAccessEgress;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.FlexAccessEgressAdapter;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
import org.opentripplanner.routing.graphfinder.NearbyStop;
import org.opentripplanner.transit.model.site.RegularStop;
public class AccessEgressMapper {
- public static List mapNearbyStops(
+ public static List mapNearbyStops(
Collection accessStops,
boolean isEgress
) {
@@ -23,7 +24,7 @@ public static List mapNearbyStops(
.collect(Collectors.toList());
}
- public static Collection mapFlexAccessEgresses(
+ public static Collection mapFlexAccessEgresses(
Collection flexAccessEgresses,
boolean isEgress
) {
@@ -33,7 +34,7 @@ public static Collection mapFlexAccessEgresses(
.collect(Collectors.toList());
}
- private static DefaultAccessEgress mapNearbyStop(NearbyStop nearbyStop, boolean isEgress) {
+ private static RoutingAccessEgress mapNearbyStop(NearbyStop nearbyStop, boolean isEgress) {
if (!(nearbyStop.stop instanceof RegularStop)) {
return null;
}
diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java
index ce2fdb31c44..76e5dcc558a 100644
--- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java
+++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java
@@ -81,6 +81,8 @@ public class RouteRequest implements Cloneable, Serializable {
private boolean wheelchair = false;
+ private Instant bookingTime;
+
/* CONSTRUCTORS */
/** Constructor for options; modes defaults to walk and transit */
@@ -112,6 +114,20 @@ public void withPreferences(Consumer body) {
this.preferences = preferences.copyOf().apply(body).build();
}
+ /**
+ * The booking time is used to exclude services which are not bookable at the
+ * requested booking time. If a service is bookable at this time or later, the service
+ * is included. This apply to FLEX access, egress and direct services.
+ */
+ public Instant bookingTime() {
+ return bookingTime;
+ }
+
+ public RouteRequest setBookingTime(Instant bookingTime) {
+ this.bookingTime = bookingTime;
+ return this;
+ }
+
void setPreferences(RoutingPreferences preferences) {
this.preferences = preferences;
}
diff --git a/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java b/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java
index 4c35f11de8a..ada17120c6b 100644
--- a/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java
+++ b/src/main/java/org/opentripplanner/routing/graphfinder/NearbyStop.java
@@ -43,6 +43,15 @@ public static NearbyStop nearbyStopForState(State state, StopLocation stop) {
return new NearbyStop(stop, effectiveWalkDistance, edges, state);
}
+ /**
+ * Return {@code true} if this instance has a lower weight/cost than the given {@code other}.
+ * If the state is not set, the distance is used for comparison instead. If the
+ * weight/cost/distance is equals (or worse) this method returns {@code false}.
+ */
+ public boolean isBetter(NearbyStop other) {
+ return compareTo(other) < 0;
+ }
+
@Override
public int compareTo(NearbyStop that) {
if ((this.state == null) != (that.state == null)) {
diff --git a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java
index b632ea6104b..6552d82770f 100644
--- a/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java
+++ b/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java
@@ -7,6 +7,7 @@
import org.opentripplanner.astar.spi.TraverseVisitor;
import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext;
import org.opentripplanner.ext.emissions.EmissionsService;
+import org.opentripplanner.ext.flex.FlexParameters;
import org.opentripplanner.ext.ridehailing.RideHailingService;
import org.opentripplanner.ext.stopconsolidation.StopConsolidationService;
import org.opentripplanner.framework.application.OTPFeature;
@@ -23,7 +24,6 @@
import org.opentripplanner.service.vehiclerental.VehicleRentalService;
import org.opentripplanner.service.worldenvelope.WorldEnvelopeService;
import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.street.service.StreetLimitationParametersService;
@@ -124,7 +124,7 @@ default GraphFinder graphFinder() {
return GraphFinder.getInstance(graph(), transitService()::findRegularStops);
}
- FlexConfig flexConfig();
+ FlexParameters flexParameters();
VectorTileConfig vectorTileConfig();
diff --git a/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java b/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java
index 4ac0688a759..55128af5659 100644
--- a/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/RouterConfig.java
@@ -8,6 +8,7 @@
import com.fasterxml.jackson.databind.node.MissingNode;
import java.io.Serializable;
import java.util.List;
+import org.opentripplanner.ext.flex.FlexParameters;
import org.opentripplanner.ext.ridehailing.RideHailingServiceParameters;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
@@ -127,7 +128,7 @@ public VectorTileConfig vectorTileConfig() {
return vectorTileConfig;
}
- public FlexConfig flexConfig() {
+ public FlexParameters flexParameters() {
return flexConfig;
}
diff --git a/src/main/java/org/opentripplanner/standalone/config/sandbox/FlexConfig.java b/src/main/java/org/opentripplanner/standalone/config/sandbox/FlexConfig.java
index cefec90b09e..2521ca5e57b 100644
--- a/src/main/java/org/opentripplanner/standalone/config/sandbox/FlexConfig.java
+++ b/src/main/java/org/opentripplanner/standalone/config/sandbox/FlexConfig.java
@@ -4,11 +4,12 @@
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3;
import java.time.Duration;
+import org.opentripplanner.ext.flex.FlexParameters;
import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
-public class FlexConfig {
+public class FlexConfig implements FlexParameters {
- public static final FlexConfig DEFAULT = new FlexConfig();
+ private static final FlexParameters DEFAULT = FlexParameters.defaultValues();
public static final String ACCESS_EGRESS_DESCRIPTION =
"""
@@ -58,7 +59,7 @@ public FlexConfig(NodeAdapter root, String parameterName) {
A lower value means that the routing is faster.
"""
)
- .asDuration(DEFAULT.maxTransferDuration);
+ .asDuration(DEFAULT.maxTransferDuration());
maxFlexTripDuration =
json
@@ -70,7 +71,7 @@ public FlexConfig(NodeAdapter root, String parameterName) {
"the access/egress duration to the boarding/alighting of the flex trip, as well as the " +
"connection to the transit stop."
)
- .asDuration(DEFAULT.maxFlexTripDuration);
+ .asDuration(DEFAULT.maxFlexTripDuration());
maxAccessWalkDuration =
json
@@ -80,7 +81,7 @@ public FlexConfig(NodeAdapter root, String parameterName) {
"The maximum duration the passenger will be allowed to walk to reach a flex stop or zone."
)
.description(ACCESS_EGRESS_DESCRIPTION)
- .asDuration(DEFAULT.maxAccessWalkDuration);
+ .asDuration(DEFAULT.maxAccessWalkDuration());
maxEgressWalkDuration =
json
@@ -90,7 +91,7 @@ public FlexConfig(NodeAdapter root, String parameterName) {
"The maximum duration the passenger will be allowed to walk after leaving the flex vehicle at the final destination."
)
.description(ACCESS_EGRESS_DESCRIPTION)
- .asDuration(DEFAULT.maxEgressWalkDuration);
+ .asDuration(DEFAULT.maxEgressWalkDuration());
}
public Duration maxFlexTripDuration() {
diff --git a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java
index 93158f87cff..eb244ce726c 100644
--- a/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java
+++ b/src/main/java/org/opentripplanner/standalone/configure/ConstructApplicationModule.java
@@ -56,7 +56,7 @@ OtpServerRequestContext providesServerContext(
realtimeVehicleService,
vehicleRentalService,
emissionsService,
- routerConfig.flexConfig(),
+ routerConfig.flexParameters(),
rideHailingServices,
stopConsolidationService,
streetLimitationParametersService,
diff --git a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java
index 019b7267015..7a4ccea9247 100644
--- a/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java
+++ b/src/main/java/org/opentripplanner/standalone/server/DefaultServerRequestContext.java
@@ -6,6 +6,7 @@
import javax.annotation.Nullable;
import org.opentripplanner.astar.spi.TraverseVisitor;
import org.opentripplanner.ext.emissions.EmissionsService;
+import org.opentripplanner.ext.flex.FlexParameters;
import org.opentripplanner.ext.ridehailing.RideHailingService;
import org.opentripplanner.ext.stopconsolidation.StopConsolidationService;
import org.opentripplanner.inspector.raster.TileRendererManager;
@@ -24,7 +25,6 @@
import org.opentripplanner.standalone.api.OtpServerRequestContext;
import org.opentripplanner.standalone.config.routerconfig.TransitRoutingConfig;
import org.opentripplanner.standalone.config.routerconfig.VectorTileConfig;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.street.service.StreetLimitationParametersService;
import org.opentripplanner.transit.service.TransitService;
@@ -41,7 +41,7 @@ public class DefaultServerRequestContext implements OtpServerRequestContext {
private final RaptorConfig raptorConfig;
private final TileRendererManager tileRendererManager;
private final VectorTileConfig vectorTileConfig;
- private final FlexConfig flexConfig;
+ private final FlexParameters flexParameters;
private final TraverseVisitor traverseVisitor;
private final WorldEnvelopeService worldEnvelopeService;
private final RealtimeVehicleService realtimeVehicleService;
@@ -69,7 +69,7 @@ private DefaultServerRequestContext(
List rideHailingServices,
StopConsolidationService stopConsolidationService,
StreetLimitationParametersService streetLimitationParametersService,
- FlexConfig flexConfig,
+ FlexParameters flexParameters,
TraverseVisitor traverseVisitor
) {
this.graph = graph;
@@ -80,7 +80,7 @@ private DefaultServerRequestContext(
this.tileRendererManager = tileRendererManager;
this.vectorTileConfig = vectorTileConfig;
this.vehicleRentalService = vehicleRentalService;
- this.flexConfig = flexConfig;
+ this.flexParameters = flexParameters;
this.traverseVisitor = traverseVisitor;
this.routeRequestDefaults = routeRequestDefaults;
this.worldEnvelopeService = worldEnvelopeService;
@@ -106,7 +106,7 @@ public static DefaultServerRequestContext create(
RealtimeVehicleService realtimeVehicleService,
VehicleRentalService vehicleRentalService,
@Nullable EmissionsService emissionsService,
- FlexConfig flexConfig,
+ FlexParameters flexParameters,
List rideHailingServices,
@Nullable StopConsolidationService stopConsolidationService,
StreetLimitationParametersService streetLimitationParametersService,
@@ -128,7 +128,7 @@ public static DefaultServerRequestContext create(
rideHailingServices,
stopConsolidationService,
streetLimitationParametersService,
- flexConfig,
+ flexParameters,
traverseVisitor
);
}
@@ -226,8 +226,8 @@ public TraverseVisitor traverseVisitor() {
}
@Override
- public FlexConfig flexConfig() {
- return flexConfig;
+ public FlexParameters flexParameters() {
+ return flexParameters;
}
@Override
diff --git a/src/main/java/org/opentripplanner/street/model/vertex/TransitStopVertex.java b/src/main/java/org/opentripplanner/street/model/vertex/TransitStopVertex.java
index d196c7d1830..a9d7d221d44 100644
--- a/src/main/java/org/opentripplanner/street/model/vertex/TransitStopVertex.java
+++ b/src/main/java/org/opentripplanner/street/model/vertex/TransitStopVertex.java
@@ -41,6 +41,10 @@ public class TransitStopVertex extends StationElementVertex {
this.wheelchairAccessibility = stop.getWheelchairAccessibility();
}
+ public static TransitStopVertexBuilder of() {
+ return new TransitStopVertexBuilder();
+ }
+
public Accessibility getWheelchairAccessibility() {
return wheelchairAccessibility;
}
diff --git a/src/main/java/org/opentripplanner/street/model/vertex/TransitStopVertexBuilder.java b/src/main/java/org/opentripplanner/street/model/vertex/TransitStopVertexBuilder.java
index 83369629af5..38fe0d45790 100644
--- a/src/main/java/org/opentripplanner/street/model/vertex/TransitStopVertexBuilder.java
+++ b/src/main/java/org/opentripplanner/street/model/vertex/TransitStopVertexBuilder.java
@@ -10,6 +10,12 @@ public class TransitStopVertexBuilder {
private RegularStop stop;
private Set modes;
+ /**
+ * Protected access to avoid instantiation, use
+ * {@link org.opentripplanner.street.model.vertex.TransitStopVertex#of()} method instead.
+ */
+ TransitStopVertexBuilder() {}
+
public TransitStopVertexBuilder withStop(RegularStop stop) {
this.stop = stop;
return this;
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java
index a62da95e79a..8eea45c092a 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/RealTimeTripTimes.java
@@ -10,9 +10,9 @@
import java.util.function.IntUnaryOperator;
import javax.annotation.Nullable;
import org.opentripplanner.framework.i18n.I18NString;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.transit.model.basic.Accessibility;
import org.opentripplanner.transit.model.framework.DataValidationException;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* A TripTimes represents the arrival and departure times for a single trip in an Timetable. It is
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java
index ff3f61394ae..d367932d24d 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimes.java
@@ -16,11 +16,11 @@
import org.opentripplanner.framework.lang.IntUtils;
import org.opentripplanner.framework.time.DurationUtils;
import org.opentripplanner.framework.time.TimeUtils;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.transit.model.basic.Accessibility;
import org.opentripplanner.transit.model.framework.DataValidationException;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.DeduplicatorService;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* Regular/planed/scheduled read-only version of {@link TripTimes}. The set of static
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java
index 0eee5d3e183..ce142fd7628 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/ScheduledTripTimesBuilder.java
@@ -5,8 +5,8 @@
import javax.annotation.Nullable;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.time.TimeUtils;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.transit.model.framework.DeduplicatorService;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
public class ScheduledTripTimesBuilder {
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java
index bd21905f7d1..0779575aee5 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/StopTimeToScheduledTripTimesMapper.java
@@ -5,9 +5,9 @@
import java.util.Collection;
import java.util.List;
import org.opentripplanner.framework.i18n.I18NString;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.transit.model.framework.DeduplicatorService;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
class StopTimeToScheduledTripTimesMapper {
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java
index 7bf1a826c1c..ff6b022fb59 100644
--- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java
@@ -7,8 +7,8 @@
import java.util.OptionalInt;
import javax.annotation.Nullable;
import org.opentripplanner.framework.i18n.I18NString;
-import org.opentripplanner.model.BookingInfo;
import org.opentripplanner.transit.model.basic.Accessibility;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
/**
* A TripTimes represents the arrival and departure times for a single trip in a timetable. It is
diff --git a/src/main/java/org/opentripplanner/model/BookingInfo.java b/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingInfo.java
similarity index 57%
rename from src/main/java/org/opentripplanner/model/BookingInfo.java
rename to src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingInfo.java
index 4dc0c17c784..3e23696b65a 100644
--- a/src/main/java/org/opentripplanner/model/BookingInfo.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingInfo.java
@@ -1,15 +1,15 @@
-package org.opentripplanner.model;
+package org.opentripplanner.transit.model.timetable.booking;
import java.io.Serializable;
import java.time.Duration;
import java.util.EnumSet;
+import javax.annotation.Nullable;
+import org.opentripplanner.framework.tostring.ToStringBuilder;
import org.opentripplanner.transit.model.organization.ContactInfo;
/**
* Info about how a trip might be booked at a particular stop. All of this is pass-through
* information, except information about booking time and booking notice.
- *
- * // TODO Make the routing take into account booking time and booking notice.
*/
public class BookingInfo implements Serializable {
@@ -20,58 +20,55 @@ public class BookingInfo implements Serializable {
/**
* Cannot be set at the same time as minimumBookingNotice or maximumBookingNotice
*/
+ @Nullable
private final BookingTime earliestBookingTime;
/**
* Cannot be set at the same time as minimumBookingNotice or maximumBookingNotice
*/
+ @Nullable
private final BookingTime latestBookingTime;
/**
* Cannot be set at the same time as earliestBookingTime or latestBookingTime
*/
+ @Nullable
private final Duration minimumBookingNotice;
/**
* Cannot be set at the same time as earliestBookingTime or latestBookingTime
*/
+ @Nullable
private final Duration maximumBookingNotice;
+ @Nullable
private final String message;
+ @Nullable
private final String pickupMessage;
+ @Nullable
private final String dropOffMessage;
- public BookingInfo(
- ContactInfo contactInfo,
- EnumSet bookingMethods,
- BookingTime earliestBookingTime,
- BookingTime latestBookingTime,
- Duration minimumBookingNotice,
- Duration maximumBookingNotice,
- String message,
- String pickupMessage,
- String dropOffMessage
- ) {
- this.contactInfo = contactInfo;
- this.bookingMethods = bookingMethods;
- this.message = message;
- this.pickupMessage = pickupMessage;
- this.dropOffMessage = dropOffMessage;
+ BookingInfo(BookingInfoBuilder builder) {
+ this.contactInfo = builder.contactInfo;
+ this.bookingMethods = builder.bookingMethods;
+ this.message = builder.message;
+ this.pickupMessage = builder.pickupMessage;
+ this.dropOffMessage = builder.dropOffMessage;
// Ensure that earliestBookingTime/latestBookingTime is not set at the same time as
// minimumBookingNotice/maximumBookingNotice
- if (earliestBookingTime != null || latestBookingTime != null) {
- this.earliestBookingTime = earliestBookingTime;
- this.latestBookingTime = latestBookingTime;
+ if (builder.earliestBookingTime != null || builder.latestBookingTime != null) {
+ this.earliestBookingTime = builder.earliestBookingTime;
+ this.latestBookingTime = builder.latestBookingTime;
this.minimumBookingNotice = null;
this.maximumBookingNotice = null;
- } else if (minimumBookingNotice != null || maximumBookingNotice != null) {
+ } else if (builder.minimumBookingNotice != null || builder.maximumBookingNotice != null) {
this.earliestBookingTime = null;
this.latestBookingTime = null;
- this.minimumBookingNotice = minimumBookingNotice;
- this.maximumBookingNotice = maximumBookingNotice;
+ this.minimumBookingNotice = builder.minimumBookingNotice;
+ this.maximumBookingNotice = builder.maximumBookingNotice;
} else {
this.earliestBookingTime = null;
this.latestBookingTime = null;
@@ -80,6 +77,10 @@ public BookingInfo(
}
}
+ public static BookingInfoBuilder of() {
+ return new BookingInfoBuilder();
+ }
+
public ContactInfo getContactInfo() {
return contactInfo;
}
@@ -88,31 +89,54 @@ public EnumSet bookingMethods() {
return bookingMethods;
}
+ @Nullable
public BookingTime getEarliestBookingTime() {
return earliestBookingTime;
}
+ @Nullable
public BookingTime getLatestBookingTime() {
return latestBookingTime;
}
+ @Nullable
public Duration getMinimumBookingNotice() {
return minimumBookingNotice;
}
+ @Nullable
public Duration getMaximumBookingNotice() {
return maximumBookingNotice;
}
+ @Nullable
public String getMessage() {
return message;
}
+ @Nullable
public String getPickupMessage() {
return pickupMessage;
}
+ @Nullable
public String getDropOffMessage() {
return dropOffMessage;
}
+
+ @Override
+ public String toString() {
+ return ToStringBuilder
+ .of(BookingInfo.class)
+ .addObj("contactInfo", contactInfo)
+ .addObj("bookingMethods", bookingMethods)
+ .addObj("earliestBookingTime", earliestBookingTime)
+ .addObj("latestBookingTime", latestBookingTime)
+ .addDuration("minimumBookingNotice", minimumBookingNotice)
+ .addDuration("maximumBookingNotice", maximumBookingNotice)
+ .addStr("message", message)
+ .addStr("pickupMessage", pickupMessage)
+ .addStr("dropOffMessage", dropOffMessage)
+ .toString();
+ }
}
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingInfoBuilder.java b/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingInfoBuilder.java
new file mode 100644
index 00000000000..fd91f9e781a
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingInfoBuilder.java
@@ -0,0 +1,69 @@
+package org.opentripplanner.transit.model.timetable.booking;
+
+import java.time.Duration;
+import java.util.EnumSet;
+import org.opentripplanner.transit.model.organization.ContactInfo;
+
+public class BookingInfoBuilder {
+
+ ContactInfo contactInfo;
+ EnumSet bookingMethods;
+ BookingTime earliestBookingTime;
+ BookingTime latestBookingTime;
+ Duration minimumBookingNotice;
+ Duration maximumBookingNotice;
+ String message;
+ String pickupMessage;
+ String dropOffMessage;
+
+ BookingInfoBuilder() {}
+
+ public BookingInfoBuilder withContactInfo(ContactInfo contactInfo) {
+ this.contactInfo = contactInfo;
+ return this;
+ }
+
+ public BookingInfoBuilder withBookingMethods(EnumSet bookingMethods) {
+ this.bookingMethods = bookingMethods;
+ return this;
+ }
+
+ public BookingInfoBuilder withEarliestBookingTime(BookingTime earliestBookingTime) {
+ this.earliestBookingTime = earliestBookingTime;
+ return this;
+ }
+
+ public BookingInfoBuilder withLatestBookingTime(BookingTime latestBookingTime) {
+ this.latestBookingTime = latestBookingTime;
+ return this;
+ }
+
+ public BookingInfoBuilder withMinimumBookingNotice(Duration minimumBookingNotice) {
+ this.minimumBookingNotice = minimumBookingNotice;
+ return this;
+ }
+
+ public BookingInfoBuilder withMaximumBookingNotice(Duration maximumBookingNotice) {
+ this.maximumBookingNotice = maximumBookingNotice;
+ return this;
+ }
+
+ public BookingInfoBuilder withMessage(String message) {
+ this.message = message;
+ return this;
+ }
+
+ public BookingInfoBuilder withPickupMessage(String pickupMessage) {
+ this.pickupMessage = pickupMessage;
+ return this;
+ }
+
+ public BookingInfoBuilder withDropOffMessage(String dropOffMessage) {
+ this.dropOffMessage = dropOffMessage;
+ return this;
+ }
+
+ public BookingInfo build() {
+ return new BookingInfo(this);
+ }
+}
diff --git a/src/main/java/org/opentripplanner/model/BookingMethod.java b/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingMethod.java
similarity index 63%
rename from src/main/java/org/opentripplanner/model/BookingMethod.java
rename to src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingMethod.java
index ac788ec8741..78cd2372698 100644
--- a/src/main/java/org/opentripplanner/model/BookingMethod.java
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingMethod.java
@@ -1,4 +1,4 @@
-package org.opentripplanner.model;
+package org.opentripplanner.transit.model.timetable.booking;
public enum BookingMethod {
CALL_DRIVER,
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingTime.java b/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingTime.java
new file mode 100644
index 00000000000..8034fe07388
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/booking/BookingTime.java
@@ -0,0 +1,60 @@
+package org.opentripplanner.transit.model.timetable.booking;
+
+import java.io.Serializable;
+import java.time.LocalTime;
+import java.util.Objects;
+import org.opentripplanner.framework.time.TimeUtils;
+
+/**
+ * Represents either the earliest or latest time a trip can be booked relative to the departure day
+ * of the trip.
+ */
+public class BookingTime implements Serializable {
+
+ private final LocalTime time;
+
+ private final int daysPrior;
+
+ public BookingTime(LocalTime time, int daysPrior) {
+ this.time = time;
+ this.daysPrior = daysPrior;
+ }
+
+ public LocalTime getTime() {
+ return time;
+ }
+
+ public int getDaysPrior() {
+ return daysPrior;
+ }
+
+ /**
+ * Get the relative time of day, can be negative if the {@code daysPrior} is set. This method
+ * does account for DST changes within the relative time.
+ */
+ public int relativeTimeSeconds() {
+ return time.toSecondOfDay() - daysPrior * TimeUtils.ONE_DAY_SECONDS;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ BookingTime that = (BookingTime) o;
+ return daysPrior == that.daysPrior && Objects.equals(time, that.time);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(time, daysPrior);
+ }
+
+ @Override
+ public String toString() {
+ return daysPrior == 0 ? time.toString() : time.toString() + "-" + daysPrior + "d";
+ }
+}
diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/booking/RoutingBookingInfo.java b/src/main/java/org/opentripplanner/transit/model/timetable/booking/RoutingBookingInfo.java
new file mode 100644
index 00000000000..8dad53a0bde
--- /dev/null
+++ b/src/main/java/org/opentripplanner/transit/model/timetable/booking/RoutingBookingInfo.java
@@ -0,0 +1,185 @@
+package org.opentripplanner.transit.model.timetable.booking;
+
+import java.time.Duration;
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.opentripplanner.framework.tostring.ToStringBuilder;
+
+/**
+ * This is the contract between booking info and the router. The router will enforce
+ * this information if the request sets the earliest-booking-time request parameter.
+ *
+ * Either {@code latestBookingTime} and {@code minimumBookingNotice} must be set to
+ * an actual value, both can not be set to {@NOT_SET} simultaneously.
+ *
+ * This class is not used by Raptor directly, but used by the BookingTimeAccessEgress which
+ * implements the RaptorAccessEgress interface.
+ */
+public final class RoutingBookingInfo {
+
+ public static final int NOT_SET = -1_999_999;
+ private static final RoutingBookingInfo UNRESTRICTED = new RoutingBookingInfo();
+
+ private final int requestedBookingTime;
+ private final int latestBookingTime;
+ private final int minimumBookingNotice;
+
+ /** Unrestricted booking info. */
+ private RoutingBookingInfo() {
+ this.requestedBookingTime = NOT_SET;
+ this.latestBookingTime = NOT_SET;
+ this.minimumBookingNotice = NOT_SET;
+ }
+
+ private RoutingBookingInfo(
+ int requestedBookingTime,
+ int latestBookingTime,
+ int minimumBookingNotice
+ ) {
+ if (notSet(requestedBookingTime)) {
+ throw new IllegalArgumentException("The requestedBookingTime must be set.");
+ }
+ if (notSet(latestBookingTime) && notSet(minimumBookingNotice)) {
+ throw new IllegalArgumentException(
+ "At least latestBookingTime or minimumBookingNotice must be set."
+ );
+ }
+ this.requestedBookingTime = requestedBookingTime;
+ this.latestBookingTime = latestBookingTime;
+ this.minimumBookingNotice = minimumBookingNotice;
+ }
+
+ public static RoutingBookingInfo.Builder of(int requestedBookingTime) {
+ return new Builder(requestedBookingTime);
+ }
+
+ public static RoutingBookingInfo of(int requestedBookingTime, @Nullable BookingInfo bookingInfo) {
+ return of(requestedBookingTime).withBookingInfo(bookingInfo).build();
+ }
+
+ /**
+ * Return an instance without any booking restrictions.
+ */
+ public static RoutingBookingInfo unrestricted() {
+ return UNRESTRICTED;
+ }
+
+ /**
+ * Time-shift departureTime if the minimum-booking-notice requires it. If required, the
+ * new time-shifted departureTime is returned, if not the given {@code departureTime} is
+ * returned as is. For example, if a service is available between 12:00 and 15:00 and the
+ * minimum booking notice is 30 minutes, the first available trip at 13:00
+ * ({@code requestedBookingTime}) is 13:30.
+ */
+ public int earliestDepartureTime(int departureTime) {
+ return notSet(minimumBookingNotice)
+ ? departureTime
+ : Math.max(minBookingNoticeLimit(), departureTime);
+ }
+
+ /**
+ * Check if the given time is after (or eq to) the earliest time allowed according to the minimum
+ * booking notice.
+ */
+ public boolean exceedsMinimumBookingNotice(int departureTime) {
+ return exist(minimumBookingNotice) && departureTime < minBookingNoticeLimit();
+ }
+
+ public boolean exceedsLatestBookingTime() {
+ return exist(latestBookingTime) && requestedBookingTime > latestBookingTime;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ var other = (RoutingBookingInfo) o;
+ return (
+ Objects.equals(latestBookingTime, other.latestBookingTime) &&
+ Objects.equals(minimumBookingNotice, other.minimumBookingNotice)
+ );
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(latestBookingTime, minimumBookingNotice);
+ }
+
+ @Override
+ public String toString() {
+ return ToStringBuilder
+ .of(RoutingBookingInfo.class)
+ .addServiceTime("latestBookingTime", latestBookingTime, NOT_SET)
+ .addDurationSec("minimumBookingNotice", minimumBookingNotice, NOT_SET)
+ .toString();
+ }
+
+ private int minBookingNoticeLimit() {
+ return requestedBookingTime + minimumBookingNotice;
+ }
+
+ private static boolean exist(int value) {
+ return value != NOT_SET;
+ }
+
+ private static boolean notSet(int value) {
+ return value == NOT_SET;
+ }
+
+ public static class Builder {
+
+ private final int requestedBookingTime;
+ private int latestBookingTime;
+ private int minimumBookingNotice;
+
+ public Builder(int requestedBookingTime) {
+ this.requestedBookingTime = requestedBookingTime;
+ setUnrestricted();
+ }
+
+ /**
+ * Convenience method to add booking info to builder.
+ */
+ Builder withBookingInfo(@Nullable BookingInfo bookingInfo) {
+ // Clear booking
+ if (bookingInfo == null) {
+ setUnrestricted();
+ return this;
+ }
+ withLatestBookingTime(bookingInfo.getLatestBookingTime());
+ withMinimumBookingNotice(bookingInfo.getMinimumBookingNotice());
+ return this;
+ }
+
+ public Builder withLatestBookingTime(BookingTime latestBookingTime) {
+ this.latestBookingTime =
+ latestBookingTime == null ? NOT_SET : latestBookingTime.relativeTimeSeconds();
+ return this;
+ }
+
+ public Builder withMinimumBookingNotice(Duration minimumBookingNotice) {
+ this.minimumBookingNotice =
+ minimumBookingNotice == null ? NOT_SET : (int) minimumBookingNotice.toSeconds();
+ return this;
+ }
+
+ public RoutingBookingInfo build() {
+ if (notSet(requestedBookingTime)) {
+ return unrestricted();
+ }
+ if (notSet(latestBookingTime) && notSet(minimumBookingNotice)) {
+ return RoutingBookingInfo.unrestricted();
+ }
+ return new RoutingBookingInfo(requestedBookingTime, latestBookingTime, minimumBookingNotice);
+ }
+
+ private void setUnrestricted() {
+ latestBookingTime = NOT_SET;
+ minimumBookingNotice = NOT_SET;
+ }
+ }
+}
diff --git a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql
index e6f4208c643..426de70ebcc 100644
--- a/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql
+++ b/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql
@@ -792,7 +792,16 @@ type QueryType {
boardSlackDefault: Int = 0,
"List of boardSlack for a given set of modes. Defaults: []"
boardSlackList: [TransportModeSlack],
- "Date and time for the earliest time the user is willing to start the journey (if arriveBy=false/not set) or the latest acceptable time of arriving (arriveBy=true). Defaults to now"
+ """
+ The date and time for the latest time the user is expected to book the journey.
+ Normally this is when the search is performed (now), plus a small grace period to
+ complete the booking. Services which must be booked before this time is excluded. The
+ `latestBookingTime` and `minimumBookingPeriod` in `BookingArrangement` (flexible
+ services only) is used to enforce this. If this parameter is _not set_, no booking-time
+ restrictions are applied - all journeys are listed.
+ """
+ bookingTime: DateTime,
+ "The date and time for the earliest time the user is willing to start the journey (if `false` or not set) or the latest acceptable time of arriving (`true`). Defaults to now."
dateTime: DateTime,
"Debug the itinerary-filter-chain. OTP will attach a system notice to itineraries instead of removing them. This is very convenient when tuning the filters."
debugItineraryFilter: Boolean = false @deprecated(reason : "Use `itineraryFilter.debug` instead."),
diff --git a/src/test/java/org/opentripplanner/TestServerContext.java b/src/test/java/org/opentripplanner/TestServerContext.java
index bf6a8d2bbd3..2f4ded1121d 100644
--- a/src/test/java/org/opentripplanner/TestServerContext.java
+++ b/src/test/java/org/opentripplanner/TestServerContext.java
@@ -51,7 +51,7 @@ public static OtpServerRequestContext createServerContext(
createRealtimeVehicleService(transitService),
createVehicleRentalService(),
createEmissionsService(),
- routerConfig.flexConfig(),
+ routerConfig.flexParameters(),
List.of(),
null,
createStreetLimitationParametersService(),
diff --git a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java
index 654c52ce912..de6b48c5ef4 100644
--- a/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java
+++ b/src/test/java/org/opentripplanner/apis/transmodel/mapping/TripRequestMapperTest.java
@@ -142,7 +142,7 @@ void setup() {
new DefaultRealtimeVehicleService(transitService),
new DefaultVehicleRentalService(),
new DefaultEmissionsService(new EmissionsDataModel()),
- RouterConfig.DEFAULT.flexConfig(),
+ RouterConfig.DEFAULT.flexParameters(),
List.of(),
null,
new DefaultStreetLimitationParametersService(new StreetLimitationParameters()),
diff --git a/src/test/java/org/opentripplanner/framework/time/TimeUtilsTest.java b/src/test/java/org/opentripplanner/framework/time/TimeUtilsTest.java
index 53a6e5d4be4..c2eb828cc21 100644
--- a/src/test/java/org/opentripplanner/framework/time/TimeUtilsTest.java
+++ b/src/test/java/org/opentripplanner/framework/time/TimeUtilsTest.java
@@ -3,6 +3,7 @@
import static java.time.ZoneOffset.UTC;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Month;
@@ -182,6 +183,30 @@ void testMsToString() {
assertEquals("-1.234s", TimeUtils.msToString(-1234));
}
+ @Test
+ void toTransitTimeSeconds() {
+ var timeZero = ZonedDateTime.of(
+ LocalDate.of(2024, Month.JANUARY, 15),
+ LocalTime.of(0, 0, 0),
+ ZoneIds.UTC
+ );
+ // otp zero is identical to time
+ assertEquals(
+ 0,
+ TimeUtils.toTransitTimeSeconds(timeZero, Instant.parse("2024-01-15T00:00:00Z"))
+ );
+ // Test positive offset - otp zero is 1h2m3s before time
+ assertEquals(
+ 3723,
+ TimeUtils.toTransitTimeSeconds(timeZero, Instant.parse("2024-01-15T01:02:03Z"))
+ );
+ // Test negative offset - otp zero is 30m after time
+ assertEquals(
+ -1800,
+ TimeUtils.toTransitTimeSeconds(timeZero, Instant.parse("2024-01-14T23:30:00Z"))
+ );
+ }
+
private static int time(int hour, int min, int sec) {
return 60 * (60 * hour + min) + sec;
}
diff --git a/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java b/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java
index 1d8d819b5cb..4756608f57d 100644
--- a/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java
+++ b/src/test/java/org/opentripplanner/graph_builder/module/OsmBoardingLocationsModuleTest.java
@@ -19,7 +19,7 @@
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.StreetEdge;
import org.opentripplanner.street.model.vertex.OsmBoardingLocationVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
+import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.model.vertex.VertexFactory;
import org.opentripplanner.street.model.vertex.VertexLabel;
@@ -74,7 +74,7 @@ void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVerti
var provider = new OsmProvider(file, false);
var floatingBusVertex = factory.transitStop(
- new TransitStopVertexBuilder().withStop(floatingBusStop).withModes(Set.of(TransitMode.BUS))
+ TransitStopVertex.of().withStop(floatingBusStop).withModes(Set.of(TransitMode.BUS))
);
var floatingBoardingLocation = factory.osmBoardingLocation(
floatingBusVertex.getCoordinate(),
@@ -91,10 +91,10 @@ void addAndLinkBoardingLocations(boolean areaVisibility, Set linkedVerti
osmModule.buildGraph();
var platformVertex = factory.transitStop(
- new TransitStopVertexBuilder().withStop(platform).withModes(Set.of(TransitMode.RAIL))
+ TransitStopVertex.of().withStop(platform).withModes(Set.of(TransitMode.RAIL))
);
var busVertex = factory.transitStop(
- new TransitStopVertexBuilder().withStop(busStop).withModes(Set.of(TransitMode.BUS))
+ TransitStopVertex.of().withStop(busStop).withModes(Set.of(TransitMode.BUS))
);
transitModel.index();
diff --git a/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java b/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java
index 4d2141eb38e..06b10575ef9 100644
--- a/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java
+++ b/src/test/java/org/opentripplanner/graph_builder/module/StreetLinkerModuleTest.java
@@ -21,7 +21,6 @@
import org.opentripplanner.street.model.edge.StreetTransitStopLink;
import org.opentripplanner.street.model.vertex.SplitterVertex;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
import org.opentripplanner.transit.model._data.TransitModelForTest;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.site.RegularStop;
@@ -68,7 +67,7 @@ void linkRegularStop() {
void linkFlexStop() {
OTPFeature.FlexRouting.testOn(() -> {
var model = new TestModel();
- var flexTrip = TransitModelForTest.of().unscheduledTrip(id("flex"), model.stop());
+ var flexTrip = TransitModelForTest.of().unscheduledTrip("flex", model.stop());
model.withFlexTrip(flexTrip);
var module = model.streetLinkerModule();
@@ -126,7 +125,7 @@ public TestModel() {
transitModel = new TransitModel(builder.build(), new Deduplicator());
- stopVertex = new TransitStopVertexBuilder().withStop(stop).build();
+ stopVertex = TransitStopVertex.of().withStop(stop).build();
graph.addVertex(stopVertex);
graph.hasStreets = true;
diff --git a/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/SubgraphOnlyFerryTest.java b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/SubgraphOnlyFerryTest.java
index 26aaf12896b..f069a518fc0 100644
--- a/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/SubgraphOnlyFerryTest.java
+++ b/src/test/java/org/opentripplanner/graph_builder/module/islandpruning/SubgraphOnlyFerryTest.java
@@ -6,7 +6,6 @@
import java.util.Set;
import org.junit.jupiter.api.Test;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
import org.opentripplanner.transit.model._data.TransitModelForTest;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.site.RegularStop;
@@ -19,7 +18,8 @@ class SubgraphOnlyFerryTest {
@Test
void subgraphHasOnlyFerry() {
- TransitStopVertex transitStopVertex = new TransitStopVertexBuilder()
+ TransitStopVertex transitStopVertex = TransitStopVertex
+ .of()
.withStop(regularStop1)
.withModes(Set.of(TransitMode.FERRY))
.build();
@@ -32,7 +32,8 @@ void subgraphHasOnlyFerry() {
@Test
void subgraphHasOnlyNoFerry() {
- TransitStopVertex transitStopVertex1 = new TransitStopVertexBuilder()
+ TransitStopVertex transitStopVertex1 = TransitStopVertex
+ .of()
.withStop(regularStop1)
.withModes(Set.of(TransitMode.BUS))
.build();
@@ -45,7 +46,8 @@ void subgraphHasOnlyNoFerry() {
@Test
void subgraphHasOnlyNoMode() {
- TransitStopVertex transitStopVertex1 = new TransitStopVertexBuilder()
+ TransitStopVertex transitStopVertex1 = TransitStopVertex
+ .of()
.withStop(regularStop1)
.withModes(Set.of())
.build();
@@ -58,11 +60,13 @@ void subgraphHasOnlyNoMode() {
@Test
void subgraphHasOnlyFerryMoreStops() {
- TransitStopVertex transitStopVertex1 = new TransitStopVertexBuilder()
+ TransitStopVertex transitStopVertex1 = TransitStopVertex
+ .of()
.withStop(regularStop1)
.withModes(Set.of(TransitMode.FERRY))
.build();
- TransitStopVertex transitStopVertex2 = new TransitStopVertexBuilder()
+ TransitStopVertex transitStopVertex2 = TransitStopVertex
+ .of()
.withStop(regularStop2)
.withModes(Set.of(TransitMode.FERRY))
.build();
@@ -76,11 +80,13 @@ void subgraphHasOnlyFerryMoreStops() {
@Test
void subgraphHasNotOnlyFerryMoreStops() {
- TransitStopVertex transitStopVertex1 = new TransitStopVertexBuilder()
+ TransitStopVertex transitStopVertex1 = TransitStopVertex
+ .of()
.withStop(regularStop1)
.withModes(Set.of(TransitMode.FERRY))
.build();
- TransitStopVertex transitStopVertex2 = new TransitStopVertexBuilder()
+ TransitStopVertex transitStopVertex2 = TransitStopVertex
+ .of()
.withStop(regularStop2)
.withModes(Set.of(TransitMode.BUS))
.build();
@@ -94,11 +100,13 @@ void subgraphHasNotOnlyFerryMoreStops() {
@Test
void subgraphHasNoModeMoreStops() {
- TransitStopVertex transitStopVertex1 = new TransitStopVertexBuilder()
+ TransitStopVertex transitStopVertex1 = TransitStopVertex
+ .of()
.withStop(regularStop1)
.withModes(Set.of(TransitMode.FERRY))
.build();
- TransitStopVertex transitStopVertex2 = new TransitStopVertexBuilder()
+ TransitStopVertex transitStopVertex2 = TransitStopVertex
+ .of()
.withStop(regularStop2)
.withModes(Set.of())
.build();
diff --git a/src/test/java/org/opentripplanner/graph_builder/module/linking/TestGraph.java b/src/test/java/org/opentripplanner/graph_builder/module/linking/TestGraph.java
index e7ac5e16a68..a45b8eef239 100644
--- a/src/test/java/org/opentripplanner/graph_builder/module/linking/TestGraph.java
+++ b/src/test/java/org/opentripplanner/graph_builder/module/linking/TestGraph.java
@@ -6,7 +6,6 @@
import org.opentripplanner.routing.linking.VertexLinker;
import org.opentripplanner.street.model.edge.StreetTransitStopLink;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.TraverseModeSet;
import org.opentripplanner.transit.model._data.TransitModelForTest;
@@ -28,7 +27,7 @@ public static void addRegularStopGrid(Graph graph) {
for (double lon = -83.1341; lon < -82.8646; lon += 0.005) {
String id = Integer.toString(count++);
RegularStop stop = TEST_MODEL.stop(id).withCoordinate(lat, lon).build();
- graph.addVertex(new TransitStopVertexBuilder().withStop(stop).build());
+ graph.addVertex(TransitStopVertex.of().withStop(stop).build());
}
}
}
@@ -40,7 +39,7 @@ public static void addExtraStops(Graph graph) {
for (double lat = 40; lat < 40.01; lat += 0.005) {
String id = "EXTRA_" + count++;
RegularStop stop = TEST_MODEL.stop(id).withCoordinate(lat, lon).build();
- graph.addVertex(new TransitStopVertexBuilder().withStop(stop).build());
+ graph.addVertex(TransitStopVertex.of().withStop(stop).build());
}
// add some duplicate stops, identical to the regular stop grid
@@ -48,7 +47,7 @@ public static void addExtraStops(Graph graph) {
for (double lat = 39.9058; lat < 40.0281; lat += 0.005) {
String id = "DUPE_" + count++;
RegularStop stop = TEST_MODEL.stop(id).withCoordinate(lat, lon).build();
- graph.addVertex(new TransitStopVertexBuilder().withStop(stop).build());
+ graph.addVertex(TransitStopVertex.of().withStop(stop).build());
}
// add some almost duplicate stops
@@ -56,7 +55,7 @@ public static void addExtraStops(Graph graph) {
for (double lat = 39.9059; lat < 40.0281; lat += 0.005) {
String id = "ALMOST_" + count++;
RegularStop stop = TEST_MODEL.stop(id).withCoordinate(lat, lon).build();
- graph.addVertex(new TransitStopVertexBuilder().withStop(stop).build());
+ graph.addVertex(TransitStopVertex.of().withStop(stop).build());
}
}
diff --git a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java
index dafcc639ceb..28be5b3a7e2 100644
--- a/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java
+++ b/src/test/java/org/opentripplanner/model/plan/TestItineraryBuilder.java
@@ -8,18 +8,15 @@
import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
import static org.opentripplanner.transit.model._data.TransitModelForTest.route;
-import gnu.trove.set.hash.TIntHashSet;
import java.time.Duration;
import java.time.LocalDate;
import java.time.Month;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
-import org.opentripplanner.ext.flex.FlexServiceDate;
import org.opentripplanner.ext.flex.FlexibleTransitLeg;
import org.opentripplanner.ext.flex.edgetype.FlexTripEdge;
import org.opentripplanner.ext.flex.flexpathcalculator.DirectFlexPathCalculator;
-import org.opentripplanner.ext.flex.template.FlexAccessTemplate;
import org.opentripplanner.ext.flex.trip.UnscheduledTrip;
import org.opentripplanner.ext.ridehailing.model.RideEstimate;
import org.opentripplanner.ext.ridehailing.model.RideHailingLeg;
@@ -30,7 +27,6 @@
import org.opentripplanner.model.StopTime;
import org.opentripplanner.model.transfer.ConstrainedTransfer;
import org.opentripplanner.model.transfer.TransferConstraint;
-import org.opentripplanner.standalone.config.sandbox.FlexConfig;
import org.opentripplanner.street.model._data.StreetModelForTest;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.transit.model._data.TransitModelForTest;
@@ -212,16 +208,9 @@ public TestItineraryBuilder flex(int start, int end, Place to) {
.withTrip(trip)
.build();
- var template = new FlexAccessTemplate(
- null,
- flexTrip,
- 0,
- 1,
- null,
- new FlexServiceDate(LocalDate.now(), 0, new TIntHashSet()),
- new DirectFlexPathCalculator(),
- FlexConfig.DEFAULT
- );
+ int fromStopPos = 0;
+ int toStopPos = 1;
+ LocalDate serviceDate = LocalDate.of(2024, Month.MAY, 22);
var fromv = StreetModelForTest.intersectionVertex(
"v1",
@@ -235,15 +224,17 @@ public TestItineraryBuilder flex(int start, int end, Place to) {
);
var flexPath = new DirectFlexPathCalculator()
- .calculateFlexPath(fromv, tov, template.fromStopIndex, template.toStopIndex);
+ .calculateFlexPath(fromv, tov, fromStopPos, toStopPos);
- var edge = FlexTripEdge.createFlexTripEdge(
+ var edge = new FlexTripEdge(
fromv,
tov,
lastPlace.stop,
to.stop,
flexTrip,
- template,
+ fromStopPos,
+ toStopPos,
+ serviceDate,
flexPath
);
diff --git a/src/test/java/org/opentripplanner/netex/mapping/BookingInfoMapperTest.java b/src/test/java/org/opentripplanner/netex/mapping/BookingInfoMapperTest.java
index b69cb2e9bad..e561a6155d3 100644
--- a/src/test/java/org/opentripplanner/netex/mapping/BookingInfoMapperTest.java
+++ b/src/test/java/org/opentripplanner/netex/mapping/BookingInfoMapperTest.java
@@ -7,7 +7,7 @@
import java.time.LocalTime;
import org.junit.jupiter.api.Test;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
-import org.opentripplanner.model.BookingInfo;
+import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
import org.rutebanken.netex.model.BookingArrangementsStructure;
import org.rutebanken.netex.model.ContactStructure;
import org.rutebanken.netex.model.FlexibleLine;
diff --git a/src/test/java/org/opentripplanner/routing/TestHalfEdges.java b/src/test/java/org/opentripplanner/routing/TestHalfEdges.java
index 13785564074..ba7e024e57a 100644
--- a/src/test/java/org/opentripplanner/routing/TestHalfEdges.java
+++ b/src/test/java/org/opentripplanner/routing/TestHalfEdges.java
@@ -40,7 +40,6 @@
import org.opentripplanner.street.model.vertex.IntersectionVertex;
import org.opentripplanner.street.model.vertex.TemporaryStreetLocation;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.model.vertex.VertexFactory;
import org.opentripplanner.street.search.StreetSearchBuilder;
@@ -165,8 +164,8 @@ public void setUp() {
stopModelBuilder.withRegularStop(s1).withRegularStop(s2);
transitModel = new TransitModel(stopModelBuilder.build(), deduplicator);
- station1 = factory.transitStop(new TransitStopVertexBuilder().withStop(s1));
- station2 = factory.transitStop(new TransitStopVertexBuilder().withStop(s2));
+ station1 = factory.transitStop(TransitStopVertex.of().withStop(s1));
+ station2 = factory.transitStop(TransitStopVertex.of().withStop(s2));
station1.addMode(TransitMode.RAIL);
station2.addMode(TransitMode.RAIL);
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java b/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java
index 2c8847748ed..69e2ddaa944 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/GraphRoutingTest.java
@@ -40,7 +40,6 @@
import org.opentripplanner.street.model.vertex.TemporaryVertex;
import org.opentripplanner.street.model.vertex.TransitEntranceVertex;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
import org.opentripplanner.street.model.vertex.VehicleParkingEntranceVertex;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.model.vertex.VertexFactory;
@@ -253,7 +252,7 @@ public TransitStopVertex stop(
boolean noTransfers
) {
return vertexFactory.transitStop(
- new TransitStopVertexBuilder().withStop(stopEntity(id, latitude, longitude, noTransfers))
+ TransitStopVertex.of().withStop(stopEntity(id, latitude, longitude, noTransfers))
);
}
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java
index 8c0710d7db6..5da127de2ba 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapperTest.java
@@ -17,6 +17,7 @@
import org.junit.jupiter.params.provider.ValueSource;
import org.opentripplanner._support.time.ZoneIds;
import org.opentripplanner.ext.flex.FlexAccessEgress;
+import org.opentripplanner.ext.flex.FlexPathDurations;
import org.opentripplanner.framework.model.Cost;
import org.opentripplanner.framework.model.TimeAndCost;
import org.opentripplanner.framework.time.TimeUtils;
@@ -57,6 +58,7 @@
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.site.RegularStop;
+import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;
import org.opentripplanner.transit.service.DefaultTransitService;
import org.opentripplanner.transit.service.TransitModel;
@@ -142,8 +144,23 @@ void penalty() {
void createItineraryWithOnBoardFlexAccess() {
RaptorPathToItineraryMapper mapper = getRaptorPathToItineraryMapper();
+ var flexTrip = TEST_MODEL.unscheduledTrip(
+ "flex",
+ TEST_MODEL.stop("A:Stop:1").build(),
+ TEST_MODEL.stop("A:Stop:2").build()
+ );
+
State state = TestStateBuilder.ofWalking().streetEdge().streetEdge().build();
- FlexAccessEgress flexAccessEgress = new FlexAccessEgress(S1, null, 0, 1, null, state, true);
+ FlexAccessEgress flexAccessEgress = new FlexAccessEgress(
+ S1,
+ new FlexPathDurations(0, (int) state.getElapsedTimeSeconds(), 0, 0),
+ 0,
+ 1,
+ flexTrip,
+ state,
+ true,
+ RoutingBookingInfo.NOT_SET
+ );
RaptorAccessEgress access = new FlexAccessEgressAdapter(flexAccessEgress, false);
Transfer transfer = new Transfer(S2.getIndex(), 0);
RaptorTransfer raptorTransfer = new DefaultRaptorTransfer(S1.getIndex(), 0, 0, transfer);
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java
index 7d68c5bfeeb..78b12a97b82 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressPenaltyDecoratorTest.java
@@ -12,6 +12,7 @@
import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.framework.time.DurationUtils;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.routing.api.request.framework.TimeAndCostPenalty;
import org.opentripplanner.routing.api.request.framework.TimeAndCostPenaltyForEnum;
@@ -24,8 +25,8 @@ class AccessEgressPenaltyDecoratorTest {
private static final int DURATION_CAR_RENTAL = 45;
private static final int DURATION_WALKING = 135;
private static final Duration D10m = DurationUtils.duration("10m");
- private static final DefaultAccessEgress WALK = ofWalking(DURATION_WALKING);
- private static final DefaultAccessEgress CAR_RENTAL = ofCarRental(DURATION_CAR_RENTAL);
+ private static final RoutingAccessEgress WALK = ofWalking(DURATION_WALKING);
+ private static final RoutingAccessEgress CAR_RENTAL = ofCarRental(DURATION_CAR_RENTAL);
private static final TimeAndCostPenalty PENALTY = new TimeAndCostPenalty(
TimePenalty.of(D10m, 1.5),
2.0
@@ -33,10 +34,10 @@ class AccessEgressPenaltyDecoratorTest {
// We use the penalty to calculate the expected value, this is not pure, but the
// TimeAndCostPenalty is unit-tested elsewhere.
- private static final DefaultAccessEgress EXP_WALK_W_PENALTY = WALK.withPenalty(
+ private static final RoutingAccessEgress EXP_WALK_W_PENALTY = WALK.withPenalty(
PENALTY.calculate(DURATION_WALKING)
);
- private static final DefaultAccessEgress EXP_CAR_RENTAL_W_PENALTY = CAR_RENTAL.withPenalty(
+ private static final RoutingAccessEgress EXP_CAR_RENTAL_W_PENALTY = CAR_RENTAL.withPenalty(
PENALTY.calculate(DURATION_CAR_RENTAL)
);
@@ -60,7 +61,7 @@ private static List decorateCarRentalTestCase() {
@ParameterizedTest
@MethodSource("decorateCarRentalTestCase")
- void decorateCarRentalTest(List expected, List input) {
+ void decorateCarRentalTest(List expected, List input) {
var subject = new AccessEgressPenaltyDecorator(
StreetMode.CAR_RENTAL,
StreetMode.WALK,
@@ -81,7 +82,7 @@ private static List decorateWalkTestCase() {
@ParameterizedTest
@MethodSource("decorateWalkTestCase")
- void decorateWalkTest(List expected, List input) {
+ void decorateWalkTest(List expected, List input) {
var subject = new AccessEgressPenaltyDecorator(
StreetMode.CAR_RENTAL,
StreetMode.WALK,
@@ -111,18 +112,18 @@ void doNotDecorateAnyIfNoPenaltyIsSet() {
@Test
void filterEgress() {}
- private static DefaultAccessEgress ofCarRental(int duration) {
+ private static RoutingAccessEgress ofCarRental(int duration) {
return ofAccessEgress(
duration,
TestStateBuilder.ofCarRental().streetEdge().pickUpCarFromStation().build()
);
}
- private static DefaultAccessEgress ofWalking(int durationInSeconds) {
+ private static RoutingAccessEgress ofWalking(int durationInSeconds) {
return ofAccessEgress(durationInSeconds, TestStateBuilder.ofWalking().streetEdge().build());
}
- private static DefaultAccessEgress ofAccessEgress(int duration, State state) {
+ private static RoutingAccessEgress ofAccessEgress(int duration, State state) {
// We do NOT need to override #withPenalty(...), because all fields including
// 'durationInSeconds' is copied over using the getters.
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressesTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressesTest.java
index 1779155a69b..bd498e82f65 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressesTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/router/street/AccessEgressesTest.java
@@ -8,36 +8,37 @@
import org.opentripplanner.framework.model.Cost;
import org.opentripplanner.framework.model.TimeAndCost;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.DefaultAccessEgress;
+import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
import org.opentripplanner.street.search.state.TestStateBuilder;
class AccessEgressesTest {
public static final Duration D3m = Duration.ofMinutes(3);
public static final Duration D7m = Duration.ofMinutes(7);
- private static final DefaultAccessEgress ACCESS_A = new DefaultAccessEgress(
+ private static final RoutingAccessEgress ACCESS_A = new DefaultAccessEgress(
1,
TestStateBuilder.ofWalking().build()
)
.withPenalty(new TimeAndCost(D3m, Cost.ZERO));
- private static final DefaultAccessEgress ACCESS_B = new DefaultAccessEgress(
+ private static final RoutingAccessEgress ACCESS_B = new DefaultAccessEgress(
1,
TestStateBuilder.ofWalking().build()
)
.withPenalty(new TimeAndCost(D7m, Cost.ZERO));
- private static final DefaultAccessEgress ACCESS_C = new DefaultAccessEgress(
+ private static final RoutingAccessEgress ACCESS_C = new DefaultAccessEgress(
1,
TestStateBuilder.ofWalking().build()
);
- private static final List ACCESSES = List.of(ACCESS_A, ACCESS_B, ACCESS_C);
- private static final DefaultAccessEgress EGRESS_A = new DefaultAccessEgress(
+ private static final List ACCESSES = List.of(ACCESS_A, ACCESS_B, ACCESS_C);
+ private static final RoutingAccessEgress EGRESS_A = new DefaultAccessEgress(
1,
TestStateBuilder.ofWalking().build()
);
- private static final DefaultAccessEgress EGRESS_B = new DefaultAccessEgress(
+ private static final RoutingAccessEgress EGRESS_B = new DefaultAccessEgress(
1,
TestStateBuilder.ofWalking().build()
);
- private static final List EGRESSES = List.of(EGRESS_A, EGRESS_B);
+ private static final List EGRESSES = List.of(EGRESS_A, EGRESS_B);
private final AccessEgresses subject = new AccessEgresses(ACCESSES, EGRESSES);
diff --git a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java
index 92ed7aa4825..0fcb7c8cea3 100644
--- a/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java
+++ b/src/test/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgressTest.java
@@ -22,7 +22,7 @@ class DefaultAccessEgressTest {
public static final TimeAndCost PENALTY = new TimeAndCost(TIME_PENALTY, COST_PENALTY);
private final DefaultAccessEgress subject = new DefaultAccessEgress(STOP, LAST_STATE);
- private final DefaultAccessEgress subjectWithPenalty = subject.withPenalty(PENALTY);
+ private final RoutingAccessEgress subjectWithPenalty = subject.withPenalty(PENALTY);
@Test
void canNotAddPenaltyTwice() {
diff --git a/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java b/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java
new file mode 100644
index 00000000000..a81d7c39c5e
--- /dev/null
+++ b/src/test/java/org/opentripplanner/routing/graphfinder/NearbyStopTest.java
@@ -0,0 +1,29 @@
+package org.opentripplanner.routing.graphfinder;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.transit.model._data.TransitModelForTest;
+
+class NearbyStopTest {
+
+ private static TransitModelForTest MODEL = TransitModelForTest.of();
+
+ // TODO Add tests for all public methods in NearbyStop here
+
+ @Test
+ void testIsBetter() {
+ // We only test the distance here, since the compareTo method used should have a more complete
+ // unit-test including tests on state weight.
+ var a = new NearbyStop(MODEL.stop("A").build(), 20.0, null, null);
+ var b = new NearbyStop(MODEL.stop("A").build(), 30.0, null, null);
+
+ assertTrue(a.isBetter(b));
+ assertFalse(b.isBetter(a));
+
+ var sameDistance = new NearbyStop(MODEL.stop("A").build(), 20.0, null, null);
+ assertFalse(a.isBetter(sameDistance));
+ assertFalse(sameDistance.isBetter(a));
+ }
+}
diff --git a/src/test/java/org/opentripplanner/routing/linking/LinkStopToPlatformTest.java b/src/test/java/org/opentripplanner/routing/linking/LinkStopToPlatformTest.java
index e44ecf48f2f..23607d2e0b3 100644
--- a/src/test/java/org/opentripplanner/routing/linking/LinkStopToPlatformTest.java
+++ b/src/test/java/org/opentripplanner/routing/linking/LinkStopToPlatformTest.java
@@ -26,7 +26,6 @@
import org.opentripplanner.street.model.vertex.IntersectionVertex;
import org.opentripplanner.street.model.vertex.LabelledIntersectionVertex;
import org.opentripplanner.street.model.vertex.TransitStopVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.TraverseModeSet;
@@ -111,7 +110,7 @@ private Graph prepareTest(Coordinate[] platform, int[] visible, Coordinate[] sto
graph.index(transitModel.getStopModel());
for (RegularStop s : transitStops) {
- var v = new TransitStopVertexBuilder().withStop(s).build();
+ var v = TransitStopVertex.of().withStop(s).build();
graph.addVertex(v);
}
diff --git a/src/test/java/org/opentripplanner/standalone/config/sandbox/FlexConfigTest.java b/src/test/java/org/opentripplanner/standalone/config/sandbox/FlexConfigTest.java
deleted file mode 100644
index 2488fd626bf..00000000000
--- a/src/test/java/org/opentripplanner/standalone/config/sandbox/FlexConfigTest.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.opentripplanner.standalone.config.sandbox;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.junit.jupiter.api.Test;
-
-class FlexConfigTest {
-
- @Test
- void initializationOrder() {
- assertNotNull(FlexConfig.DEFAULT.maxTransferDuration());
- assertNotNull(FlexConfig.DEFAULT.maxFlexTripDuration());
- }
-}
diff --git a/src/test/java/org/opentripplanner/street/model/edge/StreetTransitEntityLinkTest.java b/src/test/java/org/opentripplanner/street/model/edge/StreetTransitEntityLinkTest.java
index 25234cce3f6..6ba4dea0216 100644
--- a/src/test/java/org/opentripplanner/street/model/edge/StreetTransitEntityLinkTest.java
+++ b/src/test/java/org/opentripplanner/street/model/edge/StreetTransitEntityLinkTest.java
@@ -21,7 +21,7 @@
import org.opentripplanner.routing.api.request.preference.WheelchairPreferences;
import org.opentripplanner.street.model._data.StreetModelForTest;
import org.opentripplanner.street.model.vertex.StreetVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
+import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.search.request.StreetSearchRequest;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.street.search.state.TestStateBuilder;
@@ -89,10 +89,7 @@ void unknownStop() {
private State[] traverse(RegularStop stop, boolean onlyAccessible) {
var from = StreetModelForTest.intersectionVertex("A", 10, 10);
- var to = new TransitStopVertexBuilder()
- .withStop(stop)
- .withModes(Set.of(TransitMode.RAIL))
- .build();
+ var to = TransitStopVertex.of().withStop(stop).withModes(Set.of(TransitMode.RAIL)).build();
var req = StreetSearchRequest.of().withMode(StreetMode.BIKE);
AccessibilityPreferences feature;
@@ -166,7 +163,7 @@ void stationBasedVehiclesAreNotAllowedIntoStops(State state) {
}
private void testTraversalWithState(State state, boolean canTraverse) {
- var transitStopVertex = new TransitStopVertexBuilder().withStop(ACCESSIBLE_STOP).build();
+ var transitStopVertex = TransitStopVertex.of().withStop(ACCESSIBLE_STOP).build();
var edge = StreetTransitStopLink.createStreetTransitStopLink(
(StreetVertex) state.getVertex(),
transitStopVertex
diff --git a/src/test/java/org/opentripplanner/street/model/vertex/OsmVertexTest.java b/src/test/java/org/opentripplanner/street/model/vertex/OsmVertexTest.java
index 0ec80117426..b3a2b0baebb 100644
--- a/src/test/java/org/opentripplanner/street/model/vertex/OsmVertexTest.java
+++ b/src/test/java/org/opentripplanner/street/model/vertex/OsmVertexTest.java
@@ -7,7 +7,6 @@
import java.util.List;
import javax.annotation.Nonnull;
import org.junit.jupiter.api.Test;
-import org.opentripplanner._support.geometry.Polygons;
import org.opentripplanner.transit.model._data.TransitModelForTest;
import org.opentripplanner.transit.model.site.AreaStop;
@@ -15,14 +14,8 @@ class OsmVertexTest {
private static final TransitModelForTest TEST_MODEL = TransitModelForTest.of();
- private static final AreaStop AREA_STOP1 = TEST_MODEL.areaStopForTest(
- "flex-zone-1",
- Polygons.BERLIN
- );
- private static final AreaStop AREA_STOP2 = TEST_MODEL.areaStopForTest(
- "flex-zone-2",
- Polygons.BERLIN
- );
+ private static final AreaStop AREA_STOP1 = TEST_MODEL.areaStop("flex-zone-1").build();
+ private static final AreaStop AREA_STOP2 = TEST_MODEL.areaStop("flex-zone-2").build();
@Test
void areaStops() {
diff --git a/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java b/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java
index a4dbf76e55e..e43c5a769d0 100644
--- a/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java
+++ b/src/test/java/org/opentripplanner/street/search/state/TestStateBuilder.java
@@ -32,7 +32,7 @@
import org.opentripplanner.street.model.vertex.ElevatorOffboardVertex;
import org.opentripplanner.street.model.vertex.ElevatorOnboardVertex;
import org.opentripplanner.street.model.vertex.StreetVertex;
-import org.opentripplanner.street.model.vertex.TransitStopVertexBuilder;
+import org.opentripplanner.street.model.vertex.TransitStopVertex;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.request.StreetSearchRequest;
import org.opentripplanner.transit.model._data.TransitModelForTest;
@@ -266,7 +266,7 @@ public TestStateBuilder pathway(String s) {
@Nonnull
private TestStateBuilder arriveAtStop(RegularStop stop) {
var from = (StreetVertex) currentState.vertex;
- var to = new TransitStopVertexBuilder().withStop(stop).build();
+ var to = TransitStopVertex.of().withStop(stop).build();
Edge edge;
if (currentState.getRequest().arriveBy()) {
diff --git a/src/test/java/org/opentripplanner/transit/model/TransitModelArchitectureTest.java b/src/test/java/org/opentripplanner/transit/model/TransitModelArchitectureTest.java
index 8976e29e293..11db763d97f 100644
--- a/src/test/java/org/opentripplanner/transit/model/TransitModelArchitectureTest.java
+++ b/src/test/java/org/opentripplanner/transit/model/TransitModelArchitectureTest.java
@@ -23,6 +23,7 @@ public class TransitModelArchitectureTest {
private static final Package NETWORK = TRANSIT_MODEL.subPackage("network");
private static final Package SITE = TRANSIT_MODEL.subPackage("site");
private static final Package TIMETABLE = TRANSIT_MODEL.subPackage("timetable");
+ private static final Package TIMETABLE_BOOKING = TIMETABLE.subPackage("booking");
private static final Package LEGACY_MODEL = OTP_ROOT.subPackage("model");
@Test
@@ -84,6 +85,7 @@ void enforceTimetablePackageDependencies() {
ORGANIZATION,
NETWORK,
SITE,
+ TIMETABLE_BOOKING,
LEGACY_MODEL
)
.verify();
diff --git a/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java b/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java
index 2a4f2dfb701..0f6d872c639 100644
--- a/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java
+++ b/src/test/java/org/opentripplanner/transit/model/_data/TransitModelForTest.java
@@ -5,8 +5,13 @@
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.IntStream;
-import org.locationtech.jts.geom.Geometry;
+import java.util.stream.Stream;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Polygon;
+import org.opentripplanner._support.geometry.Coordinates;
+import org.opentripplanner.ext.flex.trip.ScheduledDeviatedTrip;
import org.opentripplanner.ext.flex.trip.UnscheduledTrip;
+import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.framework.geometry.WgsCoordinate;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.i18n.NonLocalizedString;
@@ -23,7 +28,7 @@
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.network.TripPatternBuilder;
import org.opentripplanner.transit.model.organization.Agency;
-import org.opentripplanner.transit.model.site.AreaStop;
+import org.opentripplanner.transit.model.site.AreaStopBuilder;
import org.opentripplanner.transit.model.site.GroupStop;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.RegularStopBuilder;
@@ -52,6 +57,18 @@ public class TransitModelForTest {
public static final String OTHER_TIME_ZONE_ID = "America/Los_Angeles";
public static final WgsCoordinate ANY_COORDINATE = new WgsCoordinate(60.0, 10.0);
+ // This is used to create valid objects - do not use it for verification
+ private static final Polygon ANY_POLYGON = GeometryUtils
+ .getGeometryFactory()
+ .createPolygon(
+ new Coordinate[] {
+ Coordinates.of(61.0, 10.0),
+ Coordinates.of(61.0, 12.0),
+ Coordinates.of(60.0, 11.0),
+ Coordinates.of(61.0, 10.0),
+ }
+ );
+
public static final Agency AGENCY = Agency
.of(id("A1"))
.withName("Agency Test")
@@ -149,22 +166,21 @@ public StationBuilder station(String idAndName) {
.withPriority(StopTransferPriority.ALLOWED);
}
- public GroupStop groupStopForTest(String idAndName, List stops) {
+ public GroupStop groupStop(String idAndName, RegularStop... stops) {
var builder = stopModelBuilder
.groupStop(id(idAndName))
.withName(new NonLocalizedString(idAndName));
- stops.forEach(builder::addLocation);
+ Stream.of(stops).forEach(builder::addLocation);
return builder.build();
}
- public AreaStop areaStopForTest(String idAndName, Geometry geometry) {
+ public AreaStopBuilder areaStop(String idAndName) {
return stopModelBuilder
.areaStop(id(idAndName))
.withName(new NonLocalizedString(idAndName))
- .withGeometry(geometry)
- .build();
+ .withGeometry(ANY_POLYGON);
}
public StopTime stopTime(Trip trip, int seq) {
@@ -250,7 +266,7 @@ public TripPatternBuilder pattern(TransitMode mode) {
.withStopPattern(stopPattern(3));
}
- public UnscheduledTrip unscheduledTrip(FeedScopedId id, StopLocation... stops) {
+ public UnscheduledTrip unscheduledTrip(String id, StopLocation... stops) {
var stopTimes = Arrays
.stream(stops)
.map(s -> {
@@ -262,10 +278,22 @@ public UnscheduledTrip unscheduledTrip(FeedScopedId id, StopLocation... stops) {
return st;
})
.toList();
+ return unscheduledTrip(id, stopTimes);
+ }
+
+ public UnscheduledTrip unscheduledTrip(String id, List stopTimes) {
return UnscheduledTrip
- .of(id)
+ .of(id(id))
.withTrip(trip("flex-trip").build())
.withStopTimes(stopTimes)
.build();
}
+
+ public ScheduledDeviatedTrip scheduledDeviatedTrip(String id, StopTime... stopTimes) {
+ return ScheduledDeviatedTrip
+ .of(id(id))
+ .withTrip(trip("flex-trip").build())
+ .withStopTimes(Arrays.asList(stopTimes))
+ .build();
+ }
}
diff --git a/src/test/java/org/opentripplanner/transit/model/network/StopPatternTest.java b/src/test/java/org/opentripplanner/transit/model/network/StopPatternTest.java
index 1658505aa51..1bce2edf9dc 100644
--- a/src/test/java/org/opentripplanner/transit/model/network/StopPatternTest.java
+++ b/src/test/java/org/opentripplanner/transit/model/network/StopPatternTest.java
@@ -6,8 +6,6 @@
import java.util.List;
import org.junit.jupiter.api.Test;
-import org.locationtech.jts.geom.Coordinate;
-import org.opentripplanner.framework.geometry.GeometryUtils;
import org.opentripplanner.transit.model._data.TransitModelForTest;
import org.opentripplanner.transit.model.timetable.Trip;
@@ -23,22 +21,9 @@ void boardingAlightingConditions() {
var s3 = testModel.stop("3", 62.0, 11.0).build();
var s4 = testModel.stop("4", 62.1, 11.0).build();
- var s34 = testModel.groupStopForTest("3_4", List.of(s3, s4));
+ var s34 = testModel.groupStop("3_4", s3, s4);
- var areaStop = testModel.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),
- }
- )
- );
+ var areaStop = testModel.areaStop("area").build();
Trip t = TransitModelForTest.trip("trip").build();
diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/booking/BookingInfoTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/booking/BookingInfoTest.java
new file mode 100644
index 00000000000..0bf189a1c29
--- /dev/null
+++ b/src/test/java/org/opentripplanner/transit/model/timetable/booking/BookingInfoTest.java
@@ -0,0 +1,68 @@
+package org.opentripplanner.transit.model.timetable.booking;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.EnumSet;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.transit.model.organization.ContactInfo;
+
+class BookingInfoTest {
+
+ public static final String URL = "http://booking.otp.org";
+ public static final ContactInfo CONTACT = ContactInfo
+ .of()
+ .withBookingUrl(URL)
+ .withContactPerson("Jo Contact")
+ .build();
+ public static final EnumSet BOOKING_METHODS = EnumSet.of(
+ BookingMethod.CALL_DRIVER
+ );
+ public static final BookingTime BOOKING_TIME_NOON = new BookingTime(LocalTime.NOON, 0);
+
+ @Test
+ void testBookingInfoWithLatestBookingTime() {
+ var subject = BookingInfo
+ .of()
+ .withContactInfo(CONTACT)
+ .withBookingMethods(BOOKING_METHODS)
+ .withLatestBookingTime(BOOKING_TIME_NOON)
+ .withMessage("message")
+ .withPickupMessage("pickup")
+ .withDropOffMessage("dropoff")
+ .build();
+
+ assertEquals(CONTACT, subject.getContactInfo());
+ assertEquals(BOOKING_METHODS, subject.bookingMethods());
+ assertNull(subject.getEarliestBookingTime());
+ assertEquals(BOOKING_TIME_NOON, subject.getLatestBookingTime());
+ assertEquals("message", subject.getMessage());
+ assertEquals("pickup", subject.getPickupMessage());
+ assertEquals("dropoff", subject.getDropOffMessage());
+
+ assertEquals(
+ "BookingInfo{contactInfo: ContactInfo{contactPerson: 'Jo Contact', bookingUrl: 'http://booking.otp.org'}, bookingMethods: [CALL_DRIVER], latestBookingTime: 12:00, message: 'message', pickupMessage: 'pickup', dropOffMessage: 'dropoff'}",
+ subject.toString()
+ );
+ }
+
+ @Test
+ void testBookingInfoWithMinBookingNotice() {
+ Duration minimumBookingNotice = Duration.ofMinutes(45);
+ var subject = BookingInfo
+ .of()
+ .withBookingMethods(BOOKING_METHODS)
+ .withMinimumBookingNotice(minimumBookingNotice)
+ .build();
+
+ assertNull(subject.getLatestBookingTime());
+ assertEquals(minimumBookingNotice, subject.getMinimumBookingNotice());
+
+ assertEquals(
+ "BookingInfo{bookingMethods: [CALL_DRIVER], minimumBookingNotice: 45m}",
+ subject.toString()
+ );
+ }
+}
diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/booking/BookingTimeTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/booking/BookingTimeTest.java
new file mode 100644
index 00000000000..5036defdb88
--- /dev/null
+++ b/src/test/java/org/opentripplanner/transit/model/timetable/booking/BookingTimeTest.java
@@ -0,0 +1,53 @@
+package org.opentripplanner.transit.model.timetable.booking;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.time.LocalTime;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.framework.time.TimeUtils;
+
+class BookingTimeTest {
+
+ BookingTime noon = new BookingTime(LocalTime.NOON, 0);
+ BookingTime noonYesterday = new BookingTime(LocalTime.NOON, 1);
+ BookingTime midnight = new BookingTime(LocalTime.MIDNIGHT, 0);
+ BookingTime noon2 = new BookingTime(LocalTime.NOON, 0);
+
+ @Test
+ void equalsAndHashCode() {
+ assertEquals(noon, noon);
+ assertEquals(noon, noon2);
+ assertNotEquals(noon, noonYesterday);
+ assertNotEquals(noon, midnight);
+
+ assertEquals(noon.hashCode(), noon.hashCode());
+ assertEquals(noon.hashCode(), noon2.hashCode());
+ assertNotEquals(noon.hashCode(), noonYesterday.hashCode());
+ assertNotEquals(noon.hashCode(), midnight.hashCode());
+ }
+
+ @Test
+ void getTime() {
+ assertEquals(noon.getTime(), LocalTime.NOON);
+ }
+
+ @Test
+ void testToString() {
+ assertEquals("12:00", noon.toString());
+ assertEquals("12:00-1d", noonYesterday.toString());
+ }
+
+ @Test
+ void getDaysPrior() {
+ assertEquals(noon.getDaysPrior(), 0);
+ assertEquals(noonYesterday.getDaysPrior(), 1);
+ }
+
+ @Test
+ void relativeTimeSeconds() {
+ assertEquals(midnight.relativeTimeSeconds(), 0);
+ assertEquals(noon.relativeTimeSeconds(), TimeUtils.ONE_DAY_SECONDS / 2);
+ assertEquals(noonYesterday.relativeTimeSeconds(), -TimeUtils.ONE_DAY_SECONDS / 2);
+ }
+}
diff --git a/src/test/java/org/opentripplanner/transit/model/timetable/booking/RoutingBookingInfoTest.java b/src/test/java/org/opentripplanner/transit/model/timetable/booking/RoutingBookingInfoTest.java
new file mode 100644
index 00000000000..e0f507a7983
--- /dev/null
+++ b/src/test/java/org/opentripplanner/transit/model/timetable/booking/RoutingBookingInfoTest.java
@@ -0,0 +1,152 @@
+package org.opentripplanner.transit.model.timetable.booking;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import java.time.Duration;
+import java.time.LocalTime;
+import java.util.List;
+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.framework.time.TimeUtils;
+
+class RoutingBookingInfoTest {
+
+ private static final Duration MINIMUM_BOOKING_NOTICE_20m = Duration.ofMinutes(20);
+ private static final LocalTime T13_20 = LocalTime.of(13, 20);
+ private static final LocalTime T13_00 = LocalTime.of(13, 0);
+ private static final LocalTime T13_00_01 = LocalTime.of(13, 0, 1);
+ private static final LocalTime T13_40 = LocalTime.of(13, 40);
+ private static final LocalTime T13_40_01 = LocalTime.of(13, 40, 1);
+ private static final LocalTime T14_00 = LocalTime.of(14, 0);
+ private static final LocalTime LATEST_BOOKING_TIME_13_00 = T13_00;
+
+ static List testCase() {
+ // BOARD-TIME | REQUESTED-BOOKING-TIME | EXPECTED
+ return List.of(
+ // Test min-booking-notice <= 13:40 (14:00-20m)
+ Arguments.of(T14_00, T13_40, Expect.MIN_BOOKING_NOTICE),
+ Arguments.of(T14_00, T13_40_01, Expect.NONE),
+ // Test latest-booking-time <= 13_00
+ Arguments.of(T13_00, T13_00, Expect.LATEST_BOOKING_TIME),
+ Arguments.of(T13_00, T13_00_01, Expect.NONE),
+ // Combination of both
+ Arguments.of(T13_20, LocalTime.of(13, 0, 0), Expect.BOTH)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("testCase")
+ void isThereEnoughTimeToBookWithMinBookingTimeBeforeDeparture(
+ LocalTime searchTime,
+ LocalTime requestedBookingTime,
+ Expect expect
+ ) {
+ int searchTimeSec = searchTime.toSecondOfDay();
+
+ var subject = RoutingBookingInfo
+ .of(requestedBookingTime.toSecondOfDay())
+ .withMinimumBookingNotice(MINIMUM_BOOKING_NOTICE_20m)
+ .withLatestBookingTime(new BookingTime(LATEST_BOOKING_TIME_13_00, 0))
+ .build();
+
+ // Since we have not set a duration or offset, departure and arrival is the same
+ assertEquals(expect.latestBookingTime, !subject.exceedsLatestBookingTime());
+ assertEquals(expect.minBookingNotice, !subject.exceedsMinimumBookingNotice(searchTimeSec));
+ }
+
+ @Test
+ void earliestDepartureTime() {
+ int t11_35 = TimeUtils.time("11:35");
+ int t11_55 = TimeUtils.time("11:35") + (int) MINIMUM_BOOKING_NOTICE_20m.toSeconds();
+
+ var subject = RoutingBookingInfo
+ .of(t11_35)
+ .withMinimumBookingNotice(MINIMUM_BOOKING_NOTICE_20m)
+ .build();
+
+ // 11:55 is the earliest departure time for any time before 11:55
+ assertEquals(subject.earliestDepartureTime(0), t11_55);
+ assertEquals(subject.earliestDepartureTime(t11_55 - 1), t11_55);
+ assertEquals(subject.earliestDepartureTime(t11_55), t11_55);
+ assertEquals(subject.earliestDepartureTime(t11_55 + 1), t11_55 + 1);
+ }
+
+ @Test
+ void unrestricted() {
+ assertFalse(RoutingBookingInfo.unrestricted().exceedsMinimumBookingNotice(10_000_000));
+ assertFalse(RoutingBookingInfo.unrestricted().exceedsMinimumBookingNotice(0));
+ assertFalse(RoutingBookingInfo.unrestricted().exceedsLatestBookingTime());
+
+ assertSame(RoutingBookingInfo.unrestricted(), RoutingBookingInfo.of(3600).build());
+
+ assertSame(
+ RoutingBookingInfo.unrestricted(),
+ RoutingBookingInfo
+ .of(RoutingBookingInfo.NOT_SET)
+ .withMinimumBookingNotice(MINIMUM_BOOKING_NOTICE_20m)
+ .build()
+ );
+ assertSame(
+ RoutingBookingInfo.unrestricted(),
+ RoutingBookingInfo.of(T13_00.toSecondOfDay()).build()
+ );
+ }
+
+ @Test
+ void testToString() {
+ var subject = RoutingBookingInfo
+ .of(TimeUtils.time("11:35"))
+ .withMinimumBookingNotice(MINIMUM_BOOKING_NOTICE_20m)
+ .withLatestBookingTime(new BookingTime(LATEST_BOOKING_TIME_13_00, 0))
+ .build();
+
+ assertEquals(
+ "RoutingBookingInfo{latestBookingTime: 13:00, minimumBookingNotice: 20m}",
+ subject.toString()
+ );
+ }
+
+ @Test
+ void testEqAndHashCode() {
+ var subject = RoutingBookingInfo.of(
+ TimeUtils.time("11:35"),
+ BookingInfo.of().withMinimumBookingNotice(MINIMUM_BOOKING_NOTICE_20m).build()
+ );
+ var same = RoutingBookingInfo
+ .of(TimeUtils.time("11:35"))
+ .withMinimumBookingNotice(MINIMUM_BOOKING_NOTICE_20m)
+ .build();
+
+ // Equals
+ assertNotSame(subject, same);
+ assertEquals(subject, same);
+ assertEquals(true, subject.equals(subject));
+ assertNotEquals(subject, RoutingBookingInfo.unrestricted());
+ assertNotEquals(subject, "");
+
+ // HashCode
+ assertEquals(subject.hashCode(), same.hashCode());
+ assertNotEquals(subject.hashCode(), RoutingBookingInfo.unrestricted().hashCode());
+ }
+
+ enum Expect {
+ NONE(false, false),
+ MIN_BOOKING_NOTICE(true, false),
+ LATEST_BOOKING_TIME(false, true),
+ BOTH(true, true);
+
+ final boolean minBookingNotice;
+ final boolean latestBookingTime;
+
+ Expect(boolean minBookingNotice, boolean latestBookingTime) {
+ this.minBookingNotice = minBookingNotice;
+ this.latestBookingTime = latestBookingTime;
+ }
+ }
+}