-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Direct transit search #7145
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
base: dev-2.x
Are you sure you want to change the base?
Direct transit search #7145
Changes from 28 commits
bfe7155
2207936
4588ff4
6081246
86a578e
b6a90f3
bad1eed
1bd2aaa
14c38fd
56f75f0
9552764
8f2ecc2
5bcfb71
57f1220
02ad356
bdc915c
3e525f4
4f21b2e
d500b70
1abe08a
30a9e4c
9527457
59f8755
8221011
75be5cf
2fef52c
4cb1c54
bf33980
4c2df61
46fe8ee
4fc568a
64cf3b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package org.opentripplanner.routing.algorithm.raptoradapter.transit; | ||
|
|
||
| import org.opentripplanner.raptor.api.model.AbstractAccessEgressDecorator; | ||
| import org.opentripplanner.raptor.api.model.RaptorAccessEgress; | ||
|
|
||
| /** | ||
| * This decorator will add an extra cost factor to the c1 of the access/egress. | ||
| */ | ||
| public class AccessEgressWithExtraCost extends AbstractAccessEgressDecorator { | ||
|
|
||
| private final double costFactor; | ||
|
|
||
| public AccessEgressWithExtraCost(RaptorAccessEgress delegate, double costFactor) { | ||
| super(delegate); | ||
| this.costFactor = costFactor; | ||
| } | ||
|
|
||
| @Override | ||
| public int c1() { | ||
| return (int) (delegate().c1() * costFactor); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| package org.opentripplanner.routing.algorithm.raptoradapter.transit.mappers; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.Collection; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import org.opentripplanner.raptor.api.model.RaptorAccessEgress; | ||
| import org.opentripplanner.raptor.api.request.SearchParams; | ||
| import org.opentripplanner.raptor.direct.api.RaptorDirectTransitRequest; | ||
| import org.opentripplanner.routing.algorithm.raptoradapter.transit.AccessEgressWithExtraCost; | ||
| import org.opentripplanner.routing.api.request.RouteRequest; | ||
|
|
||
| public class DirectTransitRequestMapper { | ||
|
|
||
| /// Map the request into a request object for the direct transit search. Will return empty if | ||
| /// the direct transit search shouldn't be run. | ||
| public static Optional<RaptorDirectTransitRequest> map( | ||
| RouteRequest request, | ||
| SearchParams searchParamsUsed | ||
| ) { | ||
| var directTransitRequestOpt = request.preferences().transit().directTransit(); | ||
| if (directTransitRequestOpt.isEmpty()) { | ||
| return Optional.empty(); | ||
| } | ||
| var rel = directTransitRequestOpt.orElseThrow(); | ||
| Collection<? extends RaptorAccessEgress> access = searchParamsUsed.accessPaths(); | ||
| Collection<? extends RaptorAccessEgress> egress = searchParamsUsed.egressPaths(); | ||
|
|
||
| access = filterAccessEgressNoOpeningHours(access); | ||
| egress = filterAccessEgressNoOpeningHours(egress); | ||
|
|
||
| if (rel.maxAccessEgressDuration().isPresent()) { | ||
| var maxDuration = rel.maxAccessEgressDuration().get(); | ||
| access = filterAccessEgressByDuration(access, maxDuration); | ||
| egress = filterAccessEgressByDuration(egress, maxDuration); | ||
| } | ||
| if (rel.isExtraReluctanceAddedToAccessAndEgress()) { | ||
| double f = rel.extraAccessEgressReluctance(); | ||
| access = decorateAccessEgressWithExtraCost(access, f); | ||
| egress = decorateAccessEgressWithExtraCost(egress, f); | ||
| } | ||
| if (access.isEmpty() || egress.isEmpty()) { | ||
| return Optional.empty(); | ||
| } | ||
| var directRequest = RaptorDirectTransitRequest.of() | ||
| .addAccessPaths(access) | ||
| .addEgressPaths(egress) | ||
| .searchWindowInSeconds(searchParamsUsed.searchWindowInSeconds()) | ||
| .earliestDepartureTime(searchParamsUsed.earliestDepartureTime()) | ||
| .withRelaxC1(RaptorRequestMapper.mapRelaxCost(rel.costRelaxFunction())) | ||
| .build(); | ||
| return Optional.of(directRequest); | ||
| } | ||
|
|
||
| private static List<? extends RaptorAccessEgress> filterAccessEgressByDuration( | ||
| Collection<? extends RaptorAccessEgress> list, | ||
| Duration maxDuration | ||
| ) { | ||
| return list | ||
| .stream() | ||
| .filter(ae -> ae.durationInSeconds() <= maxDuration.toSeconds()) | ||
| .toList(); | ||
| } | ||
|
|
||
| private static List<? extends RaptorAccessEgress> filterAccessEgressNoOpeningHours( | ||
| Collection<? extends RaptorAccessEgress> list | ||
| ) { | ||
| return list | ||
| .stream() | ||
| .filter(it -> !it.hasOpeningHours()) | ||
| .toList(); | ||
| } | ||
|
|
||
| private static List<? extends RaptorAccessEgress> decorateAccessEgressWithExtraCost( | ||
| Collection<? extends RaptorAccessEgress> list, | ||
| double costFactor | ||
| ) { | ||
| return list | ||
| .stream() | ||
| .map(it -> new AccessEgressWithExtraCost(it, costFactor)) | ||
| .toList(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,171 @@ | ||
| package org.opentripplanner.routing.api.request.preference; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import javax.annotation.Nullable; | ||
| import org.opentripplanner.framework.model.Cost; | ||
| import org.opentripplanner.routing.api.request.framework.CostLinearFunction; | ||
| import org.opentripplanner.utils.tostring.ToStringBuilder; | ||
|
|
||
| /// All preferences related to the Direct Transit Search | ||
| public class DirectTransitPreferences { | ||
|
|
||
| // The next constants are package-local to be readable in the unit-test. | ||
| static final double DEFAULT_RELUCTANCE = 1.0; | ||
| static final CostLinearFunction DEFAULT_COST_RELAX_FUNCTION = CostLinearFunction.of( | ||
| Cost.costOfMinutes(15), | ||
| 1.5 | ||
| ); | ||
|
|
||
| public static final DirectTransitPreferences DEFAULT = new DirectTransitPreferences( | ||
| false, | ||
| DEFAULT_COST_RELAX_FUNCTION, | ||
| DEFAULT_RELUCTANCE, | ||
| null | ||
| ); | ||
|
|
||
| private final boolean enabled; | ||
| private final CostLinearFunction costRelaxFunction; | ||
| private final double extraAccessEgressReluctance; | ||
|
|
||
| @Nullable | ||
| private final Duration maxAccessEgressDuration; | ||
|
|
||
| private DirectTransitPreferences( | ||
| boolean enabled, | ||
| CostLinearFunction costRelaxFunction, | ||
| double extraAccessEgressReluctance, | ||
| Duration maxAccessEgressDuration | ||
| ) { | ||
| this.enabled = enabled; | ||
| this.costRelaxFunction = Objects.requireNonNull(costRelaxFunction); | ||
| this.extraAccessEgressReluctance = extraAccessEgressReluctance; | ||
| this.maxAccessEgressDuration = Objects.requireNonNull(maxAccessEgressDuration); | ||
| } | ||
|
|
||
| public static Builder of() { | ||
| return new Builder(DEFAULT); | ||
| } | ||
|
|
||
| public Builder copyOf() { | ||
| return new Builder(this); | ||
| } | ||
|
|
||
| /// Whether to enable direct transit search | ||
| public boolean enabled() { | ||
| return enabled; | ||
| } | ||
|
|
||
| /// This is used to limit the results from the search. Paths are compared with the cheapest path | ||
| /// in the search window and are included in the result if they fall within the limit given by the | ||
| /// costRelaxFunction. | ||
| public CostLinearFunction costRelaxFunction() { | ||
| return costRelaxFunction; | ||
| } | ||
|
|
||
| /// An extra cost that is used to increase the cost of the access/egress legs for this search. | ||
| public double extraAccessEgressReluctance() { | ||
| return extraAccessEgressReluctance; | ||
| } | ||
|
|
||
| /// Whether there is any extra access/egress reluctance | ||
| public boolean isExtraReluctanceAddedToAccessAndEgress() { | ||
| return extraAccessEgressReluctance != DEFAULT_RELUCTANCE; | ||
| } | ||
|
|
||
| /// A limit on the duration for access/egress for this search. Setting this to 0 will only include | ||
| /// results that require no access or egress. I.e. a stop-to-stop search. | ||
| public Optional<Duration> maxAccessEgressDuration() { | ||
| return Optional.ofNullable(maxAccessEgressDuration); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean equals(Object o) { | ||
| if (o == null || getClass() != o.getClass()) { | ||
| return false; | ||
| } | ||
| DirectTransitPreferences that = (DirectTransitPreferences) o; | ||
| return ( | ||
| enabled == that.enabled && | ||
| Double.compare(extraAccessEgressReluctance, that.extraAccessEgressReluctance) == 0 && | ||
| Objects.equals(maxAccessEgressDuration, that.maxAccessEgressDuration) && | ||
| Objects.equals(costRelaxFunction, that.costRelaxFunction) | ||
| ); | ||
| } | ||
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return Objects.hash( | ||
| enabled, | ||
| costRelaxFunction, | ||
| extraAccessEgressReluctance, | ||
| maxAccessEgressDuration | ||
| ); | ||
| } | ||
|
|
||
| @Override | ||
| public String toString() { | ||
| return ToStringBuilder.of(DirectTransitPreferences.class) | ||
| .addBool("enabled", enabled) | ||
|
||
| .addObj("costRelaxFunction", costRelaxFunction, DEFAULT.costRelaxFunction) | ||
| .addNum( | ||
| "extraAccessEgressReluctance", | ||
| extraAccessEgressReluctance, | ||
| DEFAULT.extraAccessEgressReluctance | ||
| ) | ||
| .addDuration( | ||
| "maxAccessEgressDuration", | ||
| maxAccessEgressDuration, | ||
| DEFAULT.maxAccessEgressDuration | ||
| ) | ||
| .toString(); | ||
| } | ||
|
|
||
| public static class Builder { | ||
|
|
||
| private boolean enabled; | ||
| private CostLinearFunction costRelaxFunction; | ||
| private double extraAccessEgressReluctance; | ||
| private Duration maxAccessEgressDuration; | ||
| public DirectTransitPreferences original; | ||
|
|
||
| public Builder(DirectTransitPreferences original) { | ||
| this.original = original; | ||
| this.enabled = original.enabled; | ||
| this.costRelaxFunction = original.costRelaxFunction; | ||
| this.extraAccessEgressReluctance = original.extraAccessEgressReluctance; | ||
| this.maxAccessEgressDuration = original.maxAccessEgressDuration; | ||
| } | ||
|
|
||
| public Builder withEnabled(boolean enabled) { | ||
| this.enabled = enabled; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder withCostRelaxFunction(CostLinearFunction costRelaxFunction) { | ||
| this.costRelaxFunction = costRelaxFunction; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder withExtraAccessEgressReluctance(double extraAccessEgressReluctance) { | ||
| this.extraAccessEgressReluctance = extraAccessEgressReluctance; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder withMaxAccessEgressDuration(Duration maxAccessEgressDuration) { | ||
| this.maxAccessEgressDuration = maxAccessEgressDuration; | ||
| return this; | ||
| } | ||
|
|
||
| public DirectTransitPreferences build() { | ||
| var value = new DirectTransitPreferences( | ||
| enabled, | ||
| costRelaxFunction, | ||
| extraAccessEgressReluctance, | ||
| maxAccessEgressDuration | ||
| ); | ||
| return original.equals(value) ? original : value; | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.