Skip to content

Commit e4f42f7

Browse files
Implement modes and transit filters
1 parent 66a29eb commit e4f42f7

File tree

7 files changed

+388
-51
lines changed

7 files changed

+388
-51
lines changed

application/src/ext-test/java/org/opentripplanner/ext/ojp/mapping/RouteRequestMapperTest.java

Lines changed: 145 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,31 @@
66
import static org.junit.jupiter.api.Assertions.assertThrows;
77
import static org.opentripplanner.transit.model._data.FeedScopedIdForTestFactory.id;
88

9+
import de.vdv.ojp20.LineDirectionFilterStructure;
910
import de.vdv.ojp20.ModeAndModeOfOperationFilterStructure;
1011
import de.vdv.ojp20.OJPTripRequestStructure;
12+
import de.vdv.ojp20.OperatorFilterStructure;
13+
import de.vdv.ojp20.PersonalModesEnumeration;
1114
import de.vdv.ojp20.PlaceContextStructure;
1215
import de.vdv.ojp20.PlaceRefStructure;
1316
import de.vdv.ojp20.StopPlaceRefStructure;
1417
import de.vdv.ojp20.TripParamStructure;
18+
import de.vdv.ojp20.siri.LineDirectionStructure;
19+
import de.vdv.ojp20.siri.LineRefStructure;
1520
import de.vdv.ojp20.siri.LocationStructure;
21+
import de.vdv.ojp20.siri.OperatorRefStructure;
1622
import de.vdv.ojp20.siri.StopPointRefStructure;
1723
import de.vdv.ojp20.siri.VehicleModesOfTransportEnumeration;
1824
import java.time.Duration;
25+
import java.util.List;
26+
import java.util.stream.Stream;
1927
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.Arguments;
30+
import org.junit.jupiter.params.provider.MethodSource;
2031
import org.opentripplanner.api.model.transit.DefaultFeedIdMapper;
2132
import org.opentripplanner.routing.api.request.RouteRequest;
33+
import org.opentripplanner.routing.api.request.StreetMode;
2234

