Skip to content

Commit 1cfeadb

Browse files
Merge pull request #7062 from ibi-group/more-transfer-types
GTFS FaresV2 time limit types 2 and 3
2 parents 59786bb + fe6fb30 commit 1cfeadb

File tree

8 files changed

+226
-10
lines changed

8 files changed

+226
-10
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.opentripplanner.ext.fares.impl.gtfs;
2+
3+
import static java.time.Duration.ofHours;
4+
import static java.time.Duration.ofMinutes;
5+
import static org.junit.jupiter.api.Assertions.assertFalse;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
import static org.opentripplanner.ext.fares.model.TimeLimitType.ARRIVAL_TO_ARRIVAL;
8+
import static org.opentripplanner.ext.fares.model.TimeLimitType.ARRIVAL_TO_DEPARTURE;
9+
import static org.opentripplanner.ext.fares.model.TimeLimitType.DEPARTURE_TO_ARRIVAL;
10+
import static org.opentripplanner.ext.fares.model.TimeLimitType.DEPARTURE_TO_DEPARTURE;
11+
12+
import java.time.Duration;
13+
import java.util.List;
14+
import org.junit.jupiter.params.ParameterizedTest;
15+
import org.junit.jupiter.params.provider.Arguments;
16+
import org.junit.jupiter.params.provider.MethodSource;
17+
import org.opentripplanner.ext.fares.model.TimeLimit;
18+
import org.opentripplanner.ext.fares.model.TimeLimitType;
19+
import org.opentripplanner.model.plan.TestTransitLeg;
20+
import org.opentripplanner.model.plan.TransitLeg;
21+
22+
class TimeLimitEvaluatorTest {
23+
24+
private static final TransitLeg FIRST = TestTransitLeg.of()
25+
.withStartTime("10:00")
26+
.withEndTime("10:05")
27+
.build();
28+
private static final TransitLeg SECOND = TestTransitLeg.of()
29+
.withStartTime("10:10")
30+
.withEndTime("10:20")
31+
.build();
32+
33+
private static List<Arguments> withinLimitCases() {
34+
return List.of(
35+
Arguments.of(DEPARTURE_TO_ARRIVAL, ofMinutes(20)),
36+
Arguments.of(DEPARTURE_TO_ARRIVAL, ofHours(1)),
37+
Arguments.of(DEPARTURE_TO_DEPARTURE, ofMinutes(11)),
38+
Arguments.of(DEPARTURE_TO_DEPARTURE, ofHours(1)),
39+
Arguments.of(ARRIVAL_TO_ARRIVAL, ofMinutes(16)),
40+
Arguments.of(ARRIVAL_TO_DEPARTURE, ofMinutes(5)),
41+
Arguments.of(ARRIVAL_TO_DEPARTURE, ofHours(1))
42+
);
43+
}
44+
45+
@ParameterizedTest
46+
@MethodSource("withinLimitCases")
47+
void withinLimit(TimeLimitType type, Duration duration) {
48+
var limit = new TimeLimit(type, duration);
49+
assertTrue(TimeLimitEvaluator.withinTimeLimit(limit, FIRST, SECOND));
50+
}
51+
52+
private static List<Arguments> outsideLimitCases() {
53+
return List.of(
54+
Arguments.of(DEPARTURE_TO_ARRIVAL, ofMinutes(20).minusSeconds(20)),
55+
Arguments.of(DEPARTURE_TO_DEPARTURE, ofMinutes(10).minusSeconds(1)),
56+
Arguments.of(ARRIVAL_TO_ARRIVAL, ofMinutes(15).minusSeconds(1)),
57+
Arguments.of(ARRIVAL_TO_DEPARTURE, ofMinutes(5).minusSeconds(1))
58+
);
59+
}
60+
61+
@ParameterizedTest
62+
@MethodSource("outsideLimitCases")
63+
void outsideLimit(TimeLimitType type, Duration duration) {
64+
var limit = new TimeLimit(type, duration);
65+
assertFalse(TimeLimitEvaluator.withinTimeLimit(limit, FIRST, SECOND));
66+
}
67+
}

application/src/ext/java/org/opentripplanner/ext/fares/impl/gtfs/TimeLimitEvaluator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ static boolean withinTimeLimit(TimeLimit limit, TransitLeg from, TransitLeg to)
1616
switch (limit.type()) {
1717
case DEPARTURE_TO_DEPARTURE -> Duration.between(from.startTime(), to.startTime());
1818
case DEPARTURE_TO_ARRIVAL -> Duration.between(from.startTime(), to.endTime());
19+
case ARRIVAL_TO_ARRIVAL -> Duration.between(from.endTime(), to.endTime());
20+
case ARRIVAL_TO_DEPARTURE -> Duration.between(from.endTime(), to.startTime());
1921
};
2022

2123
return duration.compareTo(limit.duration()) <= 0;

application/src/ext/java/org/opentripplanner/ext/fares/model/FareTransferType.java

Lines changed: 0 additions & 7 deletions
This file was deleted.

application/src/ext/java/org/opentripplanner/ext/fares/model/TimeLimitType.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,14 @@ public enum TimeLimitType {
1414
* the next one.
1515
*/
1616
DEPARTURE_TO_DEPARTURE,
17+
/**
18+
* The duration is to be computed from the arrival time of the current leg to the departure time of
19+
* the next one.
20+
*/
21+
ARRIVAL_TO_DEPARTURE,
22+
/**
23+
* The duration is to be computed from the arrival time of the current leg to the arrival time of
24+
* the next one.
25+
*/
26+
ARRIVAL_TO_ARRIVAL,
1727
}

application/src/main/java/org/opentripplanner/gtfs/mapping/FareTransferRuleMapper.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,8 @@ static TimeLimitType mapLimitType(int durationLimitType) {
6767
return switch (durationLimitType) {
6868
case 0 -> TimeLimitType.DEPARTURE_TO_ARRIVAL;
6969
case 1 -> TimeLimitType.DEPARTURE_TO_DEPARTURE;
70-
case 2, 3 -> throw new IllegalArgumentException(
71-
"Duration limit type %s not implemented.".formatted(durationLimitType)
72-
);
70+
case 2 -> TimeLimitType.ARRIVAL_TO_DEPARTURE;
71+
case 3 -> TimeLimitType.ARRIVAL_TO_ARRIVAL;
7372
default -> throw new IllegalArgumentException("Valid duration limit type must be provided.");
7473
};
7574
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package org.opentripplanner.model.plan;
2+
3+
import java.time.ZonedDateTime;
4+
import java.util.List;
5+
import java.util.Set;
6+
import org.apache.commons.lang3.NotImplementedException;
7+
import org.jetbrains.annotations.Nullable;
8+
import org.locationtech.jts.geom.LineString;
9+
import org.opentripplanner.model.fare.FareOffer;
10+
import org.opentripplanner.model.plan.leg.LegCallTime;
11+
import org.opentripplanner.routing.alertpatch.TransitAlert;
12+
import org.opentripplanner.transit.model.basic.TransitMode;
13+
14+
/**
15+
* Many methods in this class throw {@link NotImplementedException}. Please implement them when
16+
* you need them.
17+
*/
18+
public class TestTransitLeg implements TransitLeg {
19+
20+
private final ZonedDateTime startTime;
21+
private final ZonedDateTime endTime;
22+
23+
public TestTransitLeg(TestTransitLegBuilder builder) {
24+
this.startTime = builder.startTime;
25+
this.endTime = builder.endTime;
26+
}
27+
28+
@Override
29+
public TransitMode mode() {
30+
throw new NotImplementedException();
31+
}
32+
33+
@Override
34+
public TransitLeg decorateWithAlerts(Set<TransitAlert> alerts) {
35+
throw new NotImplementedException();
36+
}
37+
38+
@Override
39+
public TransitLeg decorateWithFareOffers(List<FareOffer> fares) {
40+
throw new NotImplementedException();
41+
}
42+
43+
@Override
44+
public LegCallTime start() {
45+
throw new NotImplementedException();
46+
}
47+
48+
@Override
49+
public LegCallTime end() {
50+
throw new NotImplementedException();
51+
}
52+
53+
@Override
54+
public ZonedDateTime startTime() {
55+
return startTime;
56+
}
57+
58+
@Override
59+
public ZonedDateTime endTime() {
60+
return endTime;
61+
}
62+
63+
@Override
64+
public double distanceMeters() {
65+
return 0;
66+
}
67+
68+
@Override
69+
public Place from() {
70+
throw new NotImplementedException();
71+
}
72+
73+
@Override
74+
public Place to() {
75+
throw new NotImplementedException();
76+
}
77+
78+
@Override
79+
public @Nullable LineString legGeometry() {
80+
throw new NotImplementedException();
81+
}
82+
83+
@Override
84+
public Set<TransitAlert> listTransitAlerts() {
85+
throw new NotImplementedException();
86+
}
87+
88+
@Override
89+
public @Nullable Emission emissionPerPerson() {
90+
throw new NotImplementedException();
91+
}
92+
93+
@Override
94+
public @Nullable Leg withEmissionPerPerson(Emission emissionPerPerson) {
95+
throw new NotImplementedException();
96+
}
97+
98+
@Override
99+
public int generalizedCost() {
100+
return 0;
101+
}
102+
103+
@Override
104+
public List<FareOffer> fareOffers() {
105+
return List.of();
106+
}
107+
108+
public static TestTransitLegBuilder of() {
109+
return new TestTransitLegBuilder();
110+
}
111+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.opentripplanner.model.plan;
2+
3+
import java.time.LocalDate;
4+
import java.time.LocalTime;
5+
import java.time.ZonedDateTime;
6+
import org.opentripplanner._support.time.ZoneIds;
7+
8+
public class TestTransitLegBuilder {
9+
10+
private static final LocalDate DATE = LocalDate.parse("2025-11-17");
11+
private static final ZonedDateTime TIME = LocalTime.parse("12:00")
12+
.atDate(DATE)
13+
.atZone(ZoneIds.UTC);
14+
ZonedDateTime startTime = TIME;
15+
ZonedDateTime endTime = TIME.plusHours(1);
16+
17+
public TestTransitLegBuilder withStartTime(String startTime) {
18+
var time = LocalTime.parse(startTime);
19+
this.startTime = DATE.atTime(time).atZone(ZoneIds.UTC);
20+
return this;
21+
}
22+
23+
public TestTransitLegBuilder withEndTime(String endTime) {
24+
var time = LocalTime.parse(endTime);
25+
this.endTime = DATE.atTime(time).atZone(ZoneIds.UTC);
26+
return this;
27+
}
28+
29+
public TestTransitLeg build() {
30+
return new TestTransitLeg(this);
31+
}
32+
}

application/src/test/java/org/opentripplanner/gtfs/mapping/FareTransferRuleMapperTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ void timeLimit() {
6868
void limitType() {
6969
assertEquals(TimeLimitType.DEPARTURE_TO_ARRIVAL, FareTransferRuleMapper.mapLimitType(0));
7070
assertEquals(TimeLimitType.DEPARTURE_TO_DEPARTURE, FareTransferRuleMapper.mapLimitType(1));
71+
assertEquals(TimeLimitType.ARRIVAL_TO_DEPARTURE, FareTransferRuleMapper.mapLimitType(2));
72+
assertEquals(TimeLimitType.ARRIVAL_TO_ARRIVAL, FareTransferRuleMapper.mapLimitType(3));
7173
}
7274

7375
@Test

0 commit comments

Comments
 (0)