-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Implement GTFS Flex safe duration spec draft #5796
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 20 commits
b1ed4ff
95527bb
ea0960a
a0eb066
cc76012
dca331b
721845e
abcfa93
5066052
90b15cd
41b2e50
dec30cd
354c968
991406e
6c7d953
165e8cf
355b287
90148e0
a4dc25b
42b36bf
ea67975
695161a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package org.opentripplanner.ext.flex; | ||
|
|
||
| import static org.opentripplanner.model.StopTime.MISSING_VALUE; | ||
|
|
||
| import org.opentripplanner._support.geometry.Polygons; | ||
| import org.opentripplanner.framework.time.TimeUtils; | ||
| import org.opentripplanner.model.StopTime; | ||
| import org.opentripplanner.transit.model._data.TransitModelForTest; | ||
| import org.opentripplanner.transit.model.site.RegularStop; | ||
| import org.opentripplanner.transit.model.site.StopLocation; | ||
|
|
||
| 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 RegularStop REGULAR_STOP = TEST_MODEL.stop("stop").build(); | ||
|
|
||
| public static StopTime area(String startTime, String endTime) { | ||
| return area(AREA_STOP, endTime, startTime); | ||
| } | ||
|
|
||
| public 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; | ||
| } | ||
|
|
||
| public static StopTime regularArrival(String arrivalTime) { | ||
| return regularStopTime(TimeUtils.time(arrivalTime), MISSING_VALUE); | ||
| } | ||
|
|
||
| public static StopTime regularStopTime(String arrivalTime, String departureTime) { | ||
| return regularStopTime(TimeUtils.time(arrivalTime), TimeUtils.time(departureTime)); | ||
| } | ||
|
|
||
| public static StopTime regularStopTime(int arrivalTime, int departureTime) { | ||
| var stopTime = new StopTime(); | ||
| stopTime.setStop(REGULAR_STOP); | ||
| stopTime.setArrivalTime(arrivalTime); | ||
| stopTime.setDepartureTime(departureTime); | ||
| return stopTime; | ||
| } | ||
|
|
||
| public static StopTime regularDeparture(String departureTime) { | ||
| return regularStopTime(MISSING_VALUE, TimeUtils.time(departureTime)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package org.opentripplanner.ext.flex.flexpathcalculator; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertNull; | ||
|
|
||
| import java.time.Duration; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.opentripplanner._support.geometry.LineStrings; | ||
| import org.opentripplanner.routing.api.request.framework.TimePenalty; | ||
| import org.opentripplanner.street.model._data.StreetModelForTest; | ||
|
|
||
| class DurationModifierCalculatorTest { | ||
|
|
||
| private static final int THIRTY_MINS_IN_SECONDS = (int) Duration.ofMinutes(30).toSeconds(); | ||
|
|
||
| @Test | ||
| void calculate() { | ||
| FlexPathCalculator delegate = (fromv, tov, fromStopIndex, toStopIndex) -> | ||
| new FlexPath(10_000, THIRTY_MINS_IN_SECONDS, () -> LineStrings.SIMPLE); | ||
|
|
||
| var mod = TimePenalty.of(Duration.ofMinutes(10), 1.5f); | ||
| var calc = new DurationModifierCalculator(delegate, mod); | ||
| var path = calc.calculateFlexPath(StreetModelForTest.V1, StreetModelForTest.V2, 0, 5); | ||
| assertEquals(3300, path.durationSeconds); | ||
| } | ||
|
|
||
| @Test | ||
| void nullValue() { | ||
| FlexPathCalculator delegate = (fromv, tov, fromStopIndex, toStopIndex) -> null; | ||
| var mod = TimePenalty.of(Duration.ofMinutes(10), 1.5f); | ||
| var calc = new DurationModifierCalculator(delegate, mod); | ||
| var path = calc.calculateFlexPath(StreetModelForTest.V1, StreetModelForTest.V2, 0, 5); | ||
| assertNull(path); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package org.opentripplanner.ext.flex.flexpathcalculator; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.List; | ||
| import org.junit.jupiter.params.ParameterizedTest; | ||
| import org.junit.jupiter.params.provider.Arguments; | ||
| import org.junit.jupiter.params.provider.MethodSource; | ||
| import org.opentripplanner._support.geometry.LineStrings; | ||
| import org.opentripplanner.routing.api.request.framework.TimePenalty; | ||
|
|
||
| class FlexPathTest { | ||
|
|
||
| private static final int THIRTY_MINS_IN_SECONDS = (int) Duration.ofMinutes(30).toSeconds(); | ||
| private static final FlexPath PATH = new FlexPath( | ||
| 10_000, | ||
| THIRTY_MINS_IN_SECONDS, | ||
| () -> LineStrings.SIMPLE | ||
| ); | ||
|
|
||
| static List<Arguments> cases() { | ||
| return List.of( | ||
| Arguments.of(TimePenalty.ZERO, THIRTY_MINS_IN_SECONDS), | ||
| Arguments.of(TimePenalty.of(Duration.ofMinutes(10), 1), 2400), | ||
| Arguments.of(TimePenalty.of(Duration.ofMinutes(10), 1.5f), 3300), | ||
| Arguments.of(TimePenalty.of(Duration.ZERO, 3), 5400) | ||
| ); | ||
| } | ||
|
|
||
| @ParameterizedTest | ||
| @MethodSource("cases") | ||
| void calculate(TimePenalty mod, int expectedSeconds) { | ||
| var modified = PATH.withDurationModifier(mod); | ||
| assertEquals(expectedSeconds, modified.durationSeconds); | ||
| assertEquals(LineStrings.SIMPLE, modified.getGeometry()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package org.opentripplanner.ext.flex.flexpathcalculator; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area; | ||
| import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStopTime; | ||
| import static org.opentripplanner.street.model._data.StreetModelForTest.V1; | ||
| import static org.opentripplanner.street.model._data.StreetModelForTest.V2; | ||
| import static org.opentripplanner.transit.model._data.TransitModelForTest.id; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.List; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.opentripplanner._support.geometry.LineStrings; | ||
| import org.opentripplanner.ext.flex.trip.ScheduledDeviatedTrip; | ||
|
|
||
| class ScheduledFlexPathCalculatorTest { | ||
|
|
||
| private static final ScheduledDeviatedTrip TRIP = ScheduledDeviatedTrip | ||
| .of(id("123")) | ||
| .withStopTimes( | ||
| List.of( | ||
| regularStopTime("10:00", "10:01"), | ||
| area("10:10", "10:20"), | ||
| regularStopTime("10:25", "10:26"), | ||
| area("10:40", "10:50") | ||
| ) | ||
| ) | ||
| .build(); | ||
|
|
||
| @Test | ||
| void calculateTime() { | ||
| var c = (FlexPathCalculator) (fromv, tov, fromStopIndex, toStopIndex) -> | ||
| 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); | ||
| assertEquals(Duration.ofMinutes(19), Duration.ofSeconds(path.durationSeconds)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package org.opentripplanner.ext.flex.trip; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.opentripplanner.street.model._data.StreetModelForTest.V1; | ||
| import static org.opentripplanner.street.model._data.StreetModelForTest.V2; | ||
| import static org.opentripplanner.transit.model._data.TransitModelForTest.id; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.List; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.opentripplanner._support.geometry.LineStrings; | ||
| import org.opentripplanner.ext.flex.FlexStopTimesForTest; | ||
| import org.opentripplanner.ext.flex.flexpathcalculator.FlexPath; | ||
| import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator; | ||
| import org.opentripplanner.model.StopTime; | ||
| import org.opentripplanner.routing.api.request.framework.TimePenalty; | ||
|
|
||
| class UnscheduledDrivingDurationTest { | ||
|
|
||
| static final FlexPathCalculator STATIC_CALCULATOR = (fromv, tov, fromStopIndex, toStopIndex) -> | ||
| new FlexPath(10_000, (int) Duration.ofMinutes(10).toSeconds(), () -> LineStrings.SIMPLE); | ||
| private static final StopTime STOP_TIME = FlexStopTimesForTest.area("10:00", "18:00"); | ||
|
|
||
| @Test | ||
| void noModifier() { | ||
| var trip = UnscheduledTrip.of(id("1")).withStopTimes(List.of(STOP_TIME)).build(); | ||
|
|
||
| var calculator = trip.flexPathCalculator(STATIC_CALCULATOR); | ||
| var path = calculator.calculateFlexPath(V1, V2, 0, 0); | ||
| assertEquals(600, path.durationSeconds); | ||
| } | ||
|
|
||
| @Test | ||
| void withModifier() { | ||
| var trip = UnscheduledTrip | ||
| .of(id("1")) | ||
| .withStopTimes(List.of(STOP_TIME)) | ||
| .withDurationModifier(TimePenalty.of(Duration.ofMinutes(2), 1.5f)) | ||
| .build(); | ||
|
|
||
| var calculator = trip.flexPathCalculator(STATIC_CALCULATOR); | ||
| var path = calculator.calculateFlexPath(V1, V2, 0, 0); | ||
| assertEquals(1020, path.durationSeconds); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| import org.opentripplanner.model.StopTime; | ||
| import org.opentripplanner.model.TripStopTimes; | ||
| import org.opentripplanner.model.impl.OtpTransitServiceBuilder; | ||
| import org.opentripplanner.routing.api.request.framework.TimePenalty; | ||
| import org.opentripplanner.transit.model.timetable.Trip; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
@@ -34,10 +35,15 @@ public class FlexTripsMapper { | |
| for (Trip trip : stopTimesByTrip.keys()) { | ||
| /* Fetch the stop times for this trip. Copy the list since it's immutable. */ | ||
| List<StopTime> stopTimes = new ArrayList<>(stopTimesByTrip.get(trip)); | ||
|
|
||
| if (UnscheduledTrip.isUnscheduledTrip(stopTimes)) { | ||
| var modifier = builder.getFlexDurationFactors().getOrDefault(trip, TimePenalty.ZERO); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we stick to call to calling them modifiers instead of factors? Or is there some difference?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, sorry, I did several renames of this field and made a mess. Lets discuss in the dev meeting what this field should be called.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets use timePenalty here - modifier is too wide. Factor is not correct, it is more than a factor. |
||
| result.add( | ||
| UnscheduledTrip.of(trip.getId()).withTrip(trip).withStopTimes(stopTimes).build() | ||
| UnscheduledTrip | ||
| .of(trip.getId()) | ||
| .withTrip(trip) | ||
| .withStopTimes(stopTimes) | ||
| .withDurationModifier(modifier) | ||
| .build() | ||
| ); | ||
| } else if (ScheduledDeviatedTrip.isScheduledFlexTrip(stopTimes)) { | ||
| result.add( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package org.opentripplanner.ext.flex.flexpathcalculator; | ||
|
|
||
| import javax.annotation.Nullable; | ||
| import org.opentripplanner.routing.api.request.framework.TimePenalty; | ||
| import org.opentripplanner.street.model.vertex.Vertex; | ||
|
|
||
| /** | ||
| * A calculator to delegates the main computation to another instance and applies a duration | ||
| * modifier afterward. | ||
| */ | ||
| public class DurationModifierCalculator implements FlexPathCalculator { | ||
|
|
||
| private final FlexPathCalculator delegate; | ||
| private final TimePenalty factors; | ||
|
|
||
| public DurationModifierCalculator(FlexPathCalculator delegate, TimePenalty penalty) { | ||
| this.delegate = delegate; | ||
| this.factors = penalty; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. |
||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public FlexPath calculateFlexPath(Vertex fromv, Vertex tov, int fromStopIndex, int toStopIndex) { | ||
| var path = delegate.calculateFlexPath(fromv, tov, fromStopIndex, toStopIndex); | ||
|
|
||
| if (path == null) { | ||
| return null; | ||
| } else { | ||
| return path.withDurationModifier(factors); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.