2335
class RouteRequestMapperTest {
2436

@@ -37,7 +49,9 @@ void mapWithCoordinates() {
3749
assertEquals(8.5417, routeRequest.from().lng);
3850
assertEquals(46.9480, routeRequest.to().lat);
3951
assertEquals(7.4474, routeRequest.to().lng);
40-
assertTransitFilters(routeRequest, "[ALLOW_ALL]");
52+
assertTransitFilters("[ALLOW_ALL]", routeRequest);
53+
54+
assertEquals(StreetMode.WALK, routeRequest.journey().access().mode());
4155
}
4256

4357
@Test
@@ -161,7 +175,76 @@ void excludeMode() {
161175

162176
var routeRequest = mapper.map(tripRequest);
163177

164-
assertTransitFilters(routeRequest, "[(not: [(transportModes: [RAIL])])]");
178+
assertTransitFilters("[(not: [(transportModes: [RAIL])])]", routeRequest);
179+
}
180+
181+
private static List<Arguments> transitFilterCases() {
182+
return List.of(
183+
Arguments.of(
184+
"[(select: [(transportModes: EMPTY, agencies: [F:agency1])])]",
185+
new TripParamStructure()
186+
.withOperatorFilter(
187+
new OperatorFilterStructure()
188+
.withExclude(false)
189+
.withOperatorRef(List.of(new OperatorRefStructure().withValue("F:agency1")))
190+
)
191+
),
192+
Arguments.of(
193+
"[(not: [(transportModes: EMPTY, agencies: [F:agency1])])]",
194+
new TripParamStructure()
195+
.withOperatorFilter(
196+
new OperatorFilterStructure()
197+
.withExclude(true)
198+
.withOperatorRef(List.of(new OperatorRefStructure().withValue("F:agency1")))
199+
)
200+
),
201+
Arguments.of(
202+
"[(select: [(transportModes: EMPTY, routes: [F:route1])])]",
203+
new TripParamStructure()
204+
.withLineFilter(
205+
new LineDirectionFilterStructure()
206+
.withExclude(false)
207+
.withLine(
208+
(new LineDirectionStructure()
209+
.withLineRef(new LineRefStructure().withValue("F:route1")))
210+
)
211+
)
212+
),
213+
Arguments.of(
214+
"[(not: [(transportModes: EMPTY, routes: [F:agency1])])]",
215+
new TripParamStructure()
216+
.withLineFilter(
217+
new LineDirectionFilterStructure()
218+
.withExclude(true)
219+
.withLine(
220+
(new LineDirectionStructure()
221+
.withLineRef(new LineRefStructure().withValue("F:agency1")))
222+
)
223+
)
224+
),
225+
Arguments.of(
226+
"[(not: [(transportModes: EMPTY, routes: [B:route2, A:route1])])]",
227+
new TripParamStructure()
228+
.withLineFilter(
229+
new LineDirectionFilterStructure()
230+
.withExclude(true)
231+
.withLine(
232+
new LineDirectionStructure()
233+
.withLineRef(new LineRefStructure().withValue("A:route1")),
234+
new LineDirectionStructure()
235+
.withLineRef(new LineRefStructure().withValue("B:route2"))
236+
)
237+
)
238+
)
239+
);
240+
}
241+
242+
@ParameterizedTest
243+
@MethodSource("transitFilterCases")
244+
void transitFilter(String expectedFilter, TripParamStructure tripParamStructure) {
245+
var tripRequest = baseRequest().withParams(tripParamStructure);
246+
var routeRequest = mapper.map(tripRequest);
247+
assertTransitFilters(expectedFilter, routeRequest);
165248
}
166249

167250
@Test
@@ -174,13 +257,72 @@ void mapAdditionalTransferTime() {
174257
assertEquals(Duration.ofMinutes(10), routeRequest.preferences().transfer().slack());
175258
}
176259

260+
private static List<Arguments> personalModeCase() {
261+
return List.of(
262+
Arguments.of(PersonalModesEnumeration.FOOT, StreetMode.WALK),
263+
Arguments.of(PersonalModesEnumeration.CAR, StreetMode.CAR)
264+
);
265+
}
266+
267+
@ParameterizedTest
268+
@MethodSource("personalModeCase")
269+
void personalMode(PersonalModesEnumeration personalMode, StreetMode expectedMode) {
270+
var tripRequest = baseRequest()
271+
.withParams(
272+
new TripParamStructure()
273+
.withModeAndModeOfOperationFilter(
274+
new ModeAndModeOfOperationFilterStructure()
275+
.withExclude(false)
276+
.withPersonalMode(personalMode)
277+
)
278+
);
279+
280+
var routeRequest = mapper.map(tripRequest);
281+
282+
assertEquals(expectedMode, routeRequest.journey().access().mode());
283+
}
284+
285+
private static Stream<TripParamStructure> invalidPersonalModeCases() {
286+
return Stream.of(
287+
new TripParamStructure()
288+
.withModeAndModeOfOperationFilter(
289+
new ModeAndModeOfOperationFilterStructure()
290+
.withExclude(true)
291+
.withPersonalMode(PersonalModesEnumeration.BICYCLE)
292+
),
293+
new TripParamStructure()
294+
.withModeAndModeOfOperationFilter(
295+
new ModeAndModeOfOperationFilterStructure()
296+
.withExclude(false)
297+
.withPersonalMode(PersonalModesEnumeration.BICYCLE, PersonalModesEnumeration.CAR)
298+
),
299+
new TripParamStructure()
300+
.withModeAndModeOfOperationFilter(
301+
new ModeAndModeOfOperationFilterStructure()
302+
.withExclude(false)
303+
.withPersonalMode(PersonalModesEnumeration.BICYCLE),
304+
new ModeAndModeOfOperationFilterStructure()
305+
.withExclude(false)
306+
.withPersonalMode(PersonalModesEnumeration.FOOT)
307+
)
308+
);
309+
}
310+
311+
@ParameterizedTest
312+
@MethodSource("invalidPersonalModeCases")
313+
void throwsExceptionForUnsupportedPersonalMode(TripParamStructure tripParamStructure) {
314+
var tripRequest = baseRequest().withParams(tripParamStructure);
315+
316+
assertThrows(IllegalArgumentException.class, () -> mapper.map(tripRequest));
317+
}
318+
177319
private static OJPTripRequestStructure baseRequest() {
178320
return new OJPTripRequestStructure()
179321
.withOrigin(geoPosition(47.3769, 8.5417))
180322
.withDestination(geoPosition(46.9480, 7.4474));
181323
}
182324

183-
private static void assertTransitFilters(RouteRequest routeRequest, String expected) {
325+
private static void assertTransitFilters(String expected, RouteRequest routeRequest) {
184326
assertEquals(expected, routeRequest.journey().transit().filters().toString());
185327
}
186328

application/src/ext/java/org/opentripplanner/ext/ojp/RequestHandler.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import org.slf4j.Logger;
2020
import org.slf4j.LoggerFactory;
2121

22+
/**
23+
* Handles incoming OJP requests and routes them to the appropriate OJP service.
24+
*/
2225
public class RequestHandler {
2326

2427
private static final Logger LOG = LoggerFactory.getLogger(RequestHandler.class);
@@ -36,7 +39,7 @@ public RequestHandler(
3639
this.apiName = requireNonNull(apiName);
3740
}
3841

39-
public Response handleRequest(OJP ojp, RouteRequest routeRequest) {
42+
public Response handleRequest(OJP ojp, RouteRequest defaultRequest) {
4043
try {
4144
var request = findRequest(ojp);
4245

@@ -45,7 +48,7 @@ public Response handleRequest(OJP ojp, RouteRequest routeRequest) {
4548
StreamingOutput stream = responseMapper.apply(ojpResponse);
4649
return Response.ok(stream).build();
4750
} else if (request instanceof OJPTripRequestStructure tr) {
48-
var ojpResponse = ojpService.handleTripRequest(tr, routeRequest);
51+
var ojpResponse = ojpService.handleTripRequest(tr, defaultRequest);
4952
StreamingOutput stream = responseMapper.apply(ojpResponse);
5053
return Response.ok(stream).build();
5154
} else {
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package org.opentripplanner.ext.ojp.mapping;
2+
3+
import static java.lang.Boolean.TRUE;
4+
5+
import de.vdv.ojp20.LineDirectionFilterStructure;
6+
import de.vdv.ojp20.OJPStopEventRequestStructure;
7+
import de.vdv.ojp20.OJPTripRequestStructure;
8+
import de.vdv.ojp20.OperatorFilterStructure;
9+
import de.vdv.ojp20.StopEventParamStructure;
10+
import de.vdv.ojp20.TripParamStructure;
11+
import java.util.List;
12+
import java.util.Optional;
13+
import java.util.Set;
14+
import java.util.function.Predicate;
15+
import java.util.stream.Collectors;
16+
import org.opentripplanner.api.model.transit.FeedScopedIdMapper;
17+
import org.opentripplanner.core.model.id.FeedScopedId;
18+
19+
class FilterMapper {
20+
21+
private final FeedScopedIdMapper idMapper;
22+
23+
FilterMapper(FeedScopedIdMapper idMapper) {
24+
this.idMapper = idMapper;
25+
}
26+
27+
List<FeedScopedId> includedAgencies(OJPTripRequestStructure tr) {
28+
return List.copyOf(filterAgencies(tr, o -> !isExclude(o.isExclude())));
29+
}
30+
31+
Set<FeedScopedId> includedAgencies(OJPStopEventRequestStructure ser) {
32+
return filterAgencies(ser, o -> !isExclude(o.isExclude()));
33+
}
34+
35+
List<FeedScopedId> excludedAgencies(OJPTripRequestStructure tr) {
36+
return List.copyOf(filterAgencies(tr, f -> isExclude(f.isExclude())));
37+
}
38+
39+
Set<FeedScopedId> excludedAgencies(OJPStopEventRequestStructure ser) {
40+
return filterAgencies(ser, f -> isExclude(f.isExclude()));
41+
}
42+
43+
Set<FeedScopedId> includedRoutes(OJPStopEventRequestStructure ser) {
44+
return filterLines(ser, o -> !isExclude(o.isExclude()));
45+
}
46+
47+
List<FeedScopedId> includedRoutes(OJPTripRequestStructure ser) {
48+
return List.copyOf(filterLines(ser, o -> !isExclude(o.isExclude())));
49+
}
50+
51+
Set<FeedScopedId> excludedRoutes(OJPStopEventRequestStructure ser) {
52+
return filterLines(ser, f -> isExclude(f.isExclude()));
53+
}
54+
55+
List<FeedScopedId> excludedRoutes(OJPTripRequestStructure tr) {
56+
return List.copyOf(filterLines(tr, f -> isExclude(f.isExclude())));
57+
}
58+
59+
// private methods
60+
61+
private Set<FeedScopedId> filterAgencies(
62+
OJPTripRequestStructure tr,
63+
Predicate<OperatorFilterStructure> predicate
64+
) {
65+
var filter = params(tr).map(t -> t.getOperatorFilter());
66+
return filterAgencies(filter, predicate);
67+
}
68+
69+
private Set<FeedScopedId> filterAgencies(
70+
OJPStopEventRequestStructure ser,
71+
Predicate<OperatorFilterStructure> predicate
72+
) {
73+
var filter = params(ser).map(p -> p.getOperatorFilter());
74+
return filterAgencies(filter, predicate);
75+
}
76+
77+
private Set<FeedScopedId> filterLines(
78+
OJPStopEventRequestStructure ser,
79+
Predicate<LineDirectionFilterStructure> predicate
80+
) {
81+
var filter = params(ser).map(p -> p.getLineFilter());
82+
return filterLines(filter, predicate);
83+
}
84+
85+
private Set<FeedScopedId> filterLines(
86+
OJPTripRequestStructure ser,
87+
Predicate<LineDirectionFilterStructure> predicate
88+
) {
89+
var filter = params(ser).map(p -> p.getLineFilter());
90+
return filterLines(filter, predicate);
91+
}
92+
93+
private Set<FeedScopedId> filterLines(
94+
Optional<LineDirectionFilterStructure> lineDirectionFilterStructure,
95+
Predicate<LineDirectionFilterStructure> predicate
96+
) {
97+
return lineDirectionFilterStructure
98+
.filter(predicate)
99+
.map(o -> o.getLine())
100+
.stream()
101+
.flatMap(r -> r.stream().map(l -> l.getLineRef().getValue()))
102+
.map(idMapper::parse)
103+
.collect(Collectors.toSet());
104+
}
105+
106+
private Set<FeedScopedId> filterAgencies(
107+
Optional<OperatorFilterStructure> operatorFilterStructure,
108+
Predicate<OperatorFilterStructure> predicate
109+
) {
110+
return operatorFilterStructure
111+
.filter(predicate)
112+
.map(o -> o.getOperatorRef())
113+
.stream()
114+
.flatMap(r -> r.stream().map(ref -> ref.getValue()))
115+
.map(idMapper::parse)
116+
.collect(Collectors.toSet());
117+
}
118+
119+
private static Optional<TripParamStructure> params(OJPTripRequestStructure tr) {
120+
return Optional.ofNullable(tr.getParams());
121+
}
122+
123+
private static Optional<StopEventParamStructure> params(OJPStopEventRequestStructure ser) {
124+
return Optional.ofNullable(ser.getParams());
125+
}
126+
127+
private static boolean isExclude(Boolean b) {
128+
return b == null || TRUE.equals(b);
129+
}
130+
}

application/src/ext/java/org/opentripplanner/ext/ojp/mapping/PersonalModeMapper.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
package org.opentripplanner.ext.ojp.mapping;
22

33
import de.vdv.ojp20.PersonalModesEnumeration;
4+
import org.opentripplanner.routing.api.request.StreetMode;
45
import org.opentripplanner.street.search.TraverseMode;
56

67
class PersonalModeMapper {
78

9+
public static StreetMode toStreetMode(PersonalModesEnumeration x) {
10+
return switch (x) {
11+
case FOOT -> StreetMode.WALK;
12+
case BICYCLE -> StreetMode.BIKE;
13+
case SCOOTER -> StreetMode.SCOOTER_RENTAL;
14+
case CAR -> StreetMode.CAR;
15+
case MOTORCYCLE, TRUCK, OTHER -> throw new IllegalArgumentException("Unsupported mode: " + x);
16+
};
17+
}
18+
819
static PersonalModesEnumeration mapToOjp(TraverseMode mode) {
920
return switch (mode) {
1021
case WALK -> PersonalModesEnumeration.FOOT;

0 commit comments

Comments
 (0)