Skip to content

Commit 87a359a

Browse files
committed
Merge remote-tracking branch 'otp/dev-2.x' into add_co2_emmistions
2 parents d1b9969 + e96c949 commit 87a359a

File tree

29 files changed

+595
-67
lines changed

29 files changed

+595
-67
lines changed

application/src/client/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<link rel="icon" type="image/svg+xml" href="/img/otp-logo.svg" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>OTP Debug</title>
8-
<script type="module" crossorigin src="https://www.opentripplanner.org/debug-client-assets/2025/03/2025-03-26T08:20/assets/index-D1F3OmAI.js"></script>
9-
<link rel="stylesheet" crossorigin href="https://www.opentripplanner.org/debug-client-assets/2025/03/2025-03-26T08:20/assets/index-CiWBeHPw.css">
8+
<script type="module" crossorigin src="https://www.opentripplanner.org/debug-client-assets/2025/04/2025-04-01T06:20/assets/index-4XLjDqhn.js"></script>
9+
<link rel="stylesheet" crossorigin href="https://www.opentripplanner.org/debug-client-assets/2025/04/2025-04-01T06:20/assets/index-CiWBeHPw.css">
1010
</head>
1111
<body>
1212
<div id="root"></div>

application/src/ext-test/java/org/opentripplanner/ext/flex/FlexIntegrationTest.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.opentripplanner.model.plan.Itinerary;
3131
import org.opentripplanner.routing.api.RoutingService;
3232
import org.opentripplanner.routing.api.request.RouteRequest;
33+
import org.opentripplanner.routing.api.request.StreetMode;
3334
import org.opentripplanner.routing.api.request.framework.TimeAndCostPenalty;
3435
import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter;
3536
import org.opentripplanner.routing.graph.Graph;
@@ -236,12 +237,16 @@ private static List<Itinerary> getItineraries(
236237
);
237238

238239
var modes = request.journey().modes().copyOf();
239-
modes.withEgressMode(FLEXIBLE);
240240

241241
if (onlyDirect) {
242-
modes.withDirectMode(FLEXIBLE);
243-
request.journey().transit().setFilters(List.of(ExcludeAllTransitFilter.of()));
242+
modes
243+
.withDirectMode(FLEXIBLE)
244+
.withAccessMode(StreetMode.WALK)
245+
.withEgressMode(StreetMode.WALK);
246+
request.journey().transit().setFilters(List.of(AllowAllTransitFilter.of()));
247+
request.journey().transit().disable();
244248
} else {
249+
modes.withEgressMode(FLEXIBLE);
245250
request.journey().transit().setFilters(List.of(AllowAllTransitFilter.of()));
246251
}
247252

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package org.opentripplanner.ext.flex.filter;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id;
5+
6+
import java.util.List;
7+
import java.util.Set;
8+
import org.junit.jupiter.api.Test;
9+
import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter;
10+
import org.opentripplanner.routing.api.request.request.filter.SelectRequest;
11+
import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest;
12+
import org.opentripplanner.transit.api.request.TripRequest;
13+
import org.opentripplanner.transit.model.framework.FeedScopedId;
14+
15+
class FilterMapperTest {
16+
17+
private static final TripRequest ALLOW_ALL = TripRequest.of().build();
18+
public static final FeedScopedId ROUTE_ID1 = id("r1");
19+
public static final FeedScopedId AGENCY_ID1 = id("a1");
20+
21+
@Test
22+
void allowAll() {
23+
var filter = FilterMapper.map(List.of(AllowAllTransitFilter.of()));
24+
assertEquals(ALLOW_ALL, filter);
25+
}
26+
27+
@Test
28+
void routes() {
29+
var select = SelectRequest.of().withRoutes(List.of(ROUTE_ID1)).build();
30+
var transitFilter = TransitFilterRequest.of().addSelect(select).addNot(select).build();
31+
var actual = FilterMapper.map(List.of(transitFilter));
32+
var expected = TripRequest.of()
33+
.withIncludeRoutes(List.of(ROUTE_ID1))
34+
.withExcludeRoutes(List.of(ROUTE_ID1))
35+
.build();
36+
37+
assertEquals(expected, actual);
38+
}
39+
40+
@Test
41+
void agencies() {
42+
var select = SelectRequest.of().withAgencies(List.of(AGENCY_ID1)).build();
43+
var transitFilter = TransitFilterRequest.of().addSelect(select).addNot(select).build();
44+
var actual = FilterMapper.map(List.of(transitFilter));
45+
var expected = TripRequest.of()
46+
.withIncludeAgencies(List.of(AGENCY_ID1))
47+
.withExcludeAgencies(List.of(AGENCY_ID1))
48+
.build();
49+
50+
assertEquals(expected, actual);
51+
}
52+
53+
@Test
54+
void combinations() {
55+
var selectRoutes = SelectRequest.of().withRoutes(List.of(ROUTE_ID1)).build();
56+
var routeFilter = TransitFilterRequest.of()
57+
.addSelect(selectRoutes)
58+
.addNot(selectRoutes)
59+
.build();
60+
var selectAgencies = SelectRequest.of().withAgencies(List.of(AGENCY_ID1)).build();
61+
var agencyFilter = TransitFilterRequest.of()
62+
.addSelect(selectAgencies)
63+
.addNot(selectAgencies)
64+
.build();
65+
66+
var actual = FilterMapper.map(List.of(routeFilter, agencyFilter));
67+
var expected = TripRequest.of()
68+
.withIncludeAgencies(List.of(AGENCY_ID1))
69+
.withExcludeAgencies(List.of(AGENCY_ID1))
70+
.withIncludeRoutes(List.of(ROUTE_ID1))
71+
.withExcludeRoutes(List.of(ROUTE_ID1))
72+
.build();
73+
74+
assertEquals(expected, actual);
75+
76+
assertEquals(
77+
"TripRequest{includeAgencies: FilterValuesNullIsEverything{name: 'includeAgencies', values: [F:a1]}, includeRoutes: FilterValuesNullIsEverything{name: 'includeRoutes', values: [F:r1]}, excludeAgencies: FilterValuesEmptyIsEverything{name: 'excludedAgencies', values: [F:a1]}, excludeRoutes: FilterValuesEmptyIsEverything{name: 'excludedRoutes', values: [F:r1]}, includeNetexInternalPlanningCodes: FilterValuesNullIsEverything{name: 'includeNetexInternalPlanningCodes'}, includeServiceDates: FilterValuesNullIsEverything{name: 'includeServiceDates'}}",
78+
actual.toString()
79+
);
80+
}
81+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package org.opentripplanner.ext.flex.template;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area;
6+
import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id;
7+
8+
import gnu.trove.set.hash.TIntHashSet;
9+
import java.time.LocalDate;
10+
import java.util.Collection;
11+
import java.util.List;
12+
import java.util.Set;
13+
import org.junit.jupiter.api.Test;
14+
import org.opentripplanner._support.time.ZoneIds;
15+
import org.opentripplanner.ext.flex.trip.FlexTrip;
16+
import org.opentripplanner.ext.flex.trip.UnscheduledTrip;
17+
import org.opentripplanner.model.PathTransfer;
18+
import org.opentripplanner.routing.graphfinder.NearbyStop;
19+
import org.opentripplanner.street.model.vertex.TransitStopVertex;
20+
import org.opentripplanner.transit.api.request.TripRequest;
21+
import org.opentripplanner.transit.model._data.TimetableRepositoryForTest;
22+
import org.opentripplanner.transit.model.filter.expr.Matcher;
23+
import org.opentripplanner.transit.model.filter.transit.TripMatcherFactory;
24+
import org.opentripplanner.transit.model.framework.FeedScopedId;
25+
import org.opentripplanner.transit.model.site.StopLocation;
26+
import org.opentripplanner.transit.model.timetable.Trip;
27+
import org.opentripplanner.utils.time.ServiceDateUtils;
28+
29+
class ClosestTripTest {
30+
31+
private static final UnscheduledTrip FLEX_TRIP = UnscheduledTrip.of(id("123"))
32+
.withStopTimes(List.of(area("10:00", "11:00"), area("10:00", "11:00")))
33+
.withTrip(TimetableRepositoryForTest.trip("123").build())
34+
.build();
35+
36+
private static final LocalDate DATE = LocalDate.of(2025, 2, 28);
37+
private static final FlexServiceDate FSD = new FlexServiceDate(
38+
DATE,
39+
ServiceDateUtils.secondsSinceStartOfTime(DATE.atStartOfDay(ZoneIds.BERLIN), DATE),
40+
10,
41+
new TIntHashSet()
42+
);
43+
private static final StopLocation STOP = FLEX_TRIP.getStop(0);
44+
private static final FlexAccessEgressCallbackAdapter ADAPTER =
45+
new FlexAccessEgressCallbackAdapter() {
46+
@Override
47+
public TransitStopVertex getStopVertexForStopId(FeedScopedId id) {
48+
return null;
49+
}
50+
51+
@Override
52+
public Collection<PathTransfer> getTransfersFromStop(StopLocation stop) {
53+
return List.of();
54+
}
55+
56+
@Override
57+
public Collection<PathTransfer> getTransfersToStop(StopLocation stop) {
58+
return List.of();
59+
}
60+
61+
@Override
62+
public Collection<FlexTrip<?, ?>> getFlexTripsByStop(StopLocation stopLocation) {
63+
return List.of(FLEX_TRIP);
64+
}
65+
66+
@Override
67+
public boolean isDateActive(FlexServiceDate date, FlexTrip<?, ?> trip) {
68+
return true;
69+
}
70+
};
71+
72+
@Test
73+
void doNotFilter() {
74+
var request = TripRequest.of().build();
75+
var matcher = TripMatcherFactory.of(request, id -> Set.of(DATE));
76+
77+
var trips = closestTrips(matcher);
78+
assertThat(trips).hasSize(1);
79+
assertEquals(List.copyOf(trips).getFirst().flexTrip(), FLEX_TRIP);
80+
}
81+
82+
@Test
83+
void filter() {
84+
var request = TripRequest.of()
85+
.withExcludeAgencies(List.of(FLEX_TRIP.getTrip().getRoute().getAgency().getId()))
86+
.build();
87+
88+
var matcher = TripMatcherFactory.of(request, id -> Set.of(DATE));
89+
90+
var trips = closestTrips(matcher);
91+
assertThat(trips).isEmpty();
92+
}
93+
94+
private static Collection<ClosestTrip> closestTrips(Matcher<Trip> matcher) {
95+
return ClosestTrip.of(
96+
ADAPTER,
97+
List.of(new NearbyStop(STOP, 100, List.of(), null)),
98+
matcher,
99+
List.of(FSD),
100+
true
101+
);
102+
}
103+
}

application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.opentripplanner.street.model.vertex.StreetLocation;
4040
import org.opentripplanner.street.search.request.StreetSearchRequest;
4141
import org.opentripplanner.street.search.state.State;
42+
import org.opentripplanner.transit.api.request.TripRequest;
4243
import org.opentripplanner.transit.model.framework.FeedScopedId;
4344
import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService;
4445
import org.opentripplanner.transit.model.site.AreaStop;
@@ -104,6 +105,7 @@ void calculateDirectFare() {
104105
graph,
105106
new DefaultTransitService(timetableRepository),
106107
FlexParameters.defaultValues(),
108+
TripRequest.of().build(),
107109
OffsetDateTime.parse("2021-11-12T10:15:24-05:00").toInstant(),
108110
null,
109111
1,

application/src/ext/java/org/opentripplanner/ext/flex/FlexIndex.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@ public Collection<PathTransfer> getTransfersFromStop(StopLocation stopLocation)
6060
return flexTripsByStop.get(stopLocation);
6161
}
6262

63-
public Route getRouteById(FeedScopedId id) {
64-
return routeById.get(id);
65-
}
66-
6763
public boolean contains(Route route) {
6864
return routeById.containsKey(route.getId());
6965
}

application/src/ext/java/org/opentripplanner/ext/flex/FlexRouter.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,12 @@
2626
import org.opentripplanner.routing.graph.Graph;
2727
import org.opentripplanner.routing.graphfinder.NearbyStop;
2828
import org.opentripplanner.street.model.vertex.TransitStopVertex;
29+
import org.opentripplanner.transit.api.request.TripRequest;
30+
import org.opentripplanner.transit.model.filter.expr.Matcher;
31+
import org.opentripplanner.transit.model.filter.transit.TripMatcherFactory;
2932
import org.opentripplanner.transit.model.framework.FeedScopedId;
3033
import org.opentripplanner.transit.model.site.StopLocation;
34+
import org.opentripplanner.transit.model.timetable.Trip;
3135
import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;
3236
import org.opentripplanner.transit.service.TransitService;
3337
import org.opentripplanner.utils.time.ServiceDateUtils;
@@ -52,11 +56,13 @@ public class FlexRouter {
5256
private final int requestedTime;
5357
private final int requestedBookingTime;
5458
private final List<FlexServiceDate> dates;
59+
private final Matcher<Trip> matcher;
5560

5661
public FlexRouter(
5762
Graph graph,
5863
TransitService transitService,
5964
FlexParameters flexParameters,
65+
TripRequest filterRequest,
6066
Instant requestedTime,
6167
@Nullable Instant requestedBookingTime,
6268
int additionalPastSearchDays,
@@ -70,6 +76,10 @@ public FlexRouter(
7076
this.streetAccesses = streetAccesses;
7177
this.streetEgresses = egressTransfers;
7278
this.flexIndex = transitService.getFlexIndex();
79+
this.matcher = TripMatcherFactory.of(
80+
filterRequest,
81+
transitService.getCalendarService()::getServiceDatesForServiceId
82+
);
7383
this.callbackService = new CallbackAdapter();
7484
this.graphPathToItineraryMapper = new GraphPathToItineraryMapper(
7585
transitService.getTimeZone(),
@@ -115,7 +125,8 @@ public List<Itinerary> createFlexOnlyItineraries(boolean arriveBy) {
115125
callbackService,
116126
accessFlexPathCalculator,
117127
egressFlexPathCalculator,
118-
flexParameters.maxTransferDuration()
128+
flexParameters.maxTransferDuration(),
129+
matcher
119130
).calculateDirectFlexPaths(streetAccesses, streetEgresses, dates, requestedTime, arriveBy);
120131

121132
var itineraries = new ArrayList<Itinerary>();
@@ -139,7 +150,8 @@ public Collection<FlexAccessEgress> createFlexAccesses() {
139150
return new FlexAccessFactory(
140151
callbackService,
141152
accessFlexPathCalculator,
142-
flexParameters.maxTransferDuration()
153+
flexParameters.maxTransferDuration(),
154+
matcher
143155
).createFlexAccesses(streetAccesses, dates);
144156
}
145157

@@ -148,7 +160,8 @@ public Collection<FlexAccessEgress> createFlexEgresses() {
148160
return new FlexEgressFactory(
149161
callbackService,
150162
egressFlexPathCalculator,
151-
flexParameters.maxTransferDuration()
163+
flexParameters.maxTransferDuration(),
164+
matcher
152165
).createFlexEgresses(streetEgresses, dates);
153166
}
154167

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package org.opentripplanner.ext.flex.filter;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import org.opentripplanner.model.modes.ExcludeAllTransitFilter;
6+
import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter;
7+
import org.opentripplanner.routing.api.request.request.filter.TransitFilter;
8+
import org.opentripplanner.routing.api.request.request.filter.TransitFilterRequest;
9+
import org.opentripplanner.transit.api.request.TripRequest;
10+
import org.opentripplanner.transit.model.framework.FeedScopedId;
11+
12+
/**
13+
* Map the internal OTP filter API into the reduced, flex-specific version of it.
14+
*/
15+
public class FilterMapper {
16+
17+
private final List<FeedScopedId> excludedAgencies = new ArrayList<>();
18+
private final List<FeedScopedId> excludedRoutes = new ArrayList<>();
19+
private final List<FeedScopedId> selectedAgencies = new ArrayList<>();
20+
private final List<FeedScopedId> selectedRoutes = new ArrayList<>();
21+
22+
private FilterMapper() {}
23+
24+
public static TripRequest map(List<TransitFilter> filters) {
25+
var mapper = new FilterMapper();
26+
return mapper.mapFilters(filters);
27+
}
28+
29+
private TripRequest mapFilters(List<TransitFilter> filters) {
30+
var builder = TripRequest.of();
31+
32+
for (TransitFilter filter : filters) {
33+
if (filter instanceof TransitFilterRequest sr) {
34+
addFilter(sr);
35+
} else if (
36+
!(filter instanceof AllowAllTransitFilter) && !(filter instanceof ExcludeAllTransitFilter)
37+
) {
38+
throw new IllegalStateException("Unexpected value: " + filter);
39+
}
40+
}
41+
if (!selectedAgencies.isEmpty()) {
42+
builder.withIncludeAgencies(selectedAgencies);
43+
}
44+
if (!selectedRoutes.isEmpty()) {
45+
builder.withIncludeRoutes(selectedRoutes);
46+
}
47+
if (!excludedAgencies.isEmpty()) {
48+
builder.withExcludeAgencies(excludedAgencies);
49+
}
50+
if (!excludedRoutes.isEmpty()) {
51+
builder.withExcludeRoutes(excludedRoutes);
52+
}
53+
return builder.build();
54+
}
55+
56+
private void addFilter(TransitFilterRequest sr) {
57+
sr
58+
.not()
59+
.forEach(s -> {
60+
excludedRoutes.addAll(s.routes());
61+
excludedAgencies.addAll(s.agencies());
62+
});
63+
sr
64+
.select()
65+
.forEach(s -> {
66+
selectedRoutes.addAll(s.routes());
67+
selectedAgencies.addAll(s.agencies());
68+
});
69+
}
70+
}

0 commit comments

Comments
 (0)