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

org.opentripplanner.routing.algorithm.RoutingWorker Maven / Gradle / Ivy

package org.opentripplanner.routing.algorithm;

import org.opentripplanner.model.plan.Itinerary;
import org.opentripplanner.routing.algorithm.filterchain.ItineraryFilter;
import org.opentripplanner.routing.algorithm.mapping.RaptorPathToItineraryMapper;
import org.opentripplanner.routing.algorithm.mapping.RoutingRequestToFilterChainMapper;
import org.opentripplanner.routing.algorithm.mapping.TripPlanMapper;
import org.opentripplanner.routing.algorithm.raptor.router.street.AccessEgressRouter;
import org.opentripplanner.routing.algorithm.raptor.router.street.DirectStreetRouter;
import org.opentripplanner.routing.algorithm.raptor.transit.AccessEgress;
import org.opentripplanner.routing.algorithm.raptor.transit.TransitLayer;
import org.opentripplanner.routing.algorithm.raptor.transit.TripSchedule;
import org.opentripplanner.routing.algorithm.raptor.transit.mappers.RaptorRequestMapper;
import org.opentripplanner.routing.algorithm.raptor.transit.request.RaptorRoutingRequestTransitData;
import org.opentripplanner.routing.api.request.RoutingRequest;
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.api.response.RoutingResponse;
import org.opentripplanner.routing.api.response.TripSearchMetadata;
import org.opentripplanner.routing.error.RoutingValidationException;
import org.opentripplanner.routing.framework.DebugAggregator;
import org.opentripplanner.routing.services.FareService;
import org.opentripplanner.standalone.server.Router;
import org.opentripplanner.transit.raptor.RaptorService;
import org.opentripplanner.transit.raptor.api.path.Path;
import org.opentripplanner.transit.raptor.api.request.RaptorRequest;
import org.opentripplanner.transit.raptor.api.response.RaptorResponse;
import org.opentripplanner.transit.raptor.rangeraptor.configure.RaptorConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * Does a complete transit search, including access and egress legs.
 * 

* This class has a request scope, hence the "Worker" name. */ public class RoutingWorker { private static final int NOT_SET = -1; /** * The numbers of days before the search date to consider when filtering trips for this search. * This is set to 1 to account for trips starting yesterday and crossing midnight so that they * can be boarded today. If there are trips that last multiple days, this will need to be * increased. */ private static final int ADDITIONAL_SEARCH_DAYS_BEFORE_TODAY = 1; /** * The number of days after the search date to consider when filtering trips for this search. * This is set to 1 to account for searches today having a search window that crosses midnight * and would also need to board trips starting tomorrow. If a search window that lasts more than * a day is used, this will need to be increased. */ private static final int ADDITIONAL_SEARCH_DAYS_AFTER_TODAY = 1; private static final Logger LOG = LoggerFactory.getLogger(RoutingWorker.class); private final RaptorService raptorService; /** An object that accumulates profiling and debugging info for inclusion in the response. */ public final DebugAggregator debugAggregator = new DebugAggregator(); private final RoutingRequest request; private Instant filterOnLatestDepartureTime = null; private int searchWindowUsedInSeconds = NOT_SET; private Itinerary firstRemovedItinerary = null; public RoutingWorker(RaptorConfig config, RoutingRequest request) { this.debugAggregator.startedCalculating(); this.raptorService = new RaptorService<>(config); this.request = request; } public RoutingResponse route(Router router) { List itineraries = new ArrayList<>(); List routingErrors = new ArrayList<>(); this.debugAggregator.finishedPrecalculating(); // Direct street routing try { itineraries.addAll(DirectStreetRouter.route(router, request)); } catch (RoutingValidationException e) { routingErrors.addAll(e.getRoutingErrors()); } this.debugAggregator.finishedDirectStreetRouter(); // Transit routing try { itineraries.addAll(routeTransit(router)); } catch (RoutingValidationException e) { routingErrors.addAll(e.getRoutingErrors()); } this.debugAggregator.finishedTransitRouter(); // Filter itineraries itineraries = filterItineraries(itineraries); LOG.debug("Return TripPlan with {} itineraries", itineraries.size()); this.debugAggregator.finishedFiltering(); return new RoutingResponse( TripPlanMapper.mapTripPlan(request, itineraries), createTripSearchMetadata(), routingErrors, debugAggregator ); } private Collection routeTransit(Router router) { request.setRoutingContext(router.graph); if (request.modes.transitModes.isEmpty()) { return Collections.emptyList(); } if (!router.graph.transitFeedCovers(request.dateTime)) { throw new RoutingValidationException(List.of( new RoutingError(RoutingErrorCode.OUTSIDE_SERVICE_PERIOD, InputField.DATE_TIME) )); } TransitLayer transitLayer = request.ignoreRealtimeUpdates ? router.graph.getTransitLayer() : router.graph.getRealtimeTransitLayer(); RaptorRoutingRequestTransitData requestTransitDataProvider; requestTransitDataProvider = new RaptorRoutingRequestTransitData( transitLayer, request.getDateTime().toInstant(), ADDITIONAL_SEARCH_DAYS_BEFORE_TODAY, ADDITIONAL_SEARCH_DAYS_AFTER_TODAY, request.modes.transitModes, request.rctx.bannedRoutes, request.walkSpeed ); this.debugAggregator.finishedPatternFiltering(); // Prepare access/egress transfers Collection accessTransfers = AccessEgressRouter.streetSearch(request, false, 2000, transitLayer.getStopIndex()); Collection egressTransfers = AccessEgressRouter.streetSearch(request, true, 2000, transitLayer.getStopIndex()); verifyEgressAccess(accessTransfers, egressTransfers); this.debugAggregator.finishedAccessEgress(); // Prepare transit search RaptorRequest raptorRequest = RaptorRequestMapper.mapRequest( request, requestTransitDataProvider.getStartOfTime(), accessTransfers, egressTransfers ); // Route transit RaptorResponse transitResponse = raptorService.route( raptorRequest, requestTransitDataProvider ); LOG.debug("Found {} transit itineraries", transitResponse.paths().size()); LOG.debug("Transit search params used: {}", transitResponse.requestUsed().searchParams()); this.debugAggregator.finishedRaptorSearch(); // Create itineraries RaptorPathToItineraryMapper itineraryMapper = new RaptorPathToItineraryMapper( transitLayer, requestTransitDataProvider.getStartOfTime(), request ); FareService fareService = request.getRoutingContext().graph.getService(FareService.class); List itineraries = new ArrayList<>(); for (Path path : transitResponse.paths()) { // Convert the Raptor/Astar paths to OTP API Itineraries Itinerary itinerary = itineraryMapper.createItinerary(path); // Decorate the Itineraries with fare information. // Itinerary and Leg are API model classes, lacking internal object references needed for effective // fare calculation. We derive the fares from the internal Path objects and add them to the itinerary. if (fareService != null) { itinerary.fare = fareService.getCost(path, transitLayer); } itineraries.add(itinerary); } checkIfTransitConnectionExists(transitResponse); // Filter itineraries away that depart after the latest-departure-time for depart after // search. These itineraries is a result of time-shifting the access leg and is needed for // the raptor to prune the results. These itineraries are often not ideal, but if they // pareto optimal for the "next" window, they will appear when a "next" search is performed. searchWindowUsedInSeconds = transitResponse.requestUsed().searchParams().searchWindowInSeconds(); if(!request.arriveBy && searchWindowUsedInSeconds > 0) { filterOnLatestDepartureTime = Instant.ofEpochSecond(request.dateTime + searchWindowUsedInSeconds); } this.debugAggregator.finishedItineraryCreation(); return itineraries; } private List filterItineraries(List itineraries) { ItineraryFilter filterChain = RoutingRequestToFilterChainMapper.createFilterChain( request, filterOnLatestDepartureTime, it -> firstRemovedItinerary = it ); return filterChain.filter(itineraries); } private void verifyEgressAccess( Collection access, Collection egress ) { boolean accessExist = !access.isEmpty(); boolean egressExist = !egress.isEmpty(); if(accessExist && egressExist) { return; } List routingErrors = new ArrayList<>(); if(!accessExist) { routingErrors.add( new RoutingError(RoutingErrorCode.NO_STOPS_IN_RANGE, InputField.FROM_PLACE)); } if(!egressExist) { routingErrors.add( new RoutingError(RoutingErrorCode.NO_STOPS_IN_RANGE, InputField.TO_PLACE)); } throw new RoutingValidationException(routingErrors); } /** * If no paths or search window is found, we assume there is no transit connection between * the origin and destination. */ private void checkIfTransitConnectionExists(RaptorResponse response) { int searchWindowUsed = response.requestUsed().searchParams().searchWindowInSeconds(); if (searchWindowUsed <= 0 && response.paths().isEmpty()) { throw new RoutingValidationException(List.of( new RoutingError(RoutingErrorCode.NO_TRANSIT_CONNECTION, null))); } } private TripSearchMetadata createTripSearchMetadata() { if(searchWindowUsedInSeconds == NOT_SET) { return null; } Instant reqTime = Instant.ofEpochSecond(request.dateTime); if (request.arriveBy) { return TripSearchMetadata.createForArriveBy( reqTime, searchWindowUsedInSeconds, firstRemovedItinerary == null ? null : firstRemovedItinerary.endTime().toInstant() ); } else { return TripSearchMetadata.createForDepartAfter( reqTime, searchWindowUsedInSeconds, firstRemovedItinerary == null ? null : firstRemovedItinerary.startTime().toInstant() ); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy