Skip to content
This repository has been archived by the owner on Mar 3, 2025. It is now read-only.

Commit

Permalink
feature: Use decorators to apply time-penalty in Raptor
Browse files Browse the repository at this point in the history
  • Loading branch information
t2gran committed Feb 20, 2024
1 parent d45a7a6 commit adb6ab6
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.opentripplanner.raptor.api.model;

import java.util.Objects;
import javax.annotation.Nullable;

/**
* Using delegation to extend the {@link RaptorAccessEgress} functionality is common, so we provide
* a base delegation implementation here. This implementation delegates all operations to the
* delegate.
*/
public class AbstractAccessEgressDelegator implements RaptorAccessEgress {

private final RaptorAccessEgress delegate;

public AbstractAccessEgressDelegator(RaptorAccessEgress delegate) {
this.delegate = delegate;
}

protected RaptorAccessEgress delegate() {
return delegate;
}

@Override
public int stop() {
return delegate.stop();
}

@Override
public int c1() {
return delegate.c1();
}

@Override
public int durationInSeconds() {
return delegate.durationInSeconds();
}

@Override
public int timePenalty() {
return delegate.timePenalty();
}

@Override
public int earliestDepartureTime(int requestedDepartureTime) {
return delegate.earliestDepartureTime(requestedDepartureTime);
}

@Override
public int latestArrivalTime(int requestedArrivalTime) {
return delegate.latestArrivalTime(requestedArrivalTime);
}

@Override
public boolean hasOpeningHours() {
return delegate.hasOpeningHours();
}

@Nullable
@Override
public String openingHoursToString() {
return delegate.openingHoursToString();
}

@Override
public int numberOfRides() {
return delegate.numberOfRides();
}

@Override
public boolean hasRides() {
return delegate.hasRides();
}

@Override
public boolean stopReachedOnBoard() {
return delegate.stopReachedOnBoard();
}

@Override
public boolean stopReachedByWalking() {
return delegate.stopReachedByWalking();
}

@Override
public boolean isFree() {
return delegate.isFree();
}

@Override
public String defaultToString() {
return delegate.defaultToString();
}

@Override
public String asString(boolean includeStop, boolean includeCost, @Nullable String summary) {
return delegate.asString(includeStop, includeCost, summary);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AbstractAccessEgressDelegator that = (AbstractAccessEgressDelegator) o;
return Objects.equals(delegate, that.delegate);
}

@Override
public int hashCode() {
return Objects.hash(delegate);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ private void addAccessPaths(Collection<RaptorAccessEgress> accessPaths) {

// Access must be available after the iteration departure time
if (departureTime != RaptorConstants.TIME_NOT_SET) {
transitWorker.setAccessToStop(it, departureTime);
transitWorker.setAccessToStop(it, departureTime - it.timePenalty());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ public int calculateMaxNumberOfRides() {

/**
* The multi-criteria state can handle multiple access/egress paths to a single stop, but the
* Standard and BestTime states do not. To get a deterministic behaviour we filter the paths and
* Standard and BestTime states do not. To get a deterministic behavior, we filter the paths and
* return the paths with the shortest duration for non-multi-criteria search. If two paths have
* the same duration the first one is picked. Note! If the access/egress paths contains flex as
* the same duration, the first one is picked. Note! If the access/egress paths contains flex as
* well, then we need to look at mode for arriving at tha stop as well. A Flex arrive-on-board can
* be used with a transfer even if the time is worse compared with walking.
* <p>
Expand All @@ -63,12 +63,26 @@ public static AccessPaths create(Collection<RaptorAccessEgress> paths, RaptorPro
} else {
paths = removeNonOptimalPathsForStandardRaptor(paths);
}

paths = decorateWithTimePenaltyLogic(paths);

return new AccessPaths(
groupByRound(paths, RaptorAccessEgress::stopReachedByWalking),
groupByRound(paths, RaptorAccessEgress::stopReachedOnBoard)
);
}

/**
* Decorate access to implement time-penalty. This decoration will do the necessary
* adjustments to apply the penalty in the raptor algorithm. See the decorator class for more
* info. The original access object is returned if it does not have a time-penalty set.
*/
private static List<RaptorAccessEgress> decorateWithTimePenaltyLogic(
Collection<RaptorAccessEgress> paths
) {
return paths.stream().map(it -> it.timePenalty() > 0 ? new AccessWithPenalty(it) : it).toList();
}

/** Raptor uses this information to optimize boarding of the first trip */
public boolean hasTimeDependentAccess() {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.opentripplanner.raptor.rangeraptor.transit;

import org.opentripplanner.raptor.api.model.AbstractAccessEgressDelegator;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;

/**
* This decorator will add the time penalty to the duration of the access and adjust the
* `requestedDepartureTime` when time-shifting the access according to opening-hours.
*
* TODO PEN - Write more
*/
public class AccessWithPenalty extends AbstractAccessEgressDelegator {

public AccessWithPenalty(RaptorAccessEgress delegate) {
super(delegate);
}

@Override
public int durationInSeconds() {
return delegate().durationInSeconds() + delegate().timePenalty();
}

@Override
public int earliestDepartureTime(int requestedDepartureTime) {
return delegate().earliestDepartureTime(requestedDepartureTime + delegate().timePenalty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ private EgressPaths(TIntObjectMap<List<RaptorAccessEgress>> pathsByStop) {
* This method is static and package local to enable unit-testing.
*/
public static EgressPaths create(Collection<RaptorAccessEgress> paths, RaptorProfile profile) {
paths = decorateWithTimePenaltyLogic(paths);

if (MULTI_CRITERIA.is(profile)) {
paths = removeNonOptimalPathsForMcRaptor(paths);
} else {
Expand Down Expand Up @@ -72,6 +74,17 @@ public int[] egressesWitchStartByARide() {
return filterPathsAndGetStops(RaptorAccessEgress::stopReachedOnBoard);
}

/**
* Decorate egress to implement time-penalty. This decoration will do the necessary
* adjustments to apply the penalty in the raptor algorithm. See the decorator class for more
* info. The original egress object is returned if it does not have a time-penalty set.
*/
private static List<RaptorAccessEgress> decorateWithTimePenaltyLogic(
Collection<RaptorAccessEgress> paths
) {
return paths.stream().map(it -> it.timePenalty() > 0 ? new EgressWithPenalty(it) : it).toList();
}

private int[] filterPathsAndGetStops(Predicate<RaptorAccessEgress> filter) {
return pathsByStop
.valueCollection()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.opentripplanner.raptor.rangeraptor.transit;

import org.opentripplanner.raptor.api.model.AbstractAccessEgressDelegator;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;

/**
* This decorator will add the time penalty to the duration of the egress and adjust the
* `requestedDepartureTime` when time-shifting the egress according to opening-hours.
*
* TODO PEN - Write more
*/
public class EgressWithPenalty extends AbstractAccessEgressDelegator {

public EgressWithPenalty(RaptorAccessEgress delegate) {
super(delegate);
}

@Override
public int durationInSeconds() {
return delegate().durationInSeconds() + delegate().timePenalty();
}

@Override
public int latestArrivalTime(int requestedArrivalTime) {
return delegate().latestArrivalTime(requestedArrivalTime - delegate().timePenalty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ protected DefaultAccessEgress(DefaultAccessEgress other, TimeAndCost penalty) {
}
this.stop = other.stop();
this.durationInSeconds = other.durationInSeconds();
// In the API we have a cost associated with the time-penalty. In Raptor, there is no
// association between the time-penalty and the cost. So, we add the time-penalty cost to
// the generalized cost here. In logic later on, we will remove it.
this.generalizedCost = other.c1() + penalty.cost().toCentiSeconds();
this.penalty = penalty;
this.lastState = other.getLastState();
Expand Down

0 comments on commit adb6ab6

Please sign in to comment.