
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;
/**
* 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 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;
}
}
/**
* 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));
}
}