diff --git a/src/ext/java/org/opentripplanner/ext/traveltime/TravelTimeResource.java b/src/ext/java/org/opentripplanner/ext/traveltime/TravelTimeResource.java index 87d7549927d..d42aa3f887b 100644 --- a/src/ext/java/org/opentripplanner/ext/traveltime/TravelTimeResource.java +++ b/src/ext/java/org/opentripplanner/ext/traveltime/TravelTimeResource.java @@ -9,6 +9,7 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.StreamingOutput; +import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.Period; @@ -72,12 +73,14 @@ public class TravelTimeResource { private final RaptorService raptorService; private final Graph graph; private final TransitService transitService; + private final Duration searchWindow; public TravelTimeResource( @Context OtpServerRequestContext serverContext, @QueryParam("location") String location, @QueryParam("time") String time, @QueryParam("cutoff") @DefaultValue("60m") List cutoffs, + @QueryParam("searchWindow") @DefaultValue("0s") String searchWindow, @QueryParam("modes") String modes, @QueryParam("arriveBy") @DefaultValue("false") boolean arriveBy ) { @@ -106,14 +109,15 @@ public TravelTimeResource( var parsedLocation = LocationStringParser.fromOldStyleString(location); var requestTime = time != null ? Instant.parse(time) : Instant.now(); routingRequest.setDateTime(requestTime); + this.searchWindow = DurationUtils.duration(searchWindow); if (routingRequest.arriveBy()) { - startTime = requestTime.minus(traveltimeRequest.maxCutoff); + startTime = requestTime.minus(traveltimeRequest.maxCutoff).minus(this.searchWindow); endTime = requestTime; routingRequest.setTo(parsedLocation); } else { startTime = requestTime; - endTime = startTime.plus(traveltimeRequest.maxCutoff); + endTime = startTime.plus(traveltimeRequest.maxCutoff).plus(this.searchWindow); routingRequest.setFrom(parsedLocation); } @@ -246,22 +250,21 @@ private List getInitialStates( if (!arrivals.reachedByTransit(index)) { continue; } - final int arrivalTime = arrivals.bestTransitArrivalTime(index); Vertex v = graph.getStopVertexForStopId(stop.getId()); if (v == null) { continue; } - Instant time = startOfTime.plusSeconds(arrivalTime).toInstant(); + int duration = arrivals.bestTransitArrivalDuration(index); + var time = routingRequest.arriveBy() + ? endTime.minusSeconds(duration) + : startTime.plusSeconds(duration); List egressStateDatas = StateData.getInitialStateDatas( egressStreetSearchRequest, mode -> new TravelTimeStateData(mode, time.getEpochSecond()) ); for (var stopStateData : egressStateDatas) { State s = new State(v, time, stopStateData, directStreetSearchRequest); - s.weight = - routingRequest.arriveBy() - ? time.until(endTime, ChronoUnit.SECONDS) - : startTime.until(time, ChronoUnit.SECONDS); + s.weight = duration; initialStates.add(s); } } @@ -276,8 +279,8 @@ private RaptorResponse route(Collection { */ SingleCriteriaStopArrivals extractBestTransitArrivals(); + /** + * Get transit arrival duration for each stop reached in the search. + */ + SingleCriteriaStopArrivals extractBestTransitDurations(); + /** * Extract information about the best number of transfers for each stop arrival. */ diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorWorkerResult.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorWorkerResult.java index a28f3ea09f6..03728eb7de2 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorWorkerResult.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/multicriteria/McRaptorWorkerResult.java @@ -52,6 +52,11 @@ public int value(int stop) { }; } + @Override + public SingleCriteriaStopArrivals extractBestTransitDurations() { + throw new UnsupportedOperationException(); + } + @Override public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { return new SingleCriteriaStopArrivals() { diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorWorkerResult.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorWorkerResult.java index bf4c3ddb1b0..1dfb09207f5 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorWorkerResult.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/StdRaptorWorkerResult.java @@ -36,6 +36,11 @@ public SingleCriteriaStopArrivals extractBestTransitArrivals() { return bestTimes.extractBestTransitArrivals(); } + @Override + public SingleCriteriaStopArrivals extractBestTransitDurations() { + return bestTimes.extractBestTransitDurations(); + } + @Override public SingleCriteriaStopArrivals extractBestNumberOfTransfers() { return state.extractBestNumberOfTransfers(); diff --git a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/besttimes/BestTimes.java b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/besttimes/BestTimes.java index 3807b26b6c6..4b1619da31a 100644 --- a/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/besttimes/BestTimes.java +++ b/src/main/java/org/opentripplanner/raptor/rangeraptor/standard/besttimes/BestTimes.java @@ -4,6 +4,7 @@ import java.util.BitSet; import org.opentripplanner.framework.tostring.ToStringBuilder; +import org.opentripplanner.raptor.api.RaptorConstants; import org.opentripplanner.raptor.rangeraptor.internalapi.SingleCriteriaStopArrivals; import org.opentripplanner.raptor.rangeraptor.internalapi.WorkerLifeCycle; import org.opentripplanner.raptor.rangeraptor.support.IntArraySingleCriteriaArrivals; @@ -27,6 +28,9 @@ */ public final class BestTimes { + /** Duration is always positive, and negative is lower so use unreached high value. */ + private static final int UNREACHED_DURATION = RaptorConstants.UNREACHED_HIGH; + /** The best times to reach a stop, across rounds and iterations. */ private final int[] times; @@ -35,12 +39,17 @@ public final class BestTimes { * both transit arrivals and access-on-board arrivals. */ private final int[] transitArrivalTimes; + /** + * The best duration from the departure time of the raptor round to the arrival at the stop + */ + private final int[] transitArrivalDurations; private final BitSet reachedByTransitCurrentRound; private final TransitCalculator calculator; /** Stops touched in the CURRENT round. */ private BitSet reachedCurrentRound; /** Stops touched by in LAST round. */ private BitSet reachedLastRound; + private int departureTime; public BestTimes(int nStops, TransitCalculator calculator, WorkerLifeCycle lifeCycle) { this.calculator = calculator; @@ -49,10 +58,11 @@ public BestTimes(int nStops, TransitCalculator calculator, WorkerLifeCycle li this.reachedLastRound = new BitSet(nStops); this.transitArrivalTimes = intArray(nStops, calculator.unreachedTime()); + this.transitArrivalDurations = intArray(nStops, UNREACHED_DURATION); this.reachedByTransitCurrentRound = new BitSet(nStops); // Attach to Worker life cycle - lifeCycle.onSetupIteration(ignore -> setupIteration()); + lifeCycle.onSetupIteration(this::setupIteration); lifeCycle.onPrepareForNextRound(round -> prepareForNextRound()); } @@ -112,6 +122,7 @@ public boolean isStopReachedByTransit(int stop) { public boolean updateBestTransitArrivalTime(int stop, int time) { if (isBestTransitArrivalTime(stop, time)) { setBestTransitTime(stop, time); + setBestTransitDuration(stop, time); return true; } return false; @@ -140,6 +151,10 @@ public SingleCriteriaStopArrivals extractBestTransitArrivals() { return new IntArraySingleCriteriaArrivals(calculator.unreachedTime(), transitArrivalTimes); } + public SingleCriteriaStopArrivals extractBestTransitDurations() { + return new IntArraySingleCriteriaArrivals(UNREACHED_DURATION, transitArrivalDurations); + } + @Override public String toString() { final int unreachedTime = calculator.unreachedTime(); @@ -171,10 +186,11 @@ boolean isStopReachedInCurrentRound(int stop) { * Clear all reached flags before we start a new iteration. This is important so stops visited in * the previous iteration in the last round does not "overflow" into the next iteration. */ - private void setupIteration() { + private void setupIteration(int departureTime) { // clear all touched stops to avoid constant reƫxploration reachedCurrentRound.clear(); reachedByTransitCurrentRound.clear(); + this.departureTime = departureTime; } /** @@ -206,6 +222,13 @@ private void setBestTransitTime(int stop, int time) { reachedByTransitCurrentRound.set(stop); } + private void setBestTransitDuration(int stop, int time) { + int duration = calculator.duration(departureTime, time); + if (duration < transitArrivalDurations[stop]) { + transitArrivalDurations[stop] = duration; + } + } + private void swapReachedCurrentAndLastRound() { BitSet tmp = reachedLastRound; reachedLastRound = reachedCurrentRound; diff --git a/src/main/java/org/opentripplanner/raptor/service/DefaultStopArrivals.java b/src/main/java/org/opentripplanner/raptor/service/DefaultStopArrivals.java index fa4df04f81a..11a6e653312 100644 --- a/src/main/java/org/opentripplanner/raptor/service/DefaultStopArrivals.java +++ b/src/main/java/org/opentripplanner/raptor/service/DefaultStopArrivals.java @@ -11,6 +11,7 @@ public class DefaultStopArrivals implements StopArrivals { private SingleCriteriaStopArrivals bestOverallArrivalTime = null; private SingleCriteriaStopArrivals bestTransitArrivalTime = null; + private SingleCriteriaStopArrivals bestTransitArrivalDuration = null; private SingleCriteriaStopArrivals bestNumberOfTransfers = null; private final RaptorWorkerResult results; @@ -39,6 +40,11 @@ public int bestTransitArrivalTime(int stopIndex) { return bestTransitArrivalTime().value(stopIndex); } + @Override + public int bestTransitArrivalDuration(int stopIndex) { + return bestTransitArrivalDuration().value(stopIndex); + } + @Override public int smallestNumberOfTransfers(int stopIndex) { return bestNumberOfTransfers().value(stopIndex); @@ -58,6 +64,13 @@ private SingleCriteriaStopArrivals bestTransitArrivalTime() { return bestTransitArrivalTime; } + private SingleCriteriaStopArrivals bestTransitArrivalDuration() { + if (bestTransitArrivalDuration == null) { + this.bestTransitArrivalDuration = results.extractBestTransitDurations(); + } + return bestTransitArrivalDuration; + } + private SingleCriteriaStopArrivals bestNumberOfTransfers() { if (bestNumberOfTransfers == null) { this.bestNumberOfTransfers = results.extractBestNumberOfTransfers();