org.opentripplanner.transit.raptor.rangeraptor.transit.TripScheduleAlightSearch Maven / Gradle / Ivy
Show all versions of otp Show documentation
package org.opentripplanner.transit.raptor.rangeraptor.transit;
import java.util.function.IntUnaryOperator;
import javax.annotation.Nullable;
import org.opentripplanner.model.base.ToStringBuilder;
import org.opentripplanner.transit.raptor.api.transit.RaptorTimeTable;
import org.opentripplanner.transit.raptor.api.transit.RaptorTransferConstraint;
import org.opentripplanner.transit.raptor.api.transit.RaptorTripSchedule;
import org.opentripplanner.transit.raptor.api.transit.RaptorTripScheduleBoardOrAlightEvent;
import org.opentripplanner.transit.raptor.api.transit.RaptorTripScheduleSearch;
/**
* The purpose of this class is to optimize the search for a trip schedule for
* a given pattern and stop. Normally the search scan from the upper bound index
* and down, it can do so because the trips are ordered after the FIRST stop
* alight times. We also assume that trips do not pass each other; Hence
* trips in service on a given day will also be in order for all other stops.
* For trips operating on different service days (no overlapping) this assumption
* is not necessary true.
*
* The search use a binary search if the number of trip schedules is above a
* given threshold. A linear search is slow when the number of schedules is very
* large, let say more than 300 trip schedules.
*
* @param The TripSchedule type defined by the user of the raptor API.
*/
public final class TripScheduleAlightSearch
implements RaptorTripScheduleSearch, RaptorTripScheduleBoardOrAlightEvent {
private static final int NOT_SET = -1;
private final int nTripsBinarySearchThreshold;
private final RaptorTimeTable timeTable;
private final int nTrips;
private int latestAlightTime;
private int stopPositionInPattern;
private IntUnaryOperator arrivalTimes;
private T candidateTrip;
private int candidateTripIndex = NOT_SET;
TripScheduleAlightSearch(int scheduledTripBinarySearchThreshold, RaptorTimeTable timeTable) {
this.nTripsBinarySearchThreshold = scheduledTripBinarySearchThreshold;
this.timeTable = timeTable;
this.nTrips = timeTable.numberOfTripSchedules();
}
/* TripScheduleBoardOrAlightEvent implementation using fly-weight pattern */
@Override
public T getTrip() {
return candidateTrip;
}
@Override
public int getTripIndex() {
return candidateTripIndex;
}
@Override
public int getTime() {
return candidateTrip.arrival(stopPositionInPattern);
}
@Override
public int getStopPositionInPattern() {
return stopPositionInPattern;
}
@Override
public RaptorTransferConstraint getTransferConstraint() {
return RaptorTransferConstraint.REGULAR_TRANSFER;
}
/* TripScheduleSearch implementation */
/**
* Find the last trip leaving from the given stop BEFORE the the {@code latestAlightTime}, but
* after the given trip ({@code tripIndexLowerBound}).
*
* @param latestAlightTime The latest acceptable alight time (exclusive).
* @param stopPositionInPattern The stop to board.
* @param tripIndexLowerBound Upper bound for trip index to search for (exclusive).
*/
@Override
public RaptorTripScheduleBoardOrAlightEvent search(
int latestAlightTime,
int stopPositionInPattern,
int tripIndexLowerBound
) {
this.latestAlightTime = latestAlightTime;
this.stopPositionInPattern = stopPositionInPattern;
this.arrivalTimes = timeTable.getArrivalTimes(stopPositionInPattern);
this.candidateTrip = null;
this.candidateTripIndex = NOT_SET;
// No previous trip is found
if (tripIndexLowerBound == UNBOUNDED_TRIP_INDEX) {
if(nTrips > nTripsBinarySearchThreshold) {
return findFirstBoardingOptimizedForLargeSetOfTrips();
}
else {
return findBoardingSearchForwardInTime(0);
}
}
// We have already found a candidate in a previous search;
// Hence searching forward from the lower bound is the fastest way to proceed.
// We have to add 1 to the lower bound for go from exclusive to inclusive
return findBoardingSearchForwardInTime(tripIndexLowerBound + 1);
}
@Override
public String toString() {
return ToStringBuilder.of(TripScheduleAlightSearch.class)
.addObj("nTrips", nTrips)
.addObj("latestAlightTime", latestAlightTime)
.addObj("stopPos", stopPositionInPattern)
.addObj("tripIndex", candidateTripIndex)
.addObj("trip", candidateTrip)
.toString();
}
/* private methods */
private RaptorTripScheduleBoardOrAlightEvent findFirstBoardingOptimizedForLargeSetOfTrips() {
int indexBestGuess = binarySearchForTripIndex();
// Use the best guess from the binary search to look for a candidate trip
// We can not use upper bound to exit the search. We need to continue
// until we find a valid trip in service.
var result = findBoardingSearchForwardInTime(indexBestGuess);
// If a valid result is found and we can return
if (result != null) {
return this;
}
// No trip schedule above the best guess was found. This may happen if enough
// trips are not in service.
//
// So we have to search for the first valid trip schedule before that.
return findBoardingSearchBackwardsInTime(indexBestGuess);
}
/**
* This method search for the last scheduled trip arriving before the {@code latestAlightTime}.
* Only trips with a trip index greater than the given {@code tripIndexLowerBound} is considered.
*
* @param tripIndexLowerBound The trip index lower bound, where search start (inclusive).
*/
@Nullable
private RaptorTripScheduleBoardOrAlightEvent findBoardingSearchForwardInTime(int tripIndexLowerBound) {
for (int i = tripIndexLowerBound; i < nTrips; ++i) {
if (arrivalTimes.applyAsInt(i) <= latestAlightTime) {
candidateTripIndex = i;
} else {
// this trip arrives too late. We can break out of the loop since
// trips are sorted by departure time (trips in given schedule)
// Trips passing another trip is not accounted for if both are in service.
break;
}
}
if (candidateTripIndex == NOT_SET) { return null; }
candidateTrip = timeTable.getTripSchedule(candidateTripIndex);
return this;
}
/**
* This method search for the last scheduled trip arrival before the {@code latestAlightTime}.
* Only trips with a trip index in the range: {@code [0..tripIndexUpperBound-1]} is considered.
*
* @param tripIndexUpperBound The trip index upper bound, where search end (exclusive).
*/
@Nullable
private RaptorTripScheduleBoardOrAlightEvent findBoardingSearchBackwardsInTime(
final int tripIndexUpperBound
) {
for (int i = tripIndexUpperBound-1; i >=0; --i) {
if (arrivalTimes.applyAsInt(i) <= latestAlightTime) {
candidateTrip = timeTable.getTripSchedule(i);
candidateTripIndex = i;
return this;
}
}
return null;
}
/**
* Do a binary search to find the approximate lower bound index for where to start the search.
* We IGNORE if the trip schedule is in service.
*
* This is just a guess and we return when the trip with a best valid arrival is in the range of
* the next {@link #nTripsBinarySearchThreshold}.
*
* @return a better lower bound index (inclusive)
*/
private int binarySearchForTripIndex() {
int lower = 0, upper = nTrips;
// Do a binary search to find where to start the search.
// We IGNORE if the trip schedule is in service.
while (upper - lower > nTripsBinarySearchThreshold) {
int m = (lower + upper) / 2;
if (arrivalTimes.applyAsInt(m) <= latestAlightTime) {
lower = m;
}
else {
upper = m;
}
}
return lower;
}
}