Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a matcher API for filters in the transit service used for regularStop lookup #6234

Merged
merged 14 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,6 @@ public List<ApiStopShort> getStopsInRadius(
var stops = transitService().findRegularStops(envelope);
return stops
.stream()
.filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate()))
.map(StopMapper::mapToApiShort)
.toList();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -750,8 +750,7 @@ public DataFetcher<Iterable<Object>> stopsByBbox() {

Stream<RegularStop> stopStream = getTransitService(environment)
.findRegularStops(envelope)
.stream()
.filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate()));
.stream();

if (args.getGraphQLFeeds() != null) {
List<String> feedIds = args.getGraphQLFeeds();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.RegularStopRequest;
import org.opentripplanner.transit.api.request.TripRequest;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.framework.FeedScopedId;
Expand Down Expand Up @@ -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()
)
Expand Down Expand Up @@ -576,7 +575,7 @@ private GraphQLSchema create() {
.dataFetcher(environment ->
GqlUtil
.getTransitService(environment)
.getStopLocation(TransitIdMapper.mapIDToDomain(environment.getArgument("id")))
.getStopLocation(mapIDToDomain(environment.getArgument("id")))
)
.build()
)
Expand Down Expand Up @@ -610,7 +609,7 @@ private GraphQLSchema create() {
}
TransitService transitService = GqlUtil.getTransitService(environment);
return ((List<String>) 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) {
Expand Down Expand Up @@ -661,7 +660,12 @@ private GraphQLSchema create() {
.build()
)
.argument(
GraphQLArgument.newArgument().name("authority").type(Scalars.GraphQLString).build()
GraphQLArgument
.newArgument()
.name("authority")
.description("The ID of the authority to fetch stops for.")
eibakke marked this conversation as resolved.
Show resolved Hide resolved
.type(Scalars.GraphQLString)
.build()
)
.argument(
GraphQLArgument
Expand All @@ -683,24 +687,17 @@ private GraphQLSchema create() {
environment.getArgument("maximumLatitude")
)
);
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());

var authority = environment.<String>getArgument("authority");
var filterInUse = environment.<Boolean>getArgument("filterByInUse");

RegularStopRequest regularStopRequest = RegularStopRequest
.of(envelope)
.withFeed(authority)
eibakke marked this conversation as resolved.
Show resolved Hide resolved
.filterByInUse(filterInUse)
.build();

