org.opentripplanner.model.plan.PagingSearchWindowAdjuster Maven / Gradle / Ivy
Show all versions of otp Show documentation
package org.opentripplanner.model.plan;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import javax.annotation.Nullable;
/**
* The purpose of this class is to adjust the search-window for the next page so it better
* matches the requested number-of-itineraries. There is no exact science behind the logic in this
* class, but it performs well in practise, scaling fast to the appropriate size.
*/
public final class PagingSearchWindowAdjuster {
private final Duration minSearchWindow;
private final Duration maxSearchWindow;
/**
* Extra time is added to the search-window for the next request if the current
* result has few itineraries.
*
* Unit: minutes
*/
private final int[] pagingSearchWindowAdjustments;
public PagingSearchWindowAdjuster(
int minSearchWindowMinutes,
int maxSearchWindowMinutes,
List pagingSearchWindowAdjustments
) {
this.minSearchWindow = Duration.ofMinutes(minSearchWindowMinutes);
this.maxSearchWindow = Duration.ofMinutes(maxSearchWindowMinutes);
this.pagingSearchWindowAdjustments = pagingSearchWindowAdjustments.stream()
.mapToInt(d -> (int)d.toMinutes())
.toArray();
}
/**
* Take the given search-window and adjust it so it better matches the number-of-itineraries
* found in the search - this is likely to be a good estimate for the next/previous page.
*
* @param searchWindowUsed The search window used by raptor
* @param searchWindowStartTime The start time for the search window used
* @param rmItineraryDepartureTime If the search-window is cropped, this is the departure time
* of the first removed itinerary. This should be {@code null}
* if the search-window is not cropped in the itinerary filter.
* @param cropSearchWindowTail This indicates which end of the search-window to crop. If
* {@code true} the search-window is cropped at the end, and
* if {@code false} it is cropped in the beginning. If no
* rmItineraryDepartureTime exist, then we do not care.
*/
public Duration decreaseSearchWindow(
Duration searchWindowUsed,
Instant searchWindowStartTime,
Instant rmItineraryDepartureTime,
boolean cropSearchWindowTail
) {
// We found more itineraries than requested, decrease the search window
Duration searchWindowSlice = cropSearchWindowTail
? Duration.between(searchWindowStartTime, rmItineraryDepartureTime)
: Duration.between(rmItineraryDepartureTime, searchWindowStartTime.plus(searchWindowUsed));
return normalizeSearchWindow((int)searchWindowSlice.getSeconds());
}
/**
* If the number of returned itineraries are less than the requested number of itineraries,
* then increase the search window according to the configured
* {@code pagingSearchWindowAdjustments} This is done to avoid short search windows in low
* frequency areas, where the client would need to do multiple new request to fetch the next
* trips.
*/
public Duration increaseOrKeepSearchWindow(
Duration searchWindowUsed,
int nRequestedItineraries,
int nActualItinerariesFound
) {
if(nActualItinerariesFound >= nRequestedItineraries) {
return searchWindowUsed;
}
if (nActualItinerariesFound < pagingSearchWindowAdjustments.length) {
return normalizeSearchWindow(
// Multiply minutes with 60 to get seconds
(int)searchWindowUsed.getSeconds()
+ 60 * pagingSearchWindowAdjustments[nActualItinerariesFound]
);
}
// No change
return searchWindowUsed;
}
/**
* Round search-window({@code sw}) up:
*
* - if {@code sw < minSearchWindow } then search-window is set to `minSearchWindow`
*
- if {@code sw > maxSearchWindow} then return `maxSearchWindow`
*
- if {@code sw <= 4h} then round search-window up to closest 10 minutes
*
- if {@code sw > 4h} then round search-window up to closest 30 minutes
*
*/
Duration normalizeSearchWindow(int seconds) {
if (seconds < minSearchWindow.getSeconds()) {
return minSearchWindow;
}
if(seconds > maxSearchWindow.getSeconds()) {
return maxSearchWindow;
}
// Round down to the closest minute
int minutes = seconds / 60;
if (minutes <= 240) {
return Duration.ofMinutes(ceiling(minutes, 10));
}
return Duration.ofMinutes(ceiling(minutes, 30));
}
/**
* Round value to the closest increment of given {@code step}. This is used
* to round of a time or duration to the closest "step" of like 10 minutes.
*/
static int ceiling(int value, int step) {
if (value < 0) {
return (value / step) * step;
}
else {
return ((value + step - 1) / step) * step;
}
}
}