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 route lookup #6378

Open
wants to merge 5 commits into
base: dev-2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 @@ -64,6 +64,10 @@ public Route getRouteById(FeedScopedId id) {
return routeById.get(id);
}

public boolean contains(Route route) {
return routeById.containsKey(route.getId());
}

public Collection<Route> getAllFlexRoutes() {
return routeById.values();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@
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.FindRoutesRequest;
import org.opentripplanner.transit.api.request.TripRequest;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.service.TransitService;
import org.slf4j.Logger;
Expand Down Expand Up @@ -1098,123 +1098,116 @@ private GraphQLSchema create() {
GraphQLFieldDefinition
.newFieldDefinition()
.name("lines")
.description("Get all lines")
.description("Get all _lines_")
.withDirective(TransmodelDirectives.TIMING_DATA)
.type(new GraphQLNonNull(new GraphQLList(lineType)))
.argument(
GraphQLArgument
.newArgument()
.name("ids")
.description(
"Set of ids of _lines_ to fetch. If this is set, no other filters can be set."
)
.type(new GraphQLList(Scalars.GraphQLID))
.build()
)
.argument(GraphQLArgument.newArgument().name("name").type(Scalars.GraphQLString).build())
.argument(
GraphQLArgument.newArgument().name("publicCode").type(Scalars.GraphQLString).build()
GraphQLArgument
.newArgument()
.name("name")
.description(
"Prefix of the _name_ of the _line_ to fetch. This filter is case insensitive."
)
.type(Scalars.GraphQLString)
.build()
)
.argument(
GraphQLArgument
.newArgument()
.name("publicCode")
.description("_Public code_ of the _line_ to fetch.")
.type(Scalars.GraphQLString)
.build()
)
.argument(
GraphQLArgument
.newArgument()
.name("publicCodes")
.description("Set of _public codes_ to fetch _lines_ for.")
.type(new GraphQLList(Scalars.GraphQLString))
.build()
)
.argument(
GraphQLArgument
.newArgument()
.name("transportModes")
.description("Set of _transport modes_ to fetch _lines_ for.")
.type(new GraphQLList(TRANSPORT_MODE))
.build()
)
.argument(
GraphQLArgument
.newArgument()
.name("authorities")
.description("Set of ids of authorities to fetch lines for.")
.description("Set of ids of _authorities_ to fetch _lines_ for.")
.type(new GraphQLList(Scalars.GraphQLString))
.build()
)
.argument(
GraphQLArgument
.newArgument()
.name("flexibleOnly")
.description("Filter by lines containing flexible / on demand serviceJourneys only.")
.description(
"Filter by _lines_ containing flexible / on demand _service journey_ only."
)
.type(Scalars.GraphQLBoolean)
.defaultValue(false)
.defaultValueProgrammatic(false)
.build()
)
.dataFetcher(environment -> {
if ((environment.getArgument("ids") instanceof List)) {
if (environment.containsArgument("ids")) {
var ids = mapIDsToDomainNullSafe(environment.getArgument("ids"));

// flexibleLines gets special treatment because it has a default value.
if (
environment
.getArguments()
.entrySet()
.stream()
.filter(it ->
it.getValue() != null &&
!(it.getKey().equals("flexibleOnly") && it.getValue().equals(false))
)
.count() !=
1
Stream
.of("name", "publicCode", "publicCodes", "transportModes", "authorities")
.anyMatch(environment::containsArgument) ||
Boolean.TRUE.equals(environment.getArgument("flexibleOnly"))
) {
throw new IllegalArgumentException("Unable to combine other filters with ids");
}
return ((List<String>) environment.getArgument("ids")).stream()
.map(TransitIdMapper::mapIDToDomain)
.map(id -> {
return GqlUtil.getTransitService(environment).getRoute(id);
})
.collect(Collectors.toList());
}
Stream<Route> stream = GqlUtil.getTransitService(environment).listRoutes().stream();

if ((boolean) environment.getArgument("flexibleOnly")) {
Collection<Route> flexRoutes = GqlUtil
.getTransitService(environment)
.getFlexIndex()
.getAllFlexRoutes();
stream = stream.filter(flexRoutes::contains);
}
if (environment.getArgument("name") != null) {
stream =
stream
.filter(route -> route.getLongName() != null)
.filter(route ->
route
.getLongName()
.toString()
.toLowerCase()
.startsWith(((String) environment.getArgument("name")).toLowerCase())
);
}
if (environment.getArgument("publicCode") != null) {
stream =
stream
.filter(route -> route.getShortName() != null)
.filter(route ->
route.getShortName().equals(environment.getArgument("publicCode"))
);
}
if (environment.getArgument("publicCodes") instanceof List) {
Set<String> publicCodes = Set.copyOf(environment.getArgument("publicCodes"));
stream =
stream
.filter(route -> route.getShortName() != null)
.filter(route -> publicCodes.contains(route.getShortName()));
}
if (environment.getArgument("transportModes") != null) {
Set<TransitMode> modes = Set.copyOf(environment.getArgument("transportModes"));
stream = stream.filter(route -> modes.contains(route.getMode()));
}
if ((environment.getArgument("authorities") instanceof Collection)) {
Collection<String> authorityIds = environment.getArgument("authorities");
stream =
stream.filter(route ->
route.getAgency() != null &&
authorityIds.contains(route.getAgency().getId().getId())
);
return GqlUtil.getTransitService(environment).getRoutes(ids);
}
return stream.collect(Collectors.toList());

var name = environment.<String>getArgument("name");
var publicCode = environment.<String>getArgument("publicCode");
var publicCodes = FilterValues.ofEmptyIsEverything(
"publicCodes",
environment.<List<String>>getArgument("publicCodes")
);
var transportModes = FilterValues.ofEmptyIsEverything(
"transportModes",
environment.<List<TransitMode>>getArgument("transportModes")
);
var authorities = FilterValues.ofEmptyIsEverything(
"authorities",
environment.<List<String>>getArgument("authorities")
);
boolean flexibleOnly = Boolean.TRUE.equals(environment.getArgument("flexibleOnly"));

FindRoutesRequest findRoutesRequest = FindRoutesRequest
.of()
.withLongName(name)
.withShortName(publicCode)
.withShortNames(publicCodes)
.withTransitModes(transportModes)
.withAgencies(authorities)
.withFlexibleOnly(flexibleOnly)
.build();

return GqlUtil.getTransitService(environment).findRoutes(findRoutesRequest);
})
.build()
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.opentripplanner.transit.api.request;

import org.opentripplanner.transit.api.model.FilterValues;
import org.opentripplanner.transit.model.basic.TransitMode;
import org.opentripplanner.transit.model.network.Route;

/**
* A request for finding {@link Route}s.
* </p>
* This request is used to retrieve Routes that match the provided filter values.
* At least one filter value must be provided.
*/
public class FindRoutesRequest {

private final boolean flexibleOnly;
private final String longName;
private final String shortName;
private final FilterValues<String> shortNames;
private final FilterValues<TransitMode> transitModes;
private final FilterValues<String> agencyIds;

protected FindRoutesRequest(
boolean flexibleOnly,
String longName,
String shortName,
FilterValues<String> shortNames,
FilterValues<TransitMode> transitModes,
FilterValues<String> agencyIds
) {
this.flexibleOnly = flexibleOnly;
this.longName = longName;
this.shortName = shortName;
this.shortNames = shortNames;
this.transitModes = transitModes;
this.agencyIds = agencyIds;
}

public static FindRoutesRequestBuilder of() {
return new FindRoutesRequestBuilder();
}

public boolean flexibleOnly() {
return flexibleOnly;
}

public String longName() {
return longName;
}

public String shortName() {
return shortName;
}

public FilterValues<String> shortNames() {
return shortNames;
}

public FilterValues<TransitMode> transitModes() {
return transitModes;
}

public FilterValues<String> agencies() {
return agencyIds;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.opentripplanner.transit.api.request;

import java.util.List;
import javax.annotation.Nullable;
import org.opentripplanner.transit.api.model.FilterValues;
import org.opentripplanner.transit.model.basic.TransitMode;

public class FindRoutesRequestBuilder {

private boolean flexibleOnly;
private String longName;
private String shortName;
private FilterValues<String> shortNames = FilterValues.ofEmptyIsEverything(
"shortNames",
List.of()
);
private FilterValues<TransitMode> transitModes = FilterValues.ofEmptyIsEverything(
"transitModes",
List.of()
);
private FilterValues<String> agencies = FilterValues.ofEmptyIsEverything("agencies", List.of());

protected FindRoutesRequestBuilder() {}

public FindRoutesRequestBuilder withAgencies(FilterValues<String> agencies) {
this.agencies = agencies;
return this;
}

public FindRoutesRequestBuilder withFlexibleOnly(boolean flexibleOnly) {
this.flexibleOnly = flexibleOnly;
return this;
}

public FindRoutesRequestBuilder withLongName(@Nullable String longName) {
this.longName = longName;
return this;
}

public FindRoutesRequestBuilder withShortName(@Nullable String shortName) {
this.shortName = shortName;
return this;
}

public FindRoutesRequestBuilder withShortNames(FilterValues<String> shortNames) {
this.shortNames = shortNames;
return this;
}

public FindRoutesRequestBuilder withTransitModes(FilterValues<TransitMode> transitModes) {
this.transitModes = transitModes;
return this;
}

public FindRoutesRequest build() {
return new FindRoutesRequest(
flexibleOnly,
longName,
shortName,
shortNames,
transitModes,
agencies
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.opentripplanner.transit.model.filter.expr;

import java.util.function.Function;

/**
* A matcher that checks if a string field starts with a given value.
* <p/>
* @param <T> The type of the entity being matched.
*/
public class CaseInsensitiveStringPrefixMatcher<T> implements Matcher<T> {

private final String typeName;
private final String value;
private final Function<T, String> valueProvider;

/**
* @param typeName The typeName appears in the toString for easier debugging.
* @param value - The String that may be a prefix.
* @param valueProvider - A function that maps the entity being matched to the String being
* checked for a prefix match.
*/
public CaseInsensitiveStringPrefixMatcher(
String typeName,
String value,
Function<T, String> valueProvider
) {
this.typeName = typeName;
this.value = value;
this.valueProvider = valueProvider;
}

@Override
public boolean match(T entity) {
return valueProvider.apply(entity).toLowerCase().startsWith(value.toLowerCase());
}

@Override
public String toString() {
return typeName + " has prefix: " + value;
}
}
Loading
Loading