diff --git a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java index 2d8a03c5ce4..1549a1feab9 100644 --- a/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapperTest.java @@ -1,9 +1,12 @@ package org.opentripplanner.ext.transmodelapi.mapping; import static graphql.execution.ExecutionContextBuilder.newExecutionContextBuilder; +import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.opentripplanner.framework.time.TimeUtils.time; +import static org.opentripplanner.model.plan.TestItineraryBuilder.newItinerary; import graphql.ExecutionInput; import graphql.execution.ExecutionId; @@ -11,18 +14,26 @@ import graphql.schema.DataFetchingEnvironmentImpl; import io.micrometer.core.instrument.Metrics; import java.time.Duration; -import java.util.Arrays; +import java.time.LocalDate; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.function.Function; import java.util.stream.Stream; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.ext.transmodelapi.TransmodelRequestContext; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.model.calendar.CalendarServiceData; +import org.opentripplanner.model.plan.Itinerary; +import org.opentripplanner.model.plan.Leg; +import org.opentripplanner.model.plan.Place; import org.opentripplanner.model.plan.PlanTestConstants; +import org.opentripplanner.model.plan.ScheduledTransitLeg; import org.opentripplanner.raptor.configure.RaptorConfig; +import org.opentripplanner.routing.api.request.PassThroughPoint; import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.routing.api.request.StreetMode; import org.opentripplanner.routing.api.request.preference.StreetPreferences; @@ -36,7 +47,14 @@ import org.opentripplanner.standalone.config.RouterConfig; import org.opentripplanner.standalone.server.DefaultServerRequestContext; import org.opentripplanner.test.support.VariableSource; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.StopLocation; import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; public class TripRequestMapperTest implements PlanTestConstants { @@ -44,12 +62,43 @@ public class TripRequestMapperTest implements PlanTestConstants { static final TransmodelRequestContext context; private static final Duration MAX_FLEXIBLE = Duration.ofMinutes(20); + private static final Function STOP_TO_ID = s -> s.getId().toString(); + + private static final Route route1 = TransitModelForTest.route("route1").build(); + private static final Route route2 = TransitModelForTest.route("route2").build(); + + private static final RegularStop stop1 = TransitModelForTest.stopForTest("ST:stop1", 1, 1); + private static final RegularStop stop2 = TransitModelForTest.stopForTest("ST:stop2", 2, 1); + private static final RegularStop stop3 = TransitModelForTest.stopForTest("ST:stop3", 3, 1); + static { var graph = new Graph(); - var transitModel = new TransitModel(); + var itinerary = newItinerary(Place.forStop(stop1), time("11:00")) + .bus(route1, 1, time("11:05"), time("11:20"), Place.forStop(stop2)) + .bus(route2, 2, time("11:20"), time("11:40"), Place.forStop(stop3)) + .build(); + var patterns = itineraryPatterns(itinerary); + var stopModel = StopModel + .of() + .withRegularStop(stop1) + .withRegularStop(stop2) + .withRegularStop(stop3) + .build(); + + var transitModel = new TransitModel(stopModel, new Deduplicator()); transitModel.initTimeZone(ZoneIds.STOCKHOLM); + var calendarServiceData = new CalendarServiceData(); + LocalDate serviceDate = itinerary.startTime().toLocalDate(); + patterns.forEach(pattern -> { + transitModel.addTripPattern(pattern.getId(), pattern); + final int serviceCode = pattern.getScheduledTimetable().getTripTimes(0).getServiceCode(); + transitModel.getServiceCodes().put(pattern.getId(), serviceCode); + calendarServiceData.putServiceDatesForServiceId(pattern.getId(), List.of(serviceDate)); + }); + + transitModel.updateCalendarServiceData(true, calendarServiceData, DataImportIssueStore.NOOP); + transitModel.index(); final var transitService = new DefaultTransitService(transitModel); - var defaultRequest = new RouteRequest(); // Change defaults for FLEXIBLE to a lower value than the default 45m. This should restrict the @@ -243,6 +292,42 @@ public void testBikeTriangleFactorsHasNoEffect(BicycleOptimizeType bot) { assertEquals(TimeSlopeSafetyTriangle.DEFAULT, req1.preferences().bike().optimizeTriangle()); } + @Test + void testPassThroughPoints() { + TransitIdMapper.clearFixedFeedId(); + + final List PTP1 = List.of(stop1, stop2, stop3).stream().map(STOP_TO_ID).toList(); + final List PTP2 = List.of(stop2, stop3, stop1).stream().map(STOP_TO_ID).toList(); + final Map arguments = Map.of( + "passThroughPoints", + List.of(Map.of("name", "PTP1", "placeIds", PTP1), Map.of("placeIds", PTP2, "name", "PTP2")) + ); + + final List points = TripRequestMapper + .createRequest(executionContext(arguments)) + .getPassThroughPoints(); + assertEquals(PTP1, points.get(0).stopLocations().stream().map(STOP_TO_ID).toList()); + assertEquals("PTP1", points.get(0).name()); + assertEquals(PTP2, points.get(1).stopLocations().stream().map(STOP_TO_ID).toList()); + assertEquals("PTP2", points.get(1).name()); + } + + @Test + void testPassThroughPointsNoMatch() { + TransitIdMapper.clearFixedFeedId(); + + final Map arguments = Map.of( + "passThroughPoints", + List.of(Map.of("placeIds", List.of("F:XX:NonExisting"))) + ); + + final RuntimeException ex = assertThrows( + RuntimeException.class, + () -> TripRequestMapper.createRequest(executionContext(arguments)) + ); + assertEquals("No match for F:XX:NonExisting.", ex.getMessage()); + } + private DataFetchingEnvironment executionContext(Map arguments) { ExecutionInput executionInput = ExecutionInput .newExecutionInput() @@ -265,4 +350,14 @@ private DataFetchingEnvironment executionContext(Map arguments) return env; } + + private static List itineraryPatterns(final Itinerary itinerary) { + return itinerary + .getLegs() + .stream() + .filter(Leg::isScheduledTransitLeg) + .map(Leg::asScheduledTransitLeg) + .map(ScheduledTransitLeg::getTripPattern) + .collect(toList()); + } } diff --git a/src/ext/graphql/transmodelapi/schema.graphql b/src/ext/graphql/transmodelapi/schema.graphql index f568c46f7fc..28ca9cd129b 100644 --- a/src/ext/graphql/transmodelapi/schema.graphql +++ b/src/ext/graphql/transmodelapi/schema.graphql @@ -818,6 +818,8 @@ type QueryType { numTripPatterns: Int = 50, "Use the cursor to go to the next \"page\" of itineraries. Copy the cursor from the last response and keep the original request as is. This will enable you to search for itineraries in the next or previous time-window." pageCursor: String, + "The list of points the journey is required to pass through." + passThroughPoints: [PassThroughPoint!], """ Whether non-optimal transit paths at the destination should be returned. Let c be the existing minimum pareto optimal generalized-cost to beat. Then a trip with cost c' is @@ -1935,6 +1937,18 @@ input Modes { transportModes: [TransportModes] } +"Defines one point which the journey must pass through." +input PassThroughPoint { + "Optional name of the pass-through point for debugging and logging. It is not used in routing." + name: String + """ + The list of *stop location ids* which define the pass-through point. At least one id is required. + Quay, StopPlace, multimodal StopPlace, and GroupOfStopPlaces are supported location types. + The journey must pass through at least one of these entities - not all of them. + """ + placeIds: [String!] +} + "A combination of street mode and penalty for time and cost." input PenaltyForStreetMode { """ @@ -2028,7 +2042,7 @@ input ViaLocationInput { maxSlack: Duration = "PT1H" "The minimum time the user wants to stay in the via location before continuing his journey" minSlack: Duration = "PT5M" - "The name of the location. This is pass-through informationand is not used in routing." + "The name of the location. This is pass-through information and is not used in routing." name: String "The id of an element in the OTP model. Currently supports Quay, StopPlace, multimodal StopPlace, and GroupOfStopPlaces." place: String diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PassThroughLocationMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PassThroughLocationMapper.java new file mode 100644 index 00000000000..76bd1464e0f --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/PassThroughLocationMapper.java @@ -0,0 +1,48 @@ +package org.opentripplanner.ext.transmodelapi.mapping; + +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.opentripplanner.routing.api.request.PassThroughPoint; +import org.opentripplanner.transit.service.TransitService; + +class PassThroughLocationMapper { + + static List toLocations( + final TransitService transitService, + final List> passThroughPoints + ) { + return passThroughPoints + .stream() + .map(p -> handlePoint(transitService, p)) + .filter(Objects::nonNull) + .collect(toList()); + // TODO Propagate an error if a stopplace is unknown and fails lookup. + } + + private static PassThroughPoint handlePoint( + final TransitService transitService, + Map map + ) { + final List stops = (List) map.get("placeIds"); + final String name = (String) map.get("name"); + if (stops == null) { + return null; + } + + return stops + .stream() + .map(TransitIdMapper::mapIDToDomain) + .flatMap(id -> { + var stopLocations = transitService.getStopOrChildStops(id); + if (stopLocations.isEmpty()) { + throw new RuntimeException("No match for %s.".formatted(id)); + } + return stopLocations.stream(); + }) + .collect(collectingAndThen(toList(), sls -> new PassThroughPoint(sls, name))); + } +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapper.java index 42976bd2a2f..615439e7957 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapper.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TransitIdMapper.java @@ -98,4 +98,15 @@ public static String setupFixedFeedId(Collection + * For use from tests only. + * + * @see #setupFixedFeedId(Collection) + */ + public static void clearFixedFeedId() { + fixedFeedId = null; + } } diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapper.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapper.java index e1c7d750f26..2ee2d5b2eda 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapper.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/mapping/TripRequestMapper.java @@ -17,6 +17,7 @@ import org.opentripplanner.routing.api.request.RouteRequest; import org.opentripplanner.standalone.api.OtpServerRequestContext; import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.service.TransitService; public class TripRequestMapper { @@ -40,6 +41,13 @@ public static RouteRequest createRequest(DataFetchingEnvironment environment) { "to", (Map v) -> request.setTo(GenericLocationMapper.toGenericLocation(v)) ); + final TransitService transitService = context.getTransitService(); + callWith.argument( + "passThroughPoints", + (List> v) -> { + request.setPassThroughPoints(PassThroughLocationMapper.toLocations(transitService, v)); + } + ); callWith.argument( "dateTime", diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PassThroughPointInputType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PassThroughPointInputType.java new file mode 100644 index 00000000000..55873844a5f --- /dev/null +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/framework/PassThroughPointInputType.java @@ -0,0 +1,39 @@ +package org.opentripplanner.ext.transmodelapi.model.framework; + +import graphql.Scalars; +import graphql.schema.GraphQLInputObjectField; +import graphql.schema.GraphQLInputObjectType; +import graphql.schema.GraphQLList; +import graphql.schema.GraphQLNonNull; + +public class PassThroughPointInputType { + + public static final GraphQLInputObjectType INPUT_TYPE = GraphQLInputObjectType + .newInputObject() + .name("PassThroughPoint") + .description("Defines one point which the journey must pass through.") + .field( + GraphQLInputObjectField + .newInputObjectField() + .name("name") + .description( + "Optional name of the pass-through point for debugging and logging. It is not used in routing." + ) + .type(Scalars.GraphQLString) + .build() + ) + .field( + GraphQLInputObjectField + .newInputObjectField() + .name("placeIds") + .description( + """ + The list of *stop location ids* which define the pass-through point. At least one id is required. + Quay, StopPlace, multimodal StopPlace, and GroupOfStopPlaces are supported location types. + The journey must pass through at least one of these entities - not all of them.""" + ) + .type(new GraphQLList(new GraphQLNonNull(Scalars.GraphQLString))) + .build() + ) + .build(); +} diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java index d4c4e424c43..8fa4fa31512 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/TripQuery.java @@ -15,6 +15,7 @@ import org.opentripplanner.ext.transmodelapi.model.EnumTypes; import org.opentripplanner.ext.transmodelapi.model.TransportModeSlack; import org.opentripplanner.ext.transmodelapi.model.framework.LocationInputType; +import org.opentripplanner.ext.transmodelapi.model.framework.PassThroughPointInputType; import org.opentripplanner.ext.transmodelapi.model.framework.PenaltyForStreetModeType; import org.opentripplanner.ext.transmodelapi.support.GqlUtil; import org.opentripplanner.routing.api.request.preference.RoutingPreferences; @@ -150,6 +151,14 @@ public static GraphQLFieldDefinition create( .type(new GraphQLNonNull(LocationInputType.INPUT_TYPE)) .build() ) + .argument( + GraphQLArgument + .newArgument() + .name("passThroughPoints") + .description("The list of points the journey is required to pass through.") + .type(new GraphQLList(new GraphQLNonNull(PassThroughPointInputType.INPUT_TYPE))) + .build() + ) .argument( GraphQLArgument .newArgument() diff --git a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaLocationInputType.java b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaLocationInputType.java index 278a0dbeabd..fe5a3cf19e4 100644 --- a/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaLocationInputType.java +++ b/src/ext/java/org/opentripplanner/ext/transmodelapi/model/plan/ViaLocationInputType.java @@ -26,7 +26,7 @@ public static GraphQLInputObjectType create(GqlUtil gqlUtil) { .name("name") .description( "The name of the location. This is pass-through information" + - "and is not used in routing." + " and is not used in routing." ) .type(Scalars.GraphQLString) .build() diff --git a/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java b/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java index 5b0b95c7671..18e2966e9c5 100644 --- a/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java +++ b/src/main/java/org/opentripplanner/raptor/api/request/PassThroughPoint.java @@ -4,6 +4,7 @@ import java.util.BitSet; import java.util.Objects; import java.util.stream.IntStream; +import javax.annotation.Nullable; /** * A collection of stop indexes used to define a pass through-point. @@ -11,13 +12,15 @@ public class PassThroughPoint { private final int[] stops; + private final String name; - public PassThroughPoint(int[] stops) { + public PassThroughPoint(int[] stops, @Nullable String name) { Objects.requireNonNull(stops); if (stops.length == 0) { throw new IllegalArgumentException("At least one stop is required"); } this.stops = Arrays.copyOf(stops, stops.length); + this.name = name; } /** @@ -43,6 +46,8 @@ public int hashCode() { @Override public String toString() { - return "(stops: " + Arrays.toString(stops) + ")"; + return ( + (name == null ? "(" : "(name: '" + name + "', ") + "stops: " + Arrays.toString(stops) + ")" + ); } } diff --git a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java index 729cf839a27..e938471d808 100644 --- a/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java +++ b/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java @@ -1,5 +1,8 @@ package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; +import static java.util.function.Predicate.not; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.toList; import static org.opentripplanner.raptor.api.request.Optimization.PARALLEL; import io.micrometer.core.instrument.MeterRegistry; @@ -7,16 +10,21 @@ import java.time.Instant; import java.time.ZonedDateTime; import java.util.Collection; +import java.util.List; +import java.util.Optional; import org.opentripplanner.framework.application.OTPFeature; import org.opentripplanner.raptor.api.model.RaptorAccessEgress; import org.opentripplanner.raptor.api.model.RaptorConstants; import org.opentripplanner.raptor.api.request.Optimization; +import org.opentripplanner.raptor.api.request.PassThroughPoint; +import org.opentripplanner.raptor.api.request.PassThroughPoints; import org.opentripplanner.raptor.api.request.RaptorRequest; import org.opentripplanner.raptor.api.request.RaptorRequestBuilder; import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger; import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor; import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule; import org.opentripplanner.routing.api.request.RouteRequest; +import org.opentripplanner.transit.model.site.StopLocation; public class RaptorRequestMapper { @@ -105,12 +113,25 @@ private RaptorRequest doMap() { if (preferences.transfer().maxAdditionalTransfers() != null) { searchParams.numberOfAdditionalTransfers(preferences.transfer().maxAdditionalTransfers()); } + + final Optional passThroughPoints = request + .getPassThroughPoints() + .stream() + .map(p -> { + final int[] stops = p.stopLocations().stream().mapToInt(StopLocation::getIndex).toArray(); + return new PassThroughPoint(stops, p.name()); + }) + .collect(collectingAndThen(toList(), Optional::ofNullable)) + .filter(not(List::isEmpty)) + .map(PassThroughPoints::new); + builder.withMultiCriteria(mcBuilder -> { preferences .transit() .raptor() .relaxGeneralizedCostAtDestination() .ifPresent(mcBuilder::withRelaxCostAtDestination); + passThroughPoints.ifPresent(pt -> mcBuilder.withPassThroughPoints(pt)); }); for (Optimization optimization : preferences.transit().raptor().optimizations()) { diff --git a/src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java b/src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java new file mode 100644 index 00000000000..fa63c2e5668 --- /dev/null +++ b/src/main/java/org/opentripplanner/routing/api/request/PassThroughPoint.java @@ -0,0 +1,28 @@ +package org.opentripplanner.routing.api.request; + +import java.util.List; +import javax.annotation.Nullable; +import org.opentripplanner.transit.model.site.StopLocation; + +/** + * Defines one pass-through point which the journey must pass through. + */ +public record PassThroughPoint(List stopLocations, @Nullable String name) { + /** + * Get the one or multiple stops of the pass-through point, of which only one is required to be + * passed through. + */ + @Override + public List stopLocations() { + return stopLocations; + } + + /** + * Get an optional name of the pass-through point for debugging and logging. + */ + @Override + @Nullable + public String name() { + return name; + } +} diff --git a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java index 357afbe9ff8..4dabf5c0d86 100644 --- a/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java +++ b/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java @@ -8,6 +8,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.function.Consumer; @@ -53,6 +54,8 @@ public class RouteRequest implements Cloneable, Serializable { private GenericLocation to; + private List passThroughPoints = Collections.emptyList(); + private Instant dateTime = Instant.now(); private Duration searchWindow; @@ -295,6 +298,14 @@ public void setTo(GenericLocation to) { this.to = to; } + public List getPassThroughPoints() { + return passThroughPoints; + } + + public void setPassThroughPoints(final List passThroughPoints) { + this.passThroughPoints = passThroughPoints; + } + /** * This is the time/duration in seconds from the earliest-departure-time(EDT) to * latest-departure-time(LDT). In case of a reverse search it will be the time from earliest to diff --git a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java index 31c453056af..41e28e616d8 100644 --- a/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java +++ b/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java @@ -231,6 +231,11 @@ public StopLocation getStopLocation(FeedScopedId id) { return transitModel.getStopModel().getStopLocation(id); } + @Override + public Collection getStopOrChildStops(FeedScopedId id) { + return transitModel.getStopModel().findStopOrChildStops(id); + } + @Override public Collection listStopLocationGroups() { OTPRequestTimeoutException.checkForTimeout(); diff --git a/src/main/java/org/opentripplanner/transit/service/TransitService.java b/src/main/java/org/opentripplanner/transit/service/TransitService.java index 78c867ac83e..d0664aa292d 100644 --- a/src/main/java/org/opentripplanner/transit/service/TransitService.java +++ b/src/main/java/org/opentripplanner/transit/service/TransitService.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; import org.locationtech.jts.geom.Envelope; import org.opentripplanner.ext.flex.FlexIndex; import org.opentripplanner.model.FeedInfo; @@ -98,6 +97,8 @@ public interface TransitService { StopLocation getStopLocation(FeedScopedId parseId); + Collection getStopOrChildStops(FeedScopedId id); + Collection listStopLocationGroups(); StopLocationsGroup getStopLocationsGroup(FeedScopedId id); diff --git a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java b/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java index 08a38279a4e..0938b58240d 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointTest.java @@ -11,7 +11,7 @@ class PassThroughPointTest { private static final int[] STOPS = { 2, 7, 13 }; - private final PassThroughPoint subject = new PassThroughPoint(STOPS); + private final PassThroughPoint subject = new PassThroughPoint(STOPS, "PT1"); @Test void asBitSet() { @@ -29,8 +29,8 @@ void asBitSet() { @Test void testEqualsAndHashCode() { - var same = new PassThroughPoint(STOPS); - var other = new PassThroughPoint(new int[] { 2, 7 }); + var same = new PassThroughPoint(STOPS, "PT1"); + var other = new PassThroughPoint(new int[] { 2, 7 }, "PT2"); assertEquals(subject, subject); assertEquals(same, subject); @@ -42,6 +42,7 @@ void testEqualsAndHashCode() { @Test void testToString() { - assertEquals("(stops: [2, 7, 13])", subject.toString()); + assertEquals("(name: 'PT1', stops: [2, 7, 13])", subject.toString()); + assertEquals("(stops: [2, 7, 13])", new PassThroughPoint(STOPS, null).toString()); } } diff --git a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointsTest.java b/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointsTest.java index 7968017923b..83f4dda4879 100644 --- a/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointsTest.java +++ b/src/test/java/org/opentripplanner/raptor/api/request/PassThroughPointsTest.java @@ -12,17 +12,17 @@ class PassThroughPointsTest { - private static final int[] STOPS_POINT_1 = { 2, 7, 13 }; - private static final int[] STOPS_POINT_2 = { 12 }; + private static final int[] STOPS_1 = { 2, 7, 13 }; + private static final int[] STOPS_2 = { 12 }; private final PassThroughPoints subject = new PassThroughPoints( - List.of(new PassThroughPoint(STOPS_POINT_1), new PassThroughPoint(STOPS_POINT_2)) + List.of(new PassThroughPoint(STOPS_1, "PT1"), new PassThroughPoint(STOPS_2, "PT2")) ); @Test void stream() { assertEquals( - "(stops: [2, 7, 13]), (stops: [12])", + "(name: 'PT1', stops: [2, 7, 13]), (name: 'PT2', stops: [12])", subject.stream().map(Objects::toString).collect(Collectors.joining(", ")) ); } @@ -36,10 +36,10 @@ void isEmpty() { @Test void testEqualsAndHashCode() { var same = new PassThroughPoints( - List.of(new PassThroughPoint(STOPS_POINT_1), new PassThroughPoint(STOPS_POINT_2)) + List.of(new PassThroughPoint(STOPS_1, "PT1"), new PassThroughPoint(STOPS_2, "PT2")) ); var other = new PassThroughPoints( - List.of(new PassThroughPoint(STOPS_POINT_1), new PassThroughPoint(STOPS_POINT_1)) + List.of(new PassThroughPoint(STOPS_1, "PT1"), new PassThroughPoint(STOPS_1, "PT2")) ); assertEquals(same, subject); assertNotEquals(other, subject); @@ -51,7 +51,7 @@ void testEqualsAndHashCode() { @Test void testToString() { assertEquals( - "PassThroughPoints{points: [(stops: [2, 7, 13]), (stops: [12])]}", + "PassThroughPoints{points: [(name: 'PT1', stops: [2, 7, 13]), (name: 'PT2', stops: [12])]}", subject.toString() ); } diff --git a/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java b/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java index 5d9cc03ced4..350fcc72cbd 100644 --- a/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java +++ b/src/test/java/org/opentripplanner/raptor/moduletests/J01_PassThroughTest.java @@ -91,7 +91,7 @@ public void passThroughPointOnEgress() { .withMultiCriteria(mc -> mc.withPassThroughPoints( // Include desired pass-through point in the request - new PassThroughPoints(List.of(new PassThroughPoint(new int[] { STOP_D }))) + new PassThroughPoints(List.of(new PassThroughPoint(new int[] { STOP_D }, "PT1"))) ) ) .searchParams() @@ -127,7 +127,7 @@ public void passThroughPointOnAccess() { .withMultiCriteria(mc -> // Include desired pass-through point in the request mc.withPassThroughPoints( - new PassThroughPoints(List.of(new PassThroughPoint(new int[] { STOP_A }))) + new PassThroughPoints(List.of(new PassThroughPoint(new int[] { STOP_A }, "PT1"))) ) ) .searchParams() @@ -164,7 +164,7 @@ public void passThroughPointInTheMiddle() { .withMultiCriteria(mc -> // Include desired pass-through point in the request mc.withPassThroughPoints( - new PassThroughPoints(List.of(new PassThroughPoint(new int[] { STOP_C }))) + new PassThroughPoints(List.of(new PassThroughPoint(new int[] { STOP_C }, "PT1"))) ) ) .searchParams() @@ -203,8 +203,8 @@ public void multiplePassThroughPoints() { // Include desired pass-through point in the request new PassThroughPoints( List.of( - new PassThroughPoint(new int[] { STOP_B }), - new PassThroughPoint(new int[] { STOP_D }) + new PassThroughPoint(new int[] { STOP_B }, "PT1"), + new PassThroughPoint(new int[] { STOP_D }, "PT2") ) ) ) @@ -243,8 +243,8 @@ public void passThroughOrder() { // Include desired pass-through point in the request new PassThroughPoints( List.of( - new PassThroughPoint(new int[] { STOP_B }), - new PassThroughPoint(new int[] { STOP_C }) + new PassThroughPoint(new int[] { STOP_B }, "PT1"), + new PassThroughPoint(new int[] { STOP_C }, "PT2") ) ) ) @@ -282,7 +282,7 @@ public void passThroughGroup() { // Include desired pass-through point in the request new PassThroughPoints( // Both STOP_B and STOP_C is a valid pass-through point - List.of(new PassThroughPoint(new int[] { STOP_B, STOP_C })) + List.of(new PassThroughPoint(new int[] { STOP_B, STOP_C }, "PT1")) ) ) ) diff --git a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsServiceTest.java b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsServiceTest.java index 195bb39b453..bfe4853a3d8 100644 --- a/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsServiceTest.java +++ b/src/test/java/org/opentripplanner/raptor/rangeraptor/multicriteria/passthrough/BitSetPassThroughPointsServiceTest.java @@ -32,7 +32,9 @@ class BitSetPassThroughPointsServiceTest { private static final int STOP_31 = 6; private static final PassThroughPointsService SUBJECT = BitSetPassThroughPointsService.of( - new PassThroughPoints(List.of(new PassThroughPoint(STOPS_1), new PassThroughPoint(STOPS_2))) + new PassThroughPoints( + List.of(new PassThroughPoint(STOPS_1, "PT1"), new PassThroughPoint(STOPS_2, "PT2")) + ) ); /** * We expect the c2 value at the destination to be the same as the number of pass-through