diff --git a/application/src/ext/java/org/opentripplanner/ext/parkAndRideApi/ParkAndRideResource.java b/application/src/ext/java/org/opentripplanner/ext/parkAndRideApi/ParkAndRideResource.java index 747ba0617ec..611f4d46420 100644 --- a/application/src/ext/java/org/opentripplanner/ext/parkAndRideApi/ParkAndRideResource.java +++ b/application/src/ext/java/org/opentripplanner/ext/parkAndRideApi/ParkAndRideResource.java @@ -42,7 +42,7 @@ public ParkAndRideResource( // - serverContext.graphFinder(). This needs at least a comment! // - This can be replaced with a search done with the SiteRepository // - if we have a radius search there. - this.graphFinder = new DirectGraphFinder(serverContext.transitService()::findRegularStops); + this.graphFinder = new DirectGraphFinder(serverContext.transitService()::findRegularStopsByBoundingBox); } /** Envelopes are in latitude, longitude format */ diff --git a/application/src/ext/java/org/opentripplanner/ext/restapi/resources/IndexAPI.java b/application/src/ext/java/org/opentripplanner/ext/restapi/resources/IndexAPI.java index 7dbfabed156..b9c049f547e 100644 --- a/application/src/ext/java/org/opentripplanner/ext/restapi/resources/IndexAPI.java +++ b/application/src/ext/java/org/opentripplanner/ext/restapi/resources/IndexAPI.java @@ -200,7 +200,7 @@ public List getStopsInRadius( radius = Math.min(radius, MAX_STOP_SEARCH_RADIUS); - return new DirectGraphFinder(serverContext.transitService()::findRegularStops) + return new DirectGraphFinder(serverContext.transitService()::findRegularStopsByBoundingBox) .findClosestStops(new Coordinate(lon, lat), radius) .stream() .map(it -> StopMapper.mapToApiShort(it.stop, it.distance)) @@ -221,10 +221,9 @@ public List getStopsInRadius( new Coordinate(maxLon, maxLat) ); - var stops = transitService().findRegularStops(envelope); + var stops = transitService().findRegularStopsByBoundingBox(envelope); return stops .stream() - .filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate())) .map(StopMapper::mapToApiShort) .toList(); } diff --git a/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java b/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java index 141157f8f3e..c1e03cdefca 100644 --- a/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java +++ b/application/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java @@ -44,7 +44,7 @@ public StopsLayerBuilder( protected List getGeometries(Envelope query) { return transitService - .findRegularStops(query) + .findRegularStopsByBoundingBox(query) .stream() .filter(filter) .map(stop -> { diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java index d02d7b59ffa..c04ed6c75ed 100644 --- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java +++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java @@ -749,9 +749,8 @@ public DataFetcher> stopsByBbox() { ); Stream stopStream = getTransitService(environment) - .findRegularStops(envelope) - .stream() - .filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate())); + .findRegularStopsByBoundingBox(envelope) + .stream(); if (args.getGraphQLFeeds() != null) { List feedIds = args.getGraphQLFeeds(); diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java index 922f9f5244b..0561ec9de85 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/TransmodelGraphQLSchema.java @@ -3,6 +3,7 @@ import static java.lang.Boolean.TRUE; import static java.util.Collections.emptyList; import static org.opentripplanner.apis.transmodel.mapping.SeverityMapper.getTransmodelSeverity; +import static org.opentripplanner.apis.transmodel.mapping.TransitIdMapper.mapIDToDomain; import static org.opentripplanner.apis.transmodel.mapping.TransitIdMapper.mapIDsToDomainNullSafe; import static org.opentripplanner.apis.transmodel.model.EnumTypes.FILTER_PLACE_TYPE_ENUM; import static org.opentripplanner.apis.transmodel.model.EnumTypes.MULTI_MODAL_MODE; @@ -115,6 +116,7 @@ import org.opentripplanner.routing.graphfinder.PlaceType; import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace; import org.opentripplanner.transit.api.model.FilterValues; +import org.opentripplanner.transit.api.request.FindRegularStopsByBoundingBoxRequest; import org.opentripplanner.transit.api.request.TripRequest; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.framework.FeedScopedId; @@ -439,10 +441,7 @@ private GraphQLSchema create() { .build() ) .dataFetcher(env -> - StopPlaceType.fetchStopPlaceById( - TransitIdMapper.mapIDToDomain(env.getArgument("id")), - env - ) + StopPlaceType.fetchStopPlaceById(mapIDToDomain(env.getArgument("id")), env) ) .build() ) @@ -576,7 +575,7 @@ private GraphQLSchema create() { .dataFetcher(environment -> GqlUtil .getTransitService(environment) - .getStopLocation(TransitIdMapper.mapIDToDomain(environment.getArgument("id"))) + .getStopLocation(mapIDToDomain(environment.getArgument("id"))) ) .build() ) @@ -610,7 +609,7 @@ private GraphQLSchema create() { } TransitService transitService = GqlUtil.getTransitService(environment); return ((List) environment.getArgument("ids")).stream() - .map(id -> transitService.getStopLocation(TransitIdMapper.mapIDToDomain(id))) + .map(id -> transitService.getStopLocation(mapIDToDomain(id))) .collect(Collectors.toList()); } if (environment.getArgument("name") == null) { @@ -661,7 +660,14 @@ private GraphQLSchema create() { .build() ) .argument( - GraphQLArgument.newArgument().name("authority").type(Scalars.GraphQLString).build() + GraphQLArgument + .newArgument() + .name("authority") + .deprecate( + "This is the Transmodel namespace or the GTFS feedID - avoid using this. Request a new field if necessary." + ) + .type(Scalars.GraphQLString) + .build() ) .argument( GraphQLArgument @@ -669,7 +675,7 @@ private GraphQLSchema create() { .name("filterByInUse") .description("If true only quays with at least one visiting line are included.") .type(Scalars.GraphQLBoolean) - .defaultValue(Boolean.FALSE) + .defaultValueProgrammatic(Boolean.FALSE) .build() ) .dataFetcher(environment -> { @@ -683,24 +689,19 @@ private GraphQLSchema create() { environment.getArgument("maximumLatitude") ) ); + + var authority = environment.getArgument("authority"); + var filterInUse = environment.getArgument("filterByInUse"); + + FindRegularStopsByBoundingBoxRequest findRegularStopsByBoundingBoxRequest = FindRegularStopsByBoundingBoxRequest + .of(envelope) + .withFeedId(authority) + .filterByInUse(filterInUse) + .build(); + return GqlUtil .getTransitService(environment) - .findRegularStops(envelope) - .stream() - .filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate())) - .filter(stop -> - environment.getArgument("authority") == null || - stop.getId().getFeedId().equalsIgnoreCase(environment.getArgument("authority")) - ) - .filter(stop -> { - boolean filterByInUse = TRUE.equals(environment.getArgument("filterByInUse")); - boolean inUse = !GqlUtil - .getTransitService(environment) - .findPatterns(stop, true) - .isEmpty(); - return !filterByInUse || inUse; - }) - .collect(Collectors.toList()); + .findRegularStopsByBoundingBox(findRegularStopsByBoundingBoxRequest); }) .build() ) @@ -1438,7 +1439,7 @@ private GraphQLSchema create() { .build() ) .dataFetcher(environment -> { - var bikeParkId = TransitIdMapper.mapIDToDomain(environment.getArgument("id")); + var bikeParkId = mapIDToDomain(environment.getArgument("id")); return GqlUtil .getVehicleParkingService(environment) .listBikeParks() @@ -1573,7 +1574,7 @@ private GraphQLSchema create() { return GqlUtil .getTransitService(environment) .getTransitAlertService() - .getAlertById(TransitIdMapper.mapIDToDomain(situationNumber)); + .getAlertById(mapIDToDomain(situationNumber)); }) .build() ) diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java index fa755e8c6f7..9b8fd25c68a 100644 --- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java +++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java @@ -546,9 +546,8 @@ public static Collection fetchStopPlaces( ); Stream stations = transitService - .findRegularStops(envelope) + .findRegularStopsByBoundingBox(envelope) .stream() - .filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate())) .map(StopLocation::getParentStation) .filter(Objects::nonNull) .distinct(); diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java index d19c25a1f47..2a1ece7d6d4 100644 --- a/application/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java +++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/GraphInspectorVectorTileResource.java @@ -182,7 +182,7 @@ private static LayerBuilder createLayerBuilder( case RegularStop -> new StopLayerBuilder<>( layerParameters, locale, - e -> context.transitService().findRegularStops(e) + e -> context.transitService().findRegularStopsByBoundingBox(e) ); case AreaStop -> new StopLayerBuilder<>( layerParameters, diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StraightLineNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StraightLineNearbyStopFinder.java index 5b304d0d20c..ccdcb68446b 100644 --- a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StraightLineNearbyStopFinder.java +++ b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StraightLineNearbyStopFinder.java @@ -22,7 +22,7 @@ public StraightLineNearbyStopFinder(TransitService transitService, Duration dura // We need to accommodate straight line distance (in meters) but when streets are present we // use an earliest arrival search, which optimizes on time. Ideally we'd specify in meters, // but we don't have much of a choice here. Use the default walking speed to convert. - this.directGraphFinder = new DirectGraphFinder(transitService::findRegularStops); + this.directGraphFinder = new DirectGraphFinder(transitService::findRegularStopsByBoundingBox); } /** diff --git a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java index b5b39ddee18..befa8ff9c22 100644 --- a/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java +++ b/application/src/main/java/org/opentripplanner/standalone/api/OtpServerRequestContext.java @@ -120,7 +120,7 @@ public interface OtpServerRequestContext { TraverseVisitor traverseVisitor(); default GraphFinder graphFinder() { - return GraphFinder.getInstance(graph(), transitService()::findRegularStops); + return GraphFinder.getInstance(graph(), transitService()::findRegularStopsByBoundingBox); } FlexParameters flexParameters(); diff --git a/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java b/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java index 391b2531231..63befe3add6 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java +++ b/application/src/main/java/org/opentripplanner/transit/api/model/FilterValues.java @@ -1,8 +1,8 @@ package org.opentripplanner.transit.api.model; -import com.beust.jcommander.internal.Nullable; import java.util.Collection; import java.util.NoSuchElementException; +import javax.annotation.Nullable; import org.opentripplanner.transit.model.framework.FeedScopedId; import org.opentripplanner.transit.service.TransitService; diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/FindRegularStopsByBoundingBoxRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/FindRegularStopsByBoundingBoxRequest.java new file mode 100644 index 00000000000..476e23d7cd8 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/api/request/FindRegularStopsByBoundingBoxRequest.java @@ -0,0 +1,48 @@ +package org.opentripplanner.transit.api.request; + +import javax.annotation.Nullable; +import org.locationtech.jts.geom.Envelope; +import org.opentripplanner.transit.model.site.RegularStop; + +/** + * A request for {@link RegularStop}s within a bounding box. + *

+ * This request is used to retrieve {@link RegularStop}s that are within a provided bounding box and + * match the other criteria. + */ +public class FindRegularStopsByBoundingBoxRequest { + + private final Envelope envelope; + + @Nullable + private final String feedId; + + private final boolean filterByInUse; + + FindRegularStopsByBoundingBoxRequest( + Envelope envelope, + @Nullable String feedId, + boolean filterByInUse + ) { + this.envelope = envelope; + this.feedId = feedId; + this.filterByInUse = filterByInUse; + } + + public static FindRegularStopsByBoundingBoxRequestBuilder of(Envelope envelope) { + return new FindRegularStopsByBoundingBoxRequestBuilder(envelope); + } + + public Envelope envelope() { + return envelope; + } + + @Nullable + public String feedId() { + return feedId; + } + + public boolean filterByInUse() { + return filterByInUse; + } +} diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/FindRegularStopsByBoundingBoxRequestBuilder.java b/application/src/main/java/org/opentripplanner/transit/api/request/FindRegularStopsByBoundingBoxRequestBuilder.java new file mode 100644 index 00000000000..49d31c33db9 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/api/request/FindRegularStopsByBoundingBoxRequestBuilder.java @@ -0,0 +1,32 @@ +package org.opentripplanner.transit.api.request; + +import javax.annotation.Nullable; +import org.locationtech.jts.geom.Envelope; + +public class FindRegularStopsByBoundingBoxRequestBuilder { + + private final Envelope envelope; + + @Nullable + private String feedId; + + private boolean filterByInUse = false; + + FindRegularStopsByBoundingBoxRequestBuilder(Envelope envelope) { + this.envelope = envelope; + } + + public FindRegularStopsByBoundingBoxRequestBuilder withFeedId(@Nullable String feedId) { + this.feedId = feedId; + return this; + } + + public FindRegularStopsByBoundingBoxRequestBuilder filterByInUse(boolean filterByInUse) { + this.filterByInUse = filterByInUse; + return this; + } + + public FindRegularStopsByBoundingBoxRequest build() { + return new FindRegularStopsByBoundingBoxRequest(envelope, feedId, filterByInUse); + } +} diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java index c61bb8ad107..54dd1c7aea6 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequest.java @@ -23,7 +23,7 @@ public class TripOnServiceDateRequest { private final FilterValues netexInternalPlanningCodes; private final FilterValues alterations; - protected TripOnServiceDateRequest( + TripOnServiceDateRequest( RequiredFilterValues serviceDates, FilterValues agencies, FilterValues routes, @@ -41,7 +41,7 @@ protected TripOnServiceDateRequest( this.alterations = alterations; } - public static TripOnServiceDateRequestBuilder of(RequiredFilterValues serviceDates) { + public static TripOnServiceDateRequestBuilder of(RequiredFilterValues serviceDates) { return new TripOnServiceDateRequestBuilder(serviceDates); } diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequestBuilder.java b/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequestBuilder.java index 534557c15d8..3181819a400 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequestBuilder.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/TripOnServiceDateRequestBuilder.java @@ -30,9 +30,9 @@ public class TripOnServiceDateRequestBuilder { "alterations", List.of() ); - private RequiredFilterValues serviceDates; + private final RequiredFilterValues serviceDates; - protected TripOnServiceDateRequestBuilder(RequiredFilterValues serviceDates) { + TripOnServiceDateRequestBuilder(RequiredFilterValues serviceDates) { this.serviceDates = serviceDates; } diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java b/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java index c73e800582b..5e05b472937 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/TripRequest.java @@ -17,7 +17,7 @@ public class TripRequest { private final FilterValues netexInternalPlanningCodes; private final FilterValues serviceDates; - protected TripRequest( + TripRequest( FilterValues agencies, FilterValues routes, FilterValues netexInternalPlanningCodes, diff --git a/application/src/main/java/org/opentripplanner/transit/api/request/TripRequestBuilder.java b/application/src/main/java/org/opentripplanner/transit/api/request/TripRequestBuilder.java index 3a2f80a3e34..32ca31d5cd6 100644 --- a/application/src/main/java/org/opentripplanner/transit/api/request/TripRequestBuilder.java +++ b/application/src/main/java/org/opentripplanner/transit/api/request/TripRequestBuilder.java @@ -21,7 +21,7 @@ public class TripRequestBuilder { List.of() ); - protected TripRequestBuilder() {} + TripRequestBuilder() {} public TripRequestBuilder withAgencies(FilterValues agencies) { this.agencies = agencies; diff --git a/application/src/main/java/org/opentripplanner/transit/model/filter/expr/ExpressionBuilder.java b/application/src/main/java/org/opentripplanner/transit/model/filter/expr/ExpressionBuilder.java index f2910a4c8d2..87533ab1b5e 100644 --- a/application/src/main/java/org/opentripplanner/transit/model/filter/expr/ExpressionBuilder.java +++ b/application/src/main/java/org/opentripplanner/transit/model/filter/expr/ExpressionBuilder.java @@ -22,6 +22,11 @@ public static ExpressionBuilder of() { return new ExpressionBuilder<>(); } + public ExpressionBuilder matches(Matcher matcher) { + matchers.add(matcher); + return this; + } + public ExpressionBuilder atLeastOneMatch( FilterValues filterValues, Function> matcherProvider diff --git a/application/src/main/java/org/opentripplanner/transit/model/filter/expr/GenericUnaryMatcher.java b/application/src/main/java/org/opentripplanner/transit/model/filter/expr/GenericUnaryMatcher.java new file mode 100644 index 00000000000..0ac6c5f17ee --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/model/filter/expr/GenericUnaryMatcher.java @@ -0,0 +1,33 @@ +package org.opentripplanner.transit.model.filter.expr; + +import java.util.function.Predicate; + +/** + * A generic matcher that takes a predicate function that returns a boolean given the matched type. + *

+ * @param The type of the entity being matched. + */ +public class GenericUnaryMatcher implements Matcher { + + private final String typeName; + private final Predicate matchPredicate; + + /** + * @param typeName The typeName appears in the toString for easier debugging. + * @param matchPredicate The predicate that will be used to test the entity being matched. + */ + public GenericUnaryMatcher(String typeName, Predicate matchPredicate) { + this.typeName = typeName; + this.matchPredicate = matchPredicate; + } + + @Override + public boolean match(T entity) { + return matchPredicate.test(entity); + } + + @Override + public String toString() { + return "GenericUnaryMatcher: " + typeName; + } +} diff --git a/application/src/main/java/org/opentripplanner/transit/model/filter/transit/RegularStopMatcherFactory.java b/application/src/main/java/org/opentripplanner/transit/model/filter/transit/RegularStopMatcherFactory.java new file mode 100644 index 00000000000..261e7057d95 --- /dev/null +++ b/application/src/main/java/org/opentripplanner/transit/model/filter/transit/RegularStopMatcherFactory.java @@ -0,0 +1,47 @@ +package org.opentripplanner.transit.model.filter.transit; + +import java.util.function.Predicate; +import org.opentripplanner.transit.api.request.FindRegularStopsByBoundingBoxRequest; +import org.opentripplanner.transit.model.filter.expr.EqualityMatcher; +import org.opentripplanner.transit.model.filter.expr.ExpressionBuilder; +import org.opentripplanner.transit.model.filter.expr.GenericUnaryMatcher; +import org.opentripplanner.transit.model.filter.expr.Matcher; +import org.opentripplanner.transit.model.site.RegularStop; + +/** + * A factory for creating matchers for {@link RegularStop} objects. + *

+ * This factory is used to create matchers for {@link RegularStop} objects based on a request. The + * resulting matcher can be used to filter a list of {@link RegularStop} objects. + */ +public class RegularStopMatcherFactory { + + /** + * Creates a matcher that filters {@link RegularStop} objects with the provided {@code request} + * and {@code inUseProvider}. The {@code inUseProvider} is used to determine if a {@link RegularStop} is + * in use. The inUseProvider is an injected function, because the check is done by the transit service + * which has access to all stops and routes. A stop is used if it has routes visiting the stop. + */ + public static Matcher of( + FindRegularStopsByBoundingBoxRequest request, + Predicate inUseProvider + ) { + ExpressionBuilder expr = ExpressionBuilder.of(); + + if (request.feedId() != null) { + expr.matches(feedId(request.feedId())); + } + if (request.filterByInUse()) { + expr.matches(inUseMatcher(inUseProvider)); + } + return expr.build(); + } + + static Matcher feedId(String feedId) { + return new EqualityMatcher<>("feedId", feedId, stop -> stop.getId().getFeedId()); + } + + static Matcher inUseMatcher(Predicate inUseProvider) { + return new GenericUnaryMatcher<>("inUse", inUseProvider); + } +} diff --git a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java index d35977cae74..77f63729fd9 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java +++ b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java @@ -34,11 +34,13 @@ import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.routing.stoptimes.ArrivalDeparture; import org.opentripplanner.routing.stoptimes.StopTimesHelper; +import org.opentripplanner.transit.api.request.FindRegularStopsByBoundingBoxRequest; import org.opentripplanner.transit.api.request.TripOnServiceDateRequest; import org.opentripplanner.transit.api.request.TripRequest; import org.opentripplanner.transit.model.basic.Notice; import org.opentripplanner.transit.model.basic.TransitMode; import org.opentripplanner.transit.model.filter.expr.Matcher; +import org.opentripplanner.transit.model.filter.transit.RegularStopMatcherFactory; import org.opentripplanner.transit.model.filter.transit.TripMatcherFactory; import org.opentripplanner.transit.model.filter.transit.TripOnServiceDateMatcherFactory; import org.opentripplanner.transit.model.framework.AbstractTransitEntity; @@ -682,11 +684,27 @@ public ZonedDateTime getTransitServiceStarts() { } @Override - public Collection findRegularStops(Envelope envelope) { + public Collection findRegularStopsByBoundingBox(Envelope envelope) { OTPRequestTimeoutException.checkForTimeout(); return timetableRepository.getSiteRepository().findRegularStops(envelope); } + @Override + public Collection findRegularStopsByBoundingBox( + FindRegularStopsByBoundingBoxRequest request + ) { + OTPRequestTimeoutException.checkForTimeout(); + Collection stops = timetableRepository + .getSiteRepository() + .findRegularStops(request.envelope()); + + Matcher matcher = RegularStopMatcherFactory.of( + request, + stop -> !findPatterns(stop, true).isEmpty() + ); + return stops.stream().filter(matcher::match).toList(); + } + @Override public Collection findAreaStops(Envelope envelope) { OTPRequestTimeoutException.checkForTimeout(); diff --git a/application/src/main/java/org/opentripplanner/transit/service/StopModelIndex.java b/application/src/main/java/org/opentripplanner/transit/service/StopModelIndex.java index 13a8c0d278d..effa8f95f7e 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/StopModelIndex.java +++ b/application/src/main/java/org/opentripplanner/transit/service/StopModelIndex.java @@ -71,10 +71,17 @@ class SiteRepositoryIndex { } /** - * Find a regular stop in the spatial index + * Find a regular stop in the spatial index, where the stop is inside of the passed Envelope. + * + * @param envelope - The {@link Envelope} to search for stops in. + * @return A collection of {@link RegularStop}s that are inside of the passed envelope. */ Collection findRegularStops(Envelope envelope) { - return regularStopSpatialIndex.query(envelope); + return regularStopSpatialIndex + .query(envelope) + .stream() + .filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate())) + .toList(); } MultiModalStation getMultiModalStationForStation(Station station) { diff --git a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java index c1bba355cdf..c423c18d770 100644 --- a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java +++ b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java @@ -24,6 +24,7 @@ import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer; import org.opentripplanner.routing.services.TransitAlertService; import org.opentripplanner.routing.stoptimes.ArrivalDeparture; +import org.opentripplanner.transit.api.request.FindRegularStopsByBoundingBoxRequest; import org.opentripplanner.transit.api.request.TripOnServiceDateRequest; import org.opentripplanner.transit.api.request.TripRequest; import org.opentripplanner.transit.model.basic.Notice; @@ -266,7 +267,7 @@ List findTripTimeOnDate( boolean transitFeedCovers(Instant dateTime); - Collection findRegularStops(Envelope envelope); + Collection findRegularStopsByBoundingBox(Envelope envelope); Collection findAreaStops(Envelope envelope); @@ -282,6 +283,7 @@ List findTripTimeOnDate( * So, if more patterns of mode BUS than RAIL visit the group, the result will be [BUS,RAIL]. */ List findTransitModes(StopLocationsGroup station); + /** * For a {@link StopLocation} return its modes. *

@@ -302,18 +304,13 @@ List findTripTimeOnDate( Map getServiceCodesRunningForDate(); /** - * Returns a list of TripOnServiceDates that match the filtering defined in the request. - * - * @param request - A TripOnServiceDateRequest object with filtering defined. - * @return - A list of TripOnServiceDates + * Returns a list of {@link TripOnServiceDate}s that match the filtering defined in the request. */ List findTripsOnServiceDate(TripOnServiceDateRequest request); /** - * Returns a list of Trips that match the filtering defined in the request. + * Returns a list of {@link Trip}s that match the filtering defined in the request. * - * @param request - A TripRequest object with filtering defined. - * @return - A list of Trips */ List getTrips(TripRequest request); @@ -324,4 +321,12 @@ List findTripTimeOnDate( * @return true if the trip exists, false otherwise */ boolean containsTrip(FeedScopedId id); + + /** + * Returns a list of {@link RegularStop}s that lay within a bounding box and match the other criteria + * in the request object. + */ + Collection findRegularStopsByBoundingBox( + FindRegularStopsByBoundingBoxRequest request + ); } diff --git a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql index 783cc3e7948..46f70cfccd4 100644 --- a/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql +++ b/application/src/main/resources/org/opentripplanner/apis/transmodel/schema.graphql @@ -702,7 +702,7 @@ type QueryType { quays(ids: [String], name: String): [Quay]! @timingData "Get all quays within the specified bounding box" quaysByBbox( - authority: String, + authority: String @deprecated(reason : "This is the Transmodel namespace or the GTFS feedID - avoid using this. Request a new field if necessary."), "If true only quays with at least one visiting line are included." filterByInUse: Boolean = false, maximumLatitude: Float!, diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java index 43dd10dbdce..17193ad02c1 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapperTest.java @@ -65,7 +65,7 @@ class LegacyRouteRequestMapperTest implements PlanTestConstants { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), - GraphFinder.getInstance(graph, transitService::findRegularStops), + GraphFinder.getInstance(graph, transitService::findRegularStopsByBoundingBox), new RouteRequest() ); } diff --git a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java index 34cb865c81a..925e5009e77 100644 --- a/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java +++ b/application/src/test/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapperTest.java @@ -75,7 +75,7 @@ class RouteRequestMapperTest { new DefaultVehicleRentalService(), new DefaultVehicleParkingService(new DefaultVehicleParkingRepository()), new DefaultRealtimeVehicleService(transitService), - GraphFinder.getInstance(graph, transitService::findRegularStops), + GraphFinder.getInstance(graph, transitService::findRegularStopsByBoundingBox), new RouteRequest() ); } diff --git a/application/src/test/java/org/opentripplanner/routing/graph/DefaultRoutingServiceTest.java b/application/src/test/java/org/opentripplanner/routing/graph/DefaultRoutingServiceTest.java index ab897e3410b..48363a98c88 100644 --- a/application/src/test/java/org/opentripplanner/routing/graph/DefaultRoutingServiceTest.java +++ b/application/src/test/java/org/opentripplanner/routing/graph/DefaultRoutingServiceTest.java @@ -115,7 +115,7 @@ public void testSpatialIndex() { SphericalDistanceLibrary.metersToLonDegrees(100, stopJ.getLat()), SphericalDistanceLibrary.metersToDegrees(100) ); - Collection stops = transitService.findRegularStops(env); + Collection stops = transitService.findRegularStopsByBoundingBox(env); assertTrue(stops.contains(stopJ)); assertTrue(stops.contains(stopL)); assertTrue(stops.contains(stopM)); diff --git a/application/src/test/java/org/opentripplanner/transit/model/filter/expr/GenericUnaryMatcherTest.java b/application/src/test/java/org/opentripplanner/transit/model/filter/expr/GenericUnaryMatcherTest.java new file mode 100644 index 00000000000..ef93df53dbd --- /dev/null +++ b/application/src/test/java/org/opentripplanner/transit/model/filter/expr/GenericUnaryMatcherTest.java @@ -0,0 +1,15 @@ +package org.opentripplanner.transit.model.filter.expr; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class GenericUnaryMatcherTest { + + @Test + void testMatches() { + var matcher = new GenericUnaryMatcher<>("int", i -> i.equals(42)); + assertTrue(matcher.match(42)); + assertFalse(matcher.match(43)); + } +} diff --git a/application/src/test/java/org/opentripplanner/transit/model/filter/transit/RegularStopMatcherFactoryTest.java b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/RegularStopMatcherFactoryTest.java new file mode 100644 index 00000000000..5cfeaf2780f --- /dev/null +++ b/application/src/test/java/org/opentripplanner/transit/model/filter/transit/RegularStopMatcherFactoryTest.java @@ -0,0 +1,44 @@ +package org.opentripplanner.transit.model.filter.transit; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.site.RegularStop; + +class RegularStopMatcherFactoryTest { + + private static RegularStop stop1; + private static RegularStop stop2; + + @BeforeAll + static void setup() { + stop1 = + RegularStop + .of(new FeedScopedId("agency", "stopId"), new AtomicInteger()::getAndIncrement) + .build(); + + stop2 = + RegularStop + .of(new FeedScopedId("otherAgency", "otherStopId"), new AtomicInteger()::getAndIncrement) + .build(); + } + + @Test + void testFeedIds() { + var matcher = RegularStopMatcherFactory.feedId("agency"); + assertTrue(matcher.match(stop1)); + assertFalse(matcher.match(stop2)); + } + + @Test + void testInUseMatcher() { + var matcher = RegularStopMatcherFactory.inUseMatcher(stop -> + stop.getId().getFeedId().equals("agency") + ); + assertTrue(matcher.match(stop1)); + assertFalse(matcher.match(stop2)); + } +}