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

Remove transit with higher cost than best on-street itinerary filter #5222

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
636f36b
Add cost function to RemoveTransitIfStreetOnlyIsBetter filter
vesameskanen Jul 5, 2023
3621260
Apply hard filter for walking
vesameskanen Jul 6, 2023
bfe2b61
Pass linear function as parameter for RemoveTransitIfStreetOnlyIsBett…
vesameskanen Jul 6, 2023
709a94a
Add two missing itinerary filter configuration examples
vesameskanen Jul 6, 2023
3463eb6
Update docs
vesameskanen Jul 6, 2023
d7908d5
Merge remote-tracking branch 'otp/dev-2.x' into removetransitifstreet…
vesameskanen Jul 10, 2023
44f0ac8
Merge remote-tracking branch 'otp/dev-2.x' into removetransitifstreet…
vesameskanen Jul 18, 2023
e981121
Update auto generated docs
vesameskanen Jul 18, 2023
d380257
Merge remote-tracking branch 'otp/dev-2.x' into removetransitifstreet…
vesameskanen Aug 18, 2023
8e3e2c1
Use CostLinearFunction
vesameskanen Aug 21, 2023
4d12fd8
Split RemoveTransitIfStreetOnlyIsBetterFilter.java into two simpler f…
vesameskanen Aug 21, 2023
94a738d
Add test for the new itinerary filter
vesameskanen Aug 22, 2023
acc2bb9
Update test name
vesameskanen Aug 22, 2023
fc4f3ce
Remove unused function
vesameskanen Aug 22, 2023
e34e4e9
Use cost, not distance
vesameskanen Aug 22, 2023
f2e021f
Update test
vesameskanen Aug 22, 2023
71b069a
Merge remote-tracking branch 'otp/dev-2.x' into removetransitifstreet…
vesameskanen Aug 25, 2023
6fedd5d
Rename transit vs walking filter
vesameskanen Sep 13, 2023
07683a9
Update documentation to reflect changes in the filter
vesameskanen Sep 13, 2023
4985bd6
update docs
vesameskanen Sep 13, 2023
c42861c
Use new class name
vesameskanen Sep 13, 2023
db41b92
Use new class name
vesameskanen Sep 13, 2023
056f8f2
Remove old class
vesameskanen Sep 13, 2023
e005426
Merge branch 'rename-filter-class' into removetransitifstreetonlyisbe…
vesameskanen Sep 13, 2023
ca17977
Rename references to changed filter class name
vesameskanen Sep 13, 2023
10949a0
Move street vs transit filters to the block of absolute filters
vesameskanen Sep 13, 2023
089a4a7
Rename remaining references to old filter class name and rename the t…
vesameskanen Sep 13, 2023
43e00c9
Update docs
vesameskanen Sep 13, 2023
59f114b
Update RouteRequest doc
vesameskanen Sep 13, 2023
2aa2120
Update src/main/java/org/opentripplanner/routing/algorithm/filterchai…
vesameskanen Sep 14, 2023
775cd16
Update src/main/java/org/opentripplanner/routing/algorithm/filterchai…
vesameskanen Sep 14, 2023
38ac2bd
Update src/main/java/org/opentripplanner/routing/api/request/preferen…
vesameskanen Sep 14, 2023
79c907e
Update src/main/java/org/opentripplanner/routing/api/request/preferen…
vesameskanen Sep 14, 2023
2bfd928
Update src/main/java/org/opentripplanner/routing/api/request/preferen…
vesameskanen Sep 14, 2023
a2d109f
Update src/main/java/org/opentripplanner/routing/api/request/preferen…
vesameskanen Sep 14, 2023
5a3b13f
Update src/main/java/org/opentripplanner/routing/api/request/preferen…
vesameskanen Sep 14, 2023
4bab50a
Update src/main/java/org/opentripplanner/routing/api/request/preferen…
vesameskanen Sep 14, 2023
061c0bb
Update src/main/java/org/opentripplanner/standalone/config/routereque…
vesameskanen Sep 14, 2023
46f5291
Fix naming issues pointed out in review
vesameskanen Sep 14, 2023
a49c890
Update src/main/java/org/opentripplanner/routing/algorithm/filterchai…
vesameskanen Sep 14, 2023
200ed4e
Fix formatting
vesameskanen Sep 14, 2023
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
269 changes: 144 additions & 125 deletions docs/RouteRequest.md

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions docs/RouterConfiguration.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,8 @@ HTTP headers to add to the request. Any header key, value can be inserted.
"costLimitFunction" : "900 + 1.5 x",
"intervalRelaxFactor" : 0.4
},
"nonTransitGeneralizedCostLimit" : "400 + 1.5x",
"removeTransitWithHigherCostThanBestOnStreetOnly" : "60 + 1.3x",
"bikeRentalDistanceRatio" : 0.3,
"accessibilityScore" : true,
"minBikeParkingDistance" : 300,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveItinerariesWithShortStreetLeg;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveParkAndRideWithMostlyWalkingFilter;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfStreetOnlyIsBetterFilter;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfWalkingIsBetterFilter;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveWalkOnlyFilter;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.TransitGeneralizedCostFilter;
import org.opentripplanner.routing.algorithm.filterchain.filter.DeletionFlaggingFilter;
Expand Down Expand Up @@ -56,7 +57,7 @@ public class ItineraryListFilterChainBuilder {
private ItineraryFilterDebugProfile debug = ItineraryFilterDebugProfile.OFF;
private int maxNumberOfItineraries = NOT_SET;
private ListSection maxNumberOfItinerariesCrop = ListSection.TAIL;
private boolean removeTransitWithHigherCostThanBestOnStreetOnly = true;
private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly;
private boolean removeWalkAllTheWayResults;
private boolean sameFirstOrLastTripFilter;
private TransitGeneralizedCostFilterParams transitGeneralizedCostFilterParams;
Expand All @@ -72,6 +73,7 @@ public class ItineraryListFilterChainBuilder {
private Function<Station, MultiModalStation> getMultiModalStation;
private boolean removeItinerariesWithSameRoutesAndStops;
private double minBikeParkingDistance;
private boolean removeTransitIfWalkingIsBetter = true;

@Sandbox
private ItineraryListFilter rideHailingFilter;
Expand Down Expand Up @@ -184,21 +186,32 @@ public ItineraryListFilterChainBuilder withParkAndRideDurationRatio(double value
/**
* The direct street search(walk, bicycle, car) is not pruning the transit search, so in some
* cases we get "silly" transit itineraries that is marginally better on travel-duration compared
* with a on-street-all-the-way itinerary. Use this method to turn this filter on/off.
* with a on-street-all-the-way itinerary. Use this method to filter worse enough itineraries.
* <p>
* The filter remove all itineraries with a generalized-cost that is higher than the best
* The filter removes all itineraries with a generalized-cost that is higher than the best
* on-street-all-the-way itinerary.
* <p>
* This filter only have an effect, if an on-street-all-the-way(WALK, BICYCLE, CAR) itinerary
* exist.
*/
public ItineraryListFilterChainBuilder withRemoveTransitWithHigherCostThanBestOnStreetOnly(
boolean value
CostLinearFunction value
) {
this.removeTransitWithHigherCostThanBestOnStreetOnly = value;
return this;
}

/**
* A transit itinerary with higher generalized-cost than a walk-only itinerary is silly. This filter removes such
* itineraries.
* <p>
* This filter only have an effect, if an walk-all-the-way itinerary exist.
*/
public ItineraryListFilterChainBuilder withRemoveTransitIfWalkingIsBetter(boolean value) {
this.removeTransitIfWalkingIsBetter = value;
return this;
}

/**
* This will NOT delete itineraries, but tag them as deleted using the {@link
* Itinerary#getSystemNotices()}.
Expand Down Expand Up @@ -350,8 +363,19 @@ public ItineraryListFilterChain build() {
// is worse). B is removed by the {@link LatestDepartureTimeFilter} below. This is exactly
// what we want, since both itineraries are none optimal.
{
if (removeTransitWithHigherCostThanBestOnStreetOnly) {
filters.add(new DeletionFlaggingFilter(new RemoveTransitIfStreetOnlyIsBetterFilter()));
// Filter transit itineraries by comparing against non-transit using generalized-cost
if (removeTransitWithHigherCostThanBestOnStreetOnly != null) {
filters.add(
new DeletionFlaggingFilter(
new RemoveTransitIfStreetOnlyIsBetterFilter(
removeTransitWithHigherCostThanBestOnStreetOnly
)
)
);
}

if (removeTransitIfWalkingIsBetter) {
filters.add(new DeletionFlaggingFilter(new RemoveTransitIfWalkingIsBetterFilter()));
}

if (removeWalkAllTheWayResults) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.opentripplanner.model.plan.StreetLeg;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.OutsideSearchWindowFilter;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfStreetOnlyIsBetterFilter;
import org.opentripplanner.routing.algorithm.filterchain.deletionflagger.RemoveTransitIfWalkingIsBetterFilter;
import org.opentripplanner.routing.api.response.RoutingError;

/**
Expand Down Expand Up @@ -50,7 +51,16 @@ static List<RoutingError> computeErrors(
.getSystemNotices()
.stream()
.anyMatch(notice -> notice.tag.equals(RemoveTransitIfStreetOnlyIsBetterFilter.TAG));
if (filteredItineraries.stream().allMatch(isOnStreetAllTheWay.or(isWorseThanStreet))) {
Predicate<Itinerary> isWorseThanWalking = it ->
it
.getSystemNotices()
.stream()
.anyMatch(notice -> notice.tag.equals(RemoveTransitIfWalkingIsBetterFilter.TAG));
if (
filteredItineraries
.stream()
.allMatch(isOnStreetAllTheWay.or(isWorseThanStreet).or(isWorseThanWalking))
) {
var nonTransitIsWalking = filteredItineraries
.stream()
.flatMap(Itinerary::getStreetLegs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,27 @@

import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.stream.Collectors;
import org.opentripplanner.framework.model.Cost;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.routing.api.request.framework.CostLinearFunction;

/**
* Filter itineraries based on generalizedCost, compared with a on-street-all-the-way itinerary(if
* it exist). If an itinerary is slower than the best all-the-way-on-street itinerary, then the
* it exist). If an itinerary cost exceeds the limit computed from the best all-the-way-on-street itinerary, then the
* transit itinerary is removed.
*/
public class RemoveTransitIfStreetOnlyIsBetterFilter implements ItineraryDeletionFlagger {

private final CostLinearFunction costLimitFunction;

public RemoveTransitIfStreetOnlyIsBetterFilter(CostLinearFunction costLimitFunction) {
this.costLimitFunction = costLimitFunction;
}

/**
* Required for {@link org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain},
* to know which filters removed
Expand All @@ -26,19 +36,22 @@ public String name() {

@Override
public List<Itinerary> flagForRemoval(List<Itinerary> itineraries) {
// Find the best walk-all-the-way option
Optional<Itinerary> bestStreetOp = itineraries
// Find the best street-all-the-way option
OptionalInt minStreetCost = itineraries
.stream()
.filter(Itinerary::isOnStreetAllTheWay)
.min(Comparator.comparingInt(Itinerary::getGeneralizedCost));
.mapToInt(Itinerary::getGeneralizedCost)
.min();

if (bestStreetOp.isEmpty()) {
if (minStreetCost.isEmpty()) {
return List.of();
}

final long limit = bestStreetOp.get().getGeneralizedCost();
var limit = costLimitFunction
.calculate(Cost.costOfSeconds(minStreetCost.getAsInt()))
.toSeconds();

// Filter away itineraries that have higher cost than the best non-transit option.
// Filter away itineraries that have higher cost than limit cost computed above
return itineraries
.stream()
.filter(it -> !it.isOnStreetAllTheWay() && it.getGeneralizedCost() >= limit)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.opentripplanner.routing.algorithm.filterchain.deletionflagger;

import java.util.Comparator;
import java.util.List;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.stream.Collectors;
import org.opentripplanner.framework.model.Cost;
import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.model.plan.Leg;
import org.opentripplanner.routing.api.request.framework.CostLinearFunction;

/**
* Filter itineraries which have a higher generalized-cost than a pure walk itinerary.
*/
public class RemoveTransitIfWalkingIsBetterFilter implements ItineraryDeletionFlagger {

/**
* Required for {@link org.opentripplanner.routing.algorithm.filterchain.ItineraryListFilterChain},
* to know which filters removed
*/
public static final String TAG = "transit-vs-walk-filter";

@Override
public String name() {
return TAG;
}

@Override
public List<Itinerary> flagForRemoval(List<Itinerary> itineraries) {
OptionalInt minWalkCost = itineraries
.stream()
.filter(Itinerary::isWalkingAllTheWay)
.mapToInt(Itinerary::getGeneralizedCost)
.min();

if (minWalkCost.isEmpty()) {
return List.of();
}

var limit = minWalkCost.getAsInt();

return itineraries
.stream()
.filter(it -> !it.isOnStreetAllTheWay() && it.getGeneralizedCost() >= limit)
.collect(Collectors.toList());
}

@Override
public boolean skipAlreadyFlaggedItineraries() {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ public static ItineraryListFilterChain createFilterChain(
.withBikeRentalDistanceRatio(params.bikeRentalDistanceRatio())
.withParkAndRideDurationRatio(params.parkAndRideDurationRatio())
.withNonTransitGeneralizedCostLimit(params.nonTransitGeneralizedCostLimit())
.withRemoveTransitWithHigherCostThanBestOnStreetOnly(
params.removeTransitWithHigherCostThanBestOnStreetOnly()
)
.withSameFirstOrLastTripFilter(params.filterItinerariesWithSameFirstOrLastTrip())
.withAccessibilityScore(
params.useAccessibilityScore() && request.wheelchair(),
Expand All @@ -79,10 +82,10 @@ public static ItineraryListFilterChain createFilterChain(
context.transitService().getTransitAlertService(),
context.transitService()::getMultiModalStationForStation
)
.withRemoveTransitWithHigherCostThanBestOnStreetOnly(true)
.withLatestDepartureTimeLimit(filterOnLatestDepartureTime)
.withMaxLimitReachedSubscriber(maxLimitReachedSubscriber)
.withRemoveWalkAllTheWayResults(removeWalkAllTheWayResults)
.withRemoveTransitIfWalkingIsBetter(true)
.withDebugEnabled(params.debug());

if (!context.rideHailingServices().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public final class ItineraryFilterPreferences {
private final double parkAndRideDurationRatio;
private final boolean removeItinerariesWithSameRoutesAndStops;
private final TransitGeneralizedCostFilterParams transitGeneralizedCostLimit;
private final CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly;
private final boolean removeTransitIfWalkingIsBetter;

private ItineraryFilterPreferences() {
this.accessibilityScore = false;
Expand All @@ -48,6 +50,9 @@ private ItineraryFilterPreferences() {
CostLinearFunction.of(Duration.ofMinutes(15), 1.5),
0.4
);
this.removeTransitWithHigherCostThanBestOnStreetOnly =
CostLinearFunction.of(Duration.ofMinutes(1), 1.3);
this.removeTransitIfWalkingIsBetter = false;
}

private ItineraryFilterPreferences(Builder builder) {
Expand All @@ -66,6 +71,9 @@ private ItineraryFilterPreferences(Builder builder) {
this.parkAndRideDurationRatio = Units.ratio(builder.parkAndRideDurationRatio);
this.removeItinerariesWithSameRoutesAndStops = builder.removeItinerariesWithSameRoutesAndStops;
this.transitGeneralizedCostLimit = Objects.requireNonNull(builder.transitGeneralizedCostLimit);
this.removeTransitWithHigherCostThanBestOnStreetOnly =
Objects.requireNonNull(builder.removeTransitWithHigherCostThanBestOnStreetOnly);
this.removeTransitIfWalkingIsBetter = builder.removeTransitIfWalkingIsBetter;
}

public static Builder of() {
Expand Down Expand Up @@ -124,6 +132,14 @@ public TransitGeneralizedCostFilterParams transitGeneralizedCostLimit() {
return transitGeneralizedCostLimit;
}

public CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly() {
return removeTransitWithHigherCostThanBestOnStreetOnly;
}

public boolean removeTransitIfWalkingIsBetter() {
return removeTransitIfWalkingIsBetter;
}

@Override
public String toString() {
return ToStringBuilder
Expand Down Expand Up @@ -162,10 +178,16 @@ public String toString() {
transitGeneralizedCostLimit,
DEFAULT.transitGeneralizedCostLimit
)
.addObj(
"removeTransitWithHigherCostThanBestOnStreetOnly",
removeTransitWithHigherCostThanBestOnStreetOnly,
DEFAULT.removeTransitWithHigherCostThanBestOnStreetOnly
)
.addBoolIfTrue(
"removeItinerariesWithSameRoutesAndStops",
removeItinerariesWithSameRoutesAndStops
)
.addBoolIfTrue("removeTransitIfWalkingIsBetter", removeTransitIfWalkingIsBetter)
.toString();
}

Expand All @@ -178,6 +200,7 @@ public boolean equals(Object o) {
accessibilityScore == that.accessibilityScore &&
Double.compare(that.bikeRentalDistanceRatio, bikeRentalDistanceRatio) == 0 &&
debug == that.debug &&
removeTransitIfWalkingIsBetter == that.removeTransitIfWalkingIsBetter &&
filterItinerariesWithSameFirstOrLastTrip == that.filterItinerariesWithSameFirstOrLastTrip &&
Double.compare(
that.groupedOtherThanSameLegsMaxCostMultiplier,
Expand All @@ -190,6 +213,10 @@ public boolean equals(Object o) {
Double.compare(that.parkAndRideDurationRatio, parkAndRideDurationRatio) == 0 &&
removeItinerariesWithSameRoutesAndStops == that.removeItinerariesWithSameRoutesAndStops &&
Objects.equals(nonTransitGeneralizedCostLimit, that.nonTransitGeneralizedCostLimit) &&
Objects.equals(
removeTransitWithHigherCostThanBestOnStreetOnly,
that.removeTransitWithHigherCostThanBestOnStreetOnly
) &&
Objects.equals(transitGeneralizedCostLimit, that.transitGeneralizedCostLimit)
);
}
Expand All @@ -208,7 +235,9 @@ public int hashCode() {
nonTransitGeneralizedCostLimit,
parkAndRideDurationRatio,
removeItinerariesWithSameRoutesAndStops,
transitGeneralizedCostLimit
transitGeneralizedCostLimit,
removeTransitWithHigherCostThanBestOnStreetOnly,
removeTransitIfWalkingIsBetter
);
}

Expand All @@ -227,6 +256,8 @@ public static class Builder {
private double parkAndRideDurationRatio;
private boolean removeItinerariesWithSameRoutesAndStops;
private TransitGeneralizedCostFilterParams transitGeneralizedCostLimit;
private CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly;
private boolean removeTransitIfWalkingIsBetter;

public ItineraryFilterPreferences original() {
return original;
Expand Down Expand Up @@ -302,6 +333,19 @@ public Builder withTransitGeneralizedCostLimit(
return this;
}

public Builder withRemoveTransitWithHigherCostThanBestOnStreetOnly(
CostLinearFunction removeTransitWithHigherCostThanBestOnStreetOnly
) {
this.removeTransitWithHigherCostThanBestOnStreetOnly =
removeTransitWithHigherCostThanBestOnStreetOnly;
return this;
}

public Builder withRemoveTransitIfWalkingIsBetter(boolean removeTransitIfWalkingIsBetter) {
this.removeTransitIfWalkingIsBetter = removeTransitIfWalkingIsBetter;
return this;
}

public Builder(ItineraryFilterPreferences original) {
this.original = original;
this.accessibilityScore = original.accessibilityScore;
Expand All @@ -319,6 +363,9 @@ public Builder(ItineraryFilterPreferences original) {
this.removeItinerariesWithSameRoutesAndStops =
original.removeItinerariesWithSameRoutesAndStops;
this.transitGeneralizedCostLimit = original.transitGeneralizedCostLimit;
this.removeTransitWithHigherCostThanBestOnStreetOnly =
original.removeTransitWithHigherCostThanBestOnStreetOnly;
this.removeTransitIfWalkingIsBetter = original.removeTransitIfWalkingIsBetter;
}

public Builder apply(Consumer<Builder> body) {
Expand Down
Loading