All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opentripplanner.routing.api.request.RouteRequest Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.routing.api.request;

import static org.opentripplanner.utils.time.DurationUtils.durationInSeconds;

import java.io.Serializable;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import org.opentripplanner.model.GenericLocation;
import org.opentripplanner.model.plan.SortOrder;
import org.opentripplanner.model.plan.paging.cursor.PageCursor;
import org.opentripplanner.routing.api.request.preference.RoutingPreferences;
import org.opentripplanner.routing.api.request.request.JourneyRequest;
import org.opentripplanner.routing.api.request.via.ViaLocation;
import org.opentripplanner.routing.api.response.InputField;
import org.opentripplanner.routing.api.response.RoutingError;
import org.opentripplanner.routing.api.response.RoutingErrorCode;
import org.opentripplanner.routing.error.RoutingValidationException;
import org.opentripplanner.standalone.config.routerconfig.TransitRoutingConfig;
import org.opentripplanner.utils.collection.ListSection;
import org.opentripplanner.utils.lang.ObjectUtils;
import org.opentripplanner.utils.time.DateUtils;
import org.opentripplanner.utils.tostring.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A trip planning request. Some parameters may not be honored by the trip planner for some or all
 * itineraries.
 * 

* All defaults should be specified here in the RouteRequest, NOT as annotations on query parameters * in web services that create RouteRequests. This establishes a priority chain for default values: * RouteRequest field initializers, then JSON router config, then query parameters. */ public class RouteRequest implements Cloneable, Serializable { private static final Logger LOG = LoggerFactory.getLogger(RouteRequest.class); private static final long NOW_THRESHOLD_SEC = durationInSeconds("15h"); /* FIELDS UNIQUELY IDENTIFYING AN SPT REQUEST */ private GenericLocation from; private GenericLocation to; private List via = Collections.emptyList(); private Instant dateTime = Instant.now().truncatedTo(ChronoUnit.SECONDS); @Nullable private Duration maxSearchWindow; private Duration searchWindow; private PageCursor pageCursor; private boolean timetableView = true; private boolean arriveBy = false; private int numItineraries = 50; private Locale locale = new Locale("en", "US"); private RoutingPreferences preferences = new RoutingPreferences(); private JourneyRequest journey = new JourneyRequest(); private boolean wheelchair = false; private Instant bookingTime; /* CONSTRUCTORS */ /** Constructor for options; modes defaults to walk and transit */ public RouteRequest() { // So that they are never null. from = new GenericLocation(null, null); to = new GenericLocation(null, null); } /* ACCESSOR/SETTER METHODS */ public void setJourney(JourneyRequest journey) { this.journey = journey; } public void setArriveBy(boolean arriveBy) { this.arriveBy = arriveBy; } public JourneyRequest journey() { return journey; } public RoutingPreferences preferences() { return preferences; } public RouteRequest withPreferences(Consumer body) { this.preferences = preferences.copyOf().apply(body).build(); return this; } /** * The booking time is used to exclude services which are not bookable at the * requested booking time. If a service is bookable at this time or later, the service * is included. This applies to FLEX access, egress and direct services. */ public Instant bookingTime() { return bookingTime; } public RouteRequest setBookingTime(Instant bookingTime) { this.bookingTime = bookingTime; return this; } void setPreferences(RoutingPreferences preferences) { this.preferences = preferences; } /** * Whether the trip must be wheelchair-accessible */ public boolean wheelchair() { return wheelchair; } public void setWheelchair(boolean wheelchair) { this.wheelchair = wheelchair; } /** * The epoch date/time in seconds that the trip should depart (or arrive, for requests where * arriveBy is true) *

* The search time for the current request. If the client have moved to the next page then this is * the adjusted search time - the dateTime passed in is ignored and replaced with by a time from * the pageToken. */ public Instant dateTime() { return dateTime; } /** * The dateTime will be set to a whole number of seconds. We don't do sub-second accuracy, * and if we set the millisecond part to a non-zero value, rounding will not be guaranteed * to be the same for departAt and arriveBy queries. * @param dateTime Either a departAt time or an arriveBy time, one second's accuracy */ public void setDateTime(Instant dateTime) { this.dateTime = dateTime.truncatedTo(ChronoUnit.SECONDS); } public void setDateTime(String date, String time, ZoneId tz) { ZonedDateTime dateObject = DateUtils.toZonedDateTime(date, time, tz); setDateTime(dateObject == null ? Instant.now() : dateObject.toInstant()); } /** * Is the trip originally planned withing the previous/next 15h? */ public boolean isTripPlannedForNow() { return Duration.between(dateTime, Instant.now()).abs().toSeconds() < NOW_THRESHOLD_SEC; } public SortOrder itinerariesSortOrder() { if (pageCursor != null) { return pageCursor.originalSortOrder(); } return arriveBy ? SortOrder.STREET_AND_DEPARTURE_TIME : SortOrder.STREET_AND_ARRIVAL_TIME; } /** * Adjust the 'dateTime' if the page cursor is set to "goto next/previous page". The date-time is * used for many things, for example finding the days to search, but the transit search is using * the cursor[if exist], not the date-time. *

* The direct mode is also unset when there is a page cursor because for anything other than the * initial page we don't want to see direct results. *

* See also {@link org.opentripplanner.routing.algorithm.raptoradapter.router.FilterTransitWhenDirectModeIsEmpty}, * it uses a direct search to prune transit. */ public void applyPageCursor() { if (pageCursor != null) { // We switch to "depart-after" search when paging next(lat==null). It does not make // sense anymore to keep the latest-arrival-time when going to the "next page". if (pageCursor.latestArrivalTime() == null) { arriveBy = false; } this.dateTime = arriveBy ? pageCursor.latestArrivalTime() : pageCursor.earliestDepartureTime(); journey.setModes(journey.modes().copyOf().withDirectMode(StreetMode.NOT_SET).build()); LOG.debug("Request dateTime={} set from pageCursor.", dateTime); } } /** * When paging we must crop the list of itineraries in the right end according to the sorting of * the original search and according to the paging direction (next or previous). We always * crop at the end of the initial search. This is a utility function delegating to the * pageCursor, if available. */ public ListSection cropItinerariesAt() { return pageCursor == null ? ListSection.TAIL : pageCursor.cropItinerariesAt(); } /** * Validate that the routing request contains both an origin and a destination. Origin and * destination can be specified either by a reference to a stop place or by geographical * coordinates. Origin and destination are required in a one-to-one search, but not in a * many-to-one or one-to-many. * TODO - Refactor and make separate requests for one-to-one and the other searches. * * @throws RoutingValidationException if either origin or destination is missing. */ public void validateOriginAndDestination() { List routingErrors = new ArrayList<>(2); if (from == null || !from.isSpecified()) { routingErrors.add( new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.FROM_PLACE) ); } if (to == null || !to.isSpecified()) { routingErrors.add(new RoutingError(RoutingErrorCode.LOCATION_NOT_FOUND, InputField.TO_PLACE)); } if (!routingErrors.isEmpty()) { throw new RoutingValidationException(routingErrors); } } /* INSTANCE METHODS */ /** * This method is used to clone the default message, and insert a current time. A typical use-case * is to copy the default request(from router-config), and then set all user specified parameters * before performing a routing search. */ public RouteRequest copyWithDateTimeNow() { RouteRequest copy = clone(); copy.setDateTime(Instant.now()); return copy; } @Override public RouteRequest clone() { try { RouteRequest clone = (RouteRequest) super.clone(); clone.journey = journey.clone(); return clone; } catch (CloneNotSupportedException e) { /* this will never happen since our super is the cloneable object */ throw new RuntimeException(e); } } /** The start location */ public GenericLocation from() { return from; } public void setFrom(GenericLocation from) { this.from = from; } /** The end location */ public GenericLocation to() { return to; } public void setTo(GenericLocation to) { this.to = to; } /** * TransferOptimization is applied to all results except via-visit requests. * TODO VIA - When the Optimized transfer support this, then this method should be removed. */ public boolean allowTransferOptimization() { return !isViaSearch() || via.stream().allMatch(ViaLocation::isPassThroughLocation); } /** * Return {@code true} if at least one via location is set! */ public boolean isViaSearch() { return !via.isEmpty(); } public List getViaLocations() { return via; } public RouteRequest setViaLocations(final List via) { this.via = via; return this; } /** * This is the time/duration in seconds from the earliest-departure-time(EDT) to * latest-departure-time(LDT). In case of a reverse search, it will be the time from earliest to * latest arrival time (LAT - EAT). *

* All optimal itineraries that depart within the search window are guaranteed to be found. *

* This is sometimes referred to as the Range Raptor Search Window - but could be used in a none * Transit search as well; Hence this is named search-window and not raptor-search-window. Do not * confuse this with the travel-window, which is the time between EDT to LAT. *

* Use {@code null} to unset, and {@link Duration#ZERO} to do one Raptor iteration. The value is * dynamically assigned a suitable value, if not set. In a small-to-medium size operation, you may * use a fixed value, like 60 minutes. If you have a mixture of high-frequency city routes and * infrequent long distant journeys, the best option is normally to use the dynamic auto * assignment. *

* There is no need to set this when going to the next/previous page anymore. */ public Duration searchWindow() { return searchWindow; } public void setSearchWindow(@Nullable Duration searchWindow) { if (searchWindow != null) { if (hasMaxSearchWindow() && searchWindow.toSeconds() > maxSearchWindow.toSeconds()) { throw new IllegalArgumentException("The search window cannot exceed " + maxSearchWindow); } if (searchWindow.isNegative()) { throw new IllegalArgumentException("The search window must be a positive duration"); } } this.searchWindow = searchWindow; } private boolean hasMaxSearchWindow() { return maxSearchWindow != null; } /** * For testing only. Use {@link TransitRoutingConfig#maxSearchWindow()} instead. * @see #initMaxSearchWindow(Duration) */ public Duration maxSearchWindow() { return maxSearchWindow; } /** * Initialize the maxSearchWindow from the transit config. This is necessary because the * default route request is configured before the {@link TransitRoutingConfig}. */ public void initMaxSearchWindow(Duration maxSearchWindow) { this.maxSearchWindow = ObjectUtils.requireNotInitialized( "maxSearchWindow", this.maxSearchWindow, Objects.requireNonNull(maxSearchWindow) ); } public Locale locale() { return locale; } public void setLocale(Locale locale) { this.locale = locale; } /** * Use the cursor to go to the next or previous "page" of trips. You should pass in the original * request as is. *

* The next page of itineraries will depart after the current results and the previous page of * itineraries will depart before the current results. *

* The paging does not support timeTableView=false and arriveBy=true, this will result in none * pareto-optimal results. */ public PageCursor pageCursor() { return pageCursor; } public void setPageCursorFromEncoded(String pageCursor) { this.pageCursor = PageCursor.decode(pageCursor); } /** * Search for the best trip options within a time window. If {@code true} two itineraries are * considered optimal if one is better on arrival time(earliest wins) and the other is better on * departure time(latest wins). *

* In combination with {@code arriveBy} this parameter cover the following 3 use cases: *

    *
  • * The traveler want to find the best alternative within a time window. Set * {@code timetableView=true} and {@code arriveBy=false}. This is the default, and if the * intention of the traveler is unknown, this gives the best result. This use-case includes * all itineraries in the two next use-cases. This option also work well with paging. *

    * Setting the {@code arriveBy=false}, covers the same use-case, but the input time is * interpreted as latest-arrival-time, and not earliest-departure-time. *

  • *
  • * The traveler want to find the best alternative with departure after a specific time. * For example: I am at the station now and want to get home as quickly as possible. * Set {@code timetableView=true} and {@code arriveBy=false}. Do not support paging. *
  • *
  • * Traveler want to find the best alternative with arrival before specific time. For * example going to a meeting. Set {@code timetableView=true} and {@code arriveBy=false}. * Do not support paging. *
  • *
* Default: true */ public boolean timetableView() { return timetableView; } public void setTimetableView(boolean timetableView) { this.timetableView = timetableView; } /** * Whether the trip should depart at dateTime (false, the default), or arrive at dateTime. */ public boolean arriveBy() { return arriveBy; } /** * The maximum number of itineraries to return. In OTP1 this parameter terminates the search, but * in OTP2 it crops the list of itineraries AFTER the search is complete. This parameter is a post * search filter function. A side effect from reducing the result is that OTP2 cannot guarantee to * find all pareto-optimal itineraries when paging. Also, a large search-window and a small {@code * numItineraries} waste computer CPU calculation time. *

* The default value is 50. This is a reasonably high threshold to prevent large amount of data to * be returned. Consider tuning the search-window instead of setting this to a small value. */ public int numItineraries() { return numItineraries; } public void setNumItineraries(int numItineraries) { this.numItineraries = numItineraries; } public String toString() { return ToStringBuilder.of(RouteRequest.class) .addObj("from", from) .addObj("to", to) .addDateTime("dateTime", dateTime) .addBoolIfTrue("arriveBy", arriveBy) .addObj("modes", journey.modes()) .addCol("filters", journey.transit().filters()) .toString(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy