Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/dev-2.x' into upstream-merge-2…
Browse files Browse the repository at this point in the history
…024-05-17
  • Loading branch information
leonardehrenfried committed May 17, 2024
2 parents 636b0d1 + ef02c90 commit 1e0dd3f
Show file tree
Hide file tree
Showing 27 changed files with 446 additions and 87 deletions.
15 changes: 13 additions & 2 deletions doc-templates/Flex.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,19 @@

To enable this turn on `FlexRouting` as a feature in `otp-config.json`.

The GTFS feeds should conform to the
[GTFS-Flex v2 draft PR](https://github.com/google/transit/pull/388)
The GTFS feeds must conform to the final, approved version of the draft which has been
merged into the [mainline specification](https://gtfs.org/schedule/reference/) in March 2024.

### Experimental features

This sandbox feature also has experimental support for the following fields:

- `safe_duration_factor`
- `safe_duration_offset`

These features are currently [undergoing specification](https://github.com/MobilityData/gtfs-flex/pull/79)
and their definition might change. OTP's implementation will be also be changed so be careful
when relying on this feature.

## Configuration

Expand Down
1 change: 1 addition & 0 deletions docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ based on merged pull requests. Search GitHub issues and pull requests for smalle
- Add prettier:write to docs and rephrase slightly [ci skip] [#5831](https://github.com/opentripplanner/OpenTripPlanner/pull/5831)
- Make naming of stopTransferCosts/stopBoardAlightCosts consistent [#5822](https://github.com/opentripplanner/OpenTripPlanner/pull/5822)
- Namer for applying street names to nearby sidewalks [#5774](https://github.com/opentripplanner/OpenTripPlanner/pull/5774)
- Implement GTFS Flex safe duration spec draft [#5796](https://github.com/opentripplanner/OpenTripPlanner/pull/5796)
[](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE)

## 2.5.0 (2024-03-13)
Expand Down
15 changes: 13 additions & 2 deletions docs/sandbox/Flex.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,19 @@

To enable this turn on `FlexRouting` as a feature in `otp-config.json`.

The GTFS feeds should conform to the
[GTFS-Flex v2 draft PR](https://github.com/google/transit/pull/388)
The GTFS feeds must conform to the final, approved version of the draft which has been
merged into the [mainline specification](https://gtfs.org/schedule/reference/) in March 2024.

### Experimental features

This sandbox feature also has experimental support for the following fields:

- `safe_duration_factor`
- `safe_duration_offset`

These features are currently [undergoing specification](https://github.com/MobilityData/gtfs-flex/pull/79)
and their definition might change. OTP's implementation will be also be changed so be careful
when relying on this feature.

## Configuration

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@


<properties>
<otp.serialization.version.id>149</otp.serialization.version.id>
<otp.serialization.version.id>150</otp.serialization.version.id>
<!-- Lib versions - keep list sorted on property name -->
<geotools.version>31.0</geotools.version>
<google.dagger.version>2.51.1</google.dagger.version>
Expand Down
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,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,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 TimePenaltyCalculatorTest {

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 TimePenaltyCalculator(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 TimePenaltyCalculator(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,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 noPenalty() {
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 withPenalty() {
var trip = UnscheduledTrip
.of(id("1"))
.withStopTimes(List.of(STOP_TIME))
.withTimePenalty(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
Expand Up @@ -3,6 +3,9 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area;
import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularArrival;
import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularDeparture;
import static org.opentripplanner.ext.flex.trip.UnscheduledTrip.isUnscheduledTrip;
import static org.opentripplanner.ext.flex.trip.UnscheduledTripTest.TestCase.tc;
import static org.opentripplanner.model.PickDrop.NONE;
Expand Down Expand Up @@ -48,11 +51,10 @@ 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.areaStopForTest("area", Polygons.BERLIN);

@Nested
class IsUnscheduledTrip {

Expand Down Expand Up @@ -661,35 +663,6 @@ private static String timeToString(int time) {
return TimeUtils.timeToStrCompact(time, MISSING_VALUE, "MISSING_VALUE");
}

private static StopTime area(String startTime, String endTime) {
return area(AREA_STOP, endTime, startTime);
}

@Nonnull
private static StopTime area(StopLocation areaStop, String endTime, String startTime) {
var stopTime = new StopTime();
stopTime.setStop(areaStop);
stopTime.setFlexWindowStart(TimeUtils.time(startTime));
stopTime.setFlexWindowEnd(TimeUtils.time(endTime));
return stopTime;
}

private static StopTime regularDeparture(String departureTime) {
return regularStopTime(MISSING_VALUE, TimeUtils.time(departureTime));
}

private static StopTime regularArrival(String arrivalTime) {
return regularStopTime(TimeUtils.time(arrivalTime), MISSING_VALUE);
}

private static StopTime regularStopTime(int arrivalTime, int departureTime) {
var stopTime = new StopTime();
stopTime.setStop(REGULAR_STOP);
stopTime.setArrivalTime(arrivalTime);
stopTime.setDepartureTime(departureTime);
return stopTime;
}

@Nonnull
private static NearbyStop nearbyStop(AreaStop stop) {
return new NearbyStop(stop, 1000, List.of(), null);
Expand Down
13 changes: 9 additions & 4 deletions src/ext/java/org/opentripplanner/ext/flex/FlexTripsMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,12 +33,16 @@ public class FlexTripsMapper {
ProgressTracker progress = ProgressTracker.track("Create flex trips", 500, tripSize);

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));

var stopTimes = stopTimesByTrip.get(trip);
if (UnscheduledTrip.isUnscheduledTrip(stopTimes)) {
var timePenalty = builder.getFlexTimePenalty().getOrDefault(trip, TimePenalty.NONE);
result.add(
UnscheduledTrip.of(trip.getId()).withTrip(trip).withStopTimes(stopTimes).build()
UnscheduledTrip
.of(trip.getId())
.withTrip(trip)
.withStopTimes(stopTimes)
.withTimePenalty(timePenalty)
.build()
);
} else if (ScheduledDeviatedTrip.isScheduledFlexTrip(stopTimes)) {
result.add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ public int getGeneralizedCost() {
return generalizedCost;
}

@Override
public void addAlert(TransitAlert alert) {
transitAlerts.add(alert);
}
Expand Down
Loading

0 comments on commit 1e0dd3f

Please sign in to comment.