return GqlUtil.getTransitService(environment).findRegularStops(regularStopRequest);
})
.build()
)
Expand Down Expand Up @@ -1438,7 +1435,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()
Expand Down Expand Up @@ -1573,7 +1570,7 @@ private GraphQLSchema create() {
return GqlUtil
.getTransitService(environment)
.getTransitAlertService()
.getAlertById(TransitIdMapper.mapIDToDomain(situationNumber));
.getAlertById(mapIDToDomain(situationNumber));
})
.build()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,6 @@ public static Collection<MonoOrMultiModalStation> fetchStopPlaces(
Stream<Station> stations = transitService
.findRegularStops(envelope)
.stream()
.filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate()))
.map(StopLocation::getParentStation)
.filter(Objects::nonNull)
.distinct();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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.
* <p/>
* This request is used to retrieve {@link RegularStop}s that match the provided criteria.
*/
public class RegularStopRequest {
eibakke marked this conversation as resolved.
Show resolved Hide resolved

private final Envelope envelope;
eibakke marked this conversation as resolved.
Show resolved Hide resolved
private final String agency;
eibakke marked this conversation as resolved.
Show resolved Hide resolved
private final boolean filterByInUse;

protected RegularStopRequest(Envelope envelope, String agency, boolean filterByInUse) {
this.envelope = envelope;
this.agency = agency;
this.filterByInUse = filterByInUse;
}

public static RegularStopRequestBuilder of(Envelope envelope) {
return new RegularStopRequestBuilder(envelope);
}

public Envelope envelope() {
return envelope;
}

@Nullable
public String agency() {
return agency;
}

public boolean filterByInUse() {
return filterByInUse;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.opentripplanner.transit.api.request;

import org.locationtech.jts.geom.Envelope;

public class RegularStopRequestBuilder {

private Envelope envelope;
private String agency;
private boolean filterByInUse = false;

protected RegularStopRequestBuilder(Envelope envelope) {
this.envelope = envelope;
}

public RegularStopRequestBuilder withFeed(String agency) {
eibakke marked this conversation as resolved.
Show resolved Hide resolved
this.agency = agency;
return this;
}

public RegularStopRequestBuilder filterByInUse(boolean filterByInUse) {
this.filterByInUse = filterByInUse;
return this;
}

public RegularStopRequest build() {
return new RegularStopRequest(envelope, agency, filterByInUse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ public static <T> ExpressionBuilder<T> of() {
return new ExpressionBuilder<>();
}

public ExpressionBuilder<T> matches(Matcher<T> matcher) {
matchers.add(matcher);
return this;
}

public <V> ExpressionBuilder<T> atLeastOneMatch(
FilterValues<V> filterValues,
Function<V, Matcher<T>> matcherProvider
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p/>
* @param <T> The type of the entity being matched.
*/
public class GenericUnaryMatcher<T> implements Matcher<T> {

private final String typeName;
private final Predicate<T> 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<T> matchPredicate) {
this.typeName = typeName;
this.matchPredicate = matchPredicate;
}

@Override
public boolean match(T entity) {
return matchPredicate.test(entity);
}

@Override
public String toString() {
return "GenericUnaryMatcher: " + typeName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.opentripplanner.transit.model.filter.transit;

import java.util.function.Predicate;
import org.opentripplanner.transit.api.request.RegularStopRequest;
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.
* <p/>
* 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 {@link RegularStopRequest}
* and {@link Predicate}. The {@link Predicate} is used to determine if a {@link RegularStop} is
* in use. Typically the inUseProvider is a function that checks if the {@link RegularStop} is in
* a set of used stops.
eibakke marked this conversation as resolved.
Show resolved Hide resolved
* @param request - {@link RegularStopRequest} to filter {@link RegularStop} objects.
* @param inUseProvider - {@link Predicate} to determine if a {@link RegularStop} is in use.
* @return - {@link Matcher} for {@link RegularStop} objects.
*/
public static Matcher<RegularStop> of(
RegularStopRequest request,
Predicate<RegularStop> inUseProvider
) {
ExpressionBuilder<RegularStop> expr = ExpressionBuilder.of();

if (request.agency() != null) {
expr.matches(agency(request.agency()));
}
if (request.filterByInUse()) {
expr.matches(inUseMatcher(inUseProvider));
}
return expr.build();
}

static Matcher<RegularStop> agency(String agency) {
return new EqualityMatcher<>("agency", agency, stop -> stop.getId().getFeedId());
eibakke marked this conversation as resolved.
Show resolved Hide resolved
}

static Matcher<RegularStop> inUseMatcher(Predicate<RegularStop> inUseProvider) {
return new GenericUnaryMatcher<>("inUse", inUseProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.RegularStopRequest;
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;
Expand Down Expand Up @@ -687,6 +689,20 @@ public Collection<RegularStop> findRegularStops(Envelope envelope) {
return timetableRepository.getSiteRepository().findRegularStops(envelope);
}

@Override
public Collection<RegularStop> findRegularStops(RegularStopRequest request) {
eibakke marked this conversation as resolved.
Show resolved Hide resolved
OTPRequestTimeoutException.checkForTimeout();
Collection<RegularStop> stops = timetableRepository
.getSiteRepository()
.findRegularStops(request.envelope());

Matcher<RegularStop> matcher = RegularStopMatcherFactory.of(
request,
stop -> !findPatterns(stop, true).isEmpty()
);
return stops.stream().filter(matcher::match).toList();
}

@Override
public Collection<AreaStop> findAreaStops(Envelope envelope) {
OTPRequestTimeoutException.checkForTimeout();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<RegularStop> findRegularStops(Envelope envelope) {
return regularStopSpatialIndex.query(envelope);
return regularStopSpatialIndex
.query(envelope)
.stream()
.filter(stop -> envelope.contains(stop.getCoordinate().asJtsCoordinate()))
.toList();
}

MultiModalStation getMultiModalStationForStation(Station station) {
Expand Down
Loading
Loading