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

org.opentripplanner.raptor.service.RangeRaptorDynamicSearch Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.raptor.service;

import static org.opentripplanner.raptor.api.model.SearchDirection.FORWARD;
import static org.opentripplanner.raptor.api.model.SearchDirection.REVERSE;
import static org.opentripplanner.raptor.api.request.RaptorProfile.MULTI_CRITERIA;
import static org.opentripplanner.raptor.service.HeuristicToRunResolver.resolveHeuristicToRunBasedOnOptimizationsAndSearchParameters;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.opentripplanner.framework.application.OTPRequestTimeoutException;
import org.opentripplanner.raptor.RaptorService;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.request.RaptorRequest;
import org.opentripplanner.raptor.api.request.SearchParams;
import org.opentripplanner.raptor.api.request.SearchParamsBuilder;
import org.opentripplanner.raptor.api.response.RaptorResponse;
import org.opentripplanner.raptor.configure.RaptorConfig;
import org.opentripplanner.raptor.rangeraptor.internalapi.Heuristics;
import org.opentripplanner.raptor.rangeraptor.internalapi.RaptorWorker;
import org.opentripplanner.raptor.rangeraptor.transit.RaptorSearchWindowCalculator;
import org.opentripplanner.raptor.spi.RaptorTransitDataProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This search helps the {@link RaptorService} to configure
 * heuristics and set dynamic search parameters like EDT, LAT and raptor-search-window.
 * 

* If possible the forward and reverse heuristics will be run in parallel. *

* Depending on which optimization is enabled and which search parameters are set a forward and/or a * reverse "single-iteration" raptor search is performed and heuristics are collected. This is used * to configure the "main" multi-iteration RangeRaptor search. */ public class RangeRaptorDynamicSearch { private static final Logger LOG = LoggerFactory.getLogger(RangeRaptorDynamicSearch.class); private final RaptorConfig config; private final RaptorTransitDataProvider transitData; private final RaptorRequest originalRequest; private final RaptorSearchWindowCalculator dynamicSearchParamsCalculator; private final HeuristicSearchTask fwdHeuristics; private final HeuristicSearchTask revHeuristics; public RangeRaptorDynamicSearch( RaptorConfig config, RaptorTransitDataProvider transitData, RaptorRequest originalRequest ) { this.config = config; this.transitData = transitData; this.originalRequest = originalRequest; this.dynamicSearchParamsCalculator = config.searchWindowCalculator().withSearchParams(originalRequest.searchParams()); this.fwdHeuristics = new HeuristicSearchTask<>(FORWARD, "Forward", config, transitData); this.revHeuristics = new HeuristicSearchTask<>(REVERSE, "Reverse", config, transitData); } public RaptorResponse route() { try { enableHeuristicSearchBasedOnOptimizationsAndSearchParameters(); // Run heuristics, if no destination is reached runHeuristics(); // Set search-window and other dynamic calculated parameters RaptorRequest dynamicRequest = originalRequest; dynamicRequest = requestWithDynamicSearchParams(dynamicRequest); return createAndRunDynamicRRWorker(dynamicRequest); } catch (DestinationNotReachedException e) { return new RaptorResponse<>( Collections.emptyList(), null, // If a trip exist(forward heuristics succeed), but is outside the calculated // search-window, then set the search-window params as if the request was // performed. This enables the client to page to the next window requestWithDynamicSearchParams(originalRequest), false ); } } /** * Only exposed for testing purposes */ @Nullable public Heuristics getDestinationHeuristics() { if (!originalRequest.useDestinationPruning()) { return null; } LOG.debug("RangeRaptor - Destination pruning enabled."); return revHeuristics.result(); } /** * Create and prepare heuristic search (both FORWARD and REVERSE) based on optimizations and input * search parameters. This is done for Standard and Multi-criteria profiles only. */ private void enableHeuristicSearchBasedOnOptimizationsAndSearchParameters() { // We delegate this to a static method to be able to write unit test on this logic resolveHeuristicToRunBasedOnOptimizationsAndSearchParameters( originalRequest, fwdHeuristics::enable, revHeuristics::enable ); } /** * Run standard "singe-iteration" raptor search to calculate heuristics - this should be really * fast to run compared with a (multi-criteria) range-raptor search. * * @throws DestinationNotReachedException if destination is not reached. */ private void runHeuristics() { if (isItPossibleToRunHeuristicsInParallel()) { runHeuristicsInParallel(); } else { runHeuristicsSequentially(); } fwdHeuristics.debugCompareResult(revHeuristics); } private RaptorResponse createAndRunDynamicRRWorker(RaptorRequest request) { LOG.debug("Main request: {}", request); RaptorWorker raptorWorker; // Create worker if (request.profile().is(MULTI_CRITERIA)) { raptorWorker = config.createMcWorker(transitData, request, getDestinationHeuristics()); } else { raptorWorker = config.createStdWorker(transitData, request); } // Route var result = raptorWorker.route(); // create and return response return new RaptorResponse<>( result.extractPaths(), new DefaultStopArrivals(result), request, // This method is not run unless the heuristic reached the destination true ); } private boolean isItPossibleToRunHeuristicsInParallel() { SearchParams s = originalRequest.searchParams(); return ( config.isMultiThreaded() && originalRequest.runInParallel() && s.isEarliestDepartureTimeSet() && s.isLatestArrivalTimeSet() && fwdHeuristics.isEnabled() && revHeuristics.isEnabled() ); } /** * @throws DestinationNotReachedException if destination is not reached */ private void runHeuristicsInParallel() { fwdHeuristics.withRequest(originalRequest); revHeuristics.withRequest(originalRequest); Future asyncResult = null; try { asyncResult = config.threadPool().submit(fwdHeuristics::run); revHeuristics.run(); asyncResult.get(); LOG.debug( "Route using RangeRaptor - " + "REVERSE and FORWARD heuristic search performed in parallel." ); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // propagate interruption to the running task. asyncResult.cancel(true); throw new OTPRequestTimeoutException(); } catch (ExecutionException e) { if (e.getCause() instanceof DestinationNotReachedException) { throw new DestinationNotReachedException(); } LOG.error(e.getMessage() + ". Request: " + originalRequest, e); throw new IllegalStateException( "Failed to run FORWARD/REVERSE heuristic search in parallel. Details: " + e.getMessage() ); } } /** * @throws DestinationNotReachedException if destination is not reached */ private void runHeuristicsSequentially() { List> tasks = listTasksInOrder(); if (tasks.isEmpty()) { return; } // Run the first heuristic search Heuristics result = runHeuristicSearchTask(tasks.get(0)); calculateDynamicSearchParametersFromHeuristics(result); if (tasks.size() == 1) { return; } // Run the second heuristic search runHeuristicSearchTask(tasks.get(1)); } private Heuristics runHeuristicSearchTask(HeuristicSearchTask task) { RaptorRequest request = task.getDirection().isForward() ? requestForForwardHeurSearchWithDynamicSearchParams() : requestForReverseHeurSearchWithDynamicSearchParams(); task.withRequest(request).run(); return task.result(); } /** * If the earliest-departure-time(EDT) is set, the task order should be: *

    *
  1. {@code FORWARD}
  2. *
  3. {@code REVERSE}
  4. *
* If not EDT is set, the latest-arrival-time is set, and the order should be the opposite, * with {@code REVERSE} first */ private List> listTasksInOrder() { boolean performForwardFirst = originalRequest.searchParams().isEarliestDepartureTimeSet(); List> list = performForwardFirst ? List.of(fwdHeuristics, revHeuristics) : List.of(revHeuristics, fwdHeuristics); return list.stream().filter(HeuristicSearchTask::isEnabled).collect(Collectors.toList()); } private RaptorRequest requestForForwardHeurSearchWithDynamicSearchParams() { if (originalRequest.searchParams().isEarliestDepartureTimeSet()) { return originalRequest; } return originalRequest .mutate() .searchParams() .earliestDepartureTime(transitData.getValidTransitDataStartTime()) .build(); } private RaptorRequest requestForReverseHeurSearchWithDynamicSearchParams() { if (originalRequest.searchParams().isLatestArrivalTimeSet()) { return originalRequest; } return originalRequest .mutate() .searchParams() .latestArrivalTime( transitData.getValidTransitDataEndTime() + originalRequest.searchParams().accessEgressMaxDurationSeconds() ) .build(); } private RaptorRequest requestWithDynamicSearchParams(RaptorRequest request) { SearchParamsBuilder builder = request.mutate().searchParams(); if (!request.searchParams().isEarliestDepartureTimeSet()) { builder.earliestDepartureTime(dynamicSearchParamsCalculator.getEarliestDepartureTime()); } if (!request.searchParams().isSearchWindowSet()) { builder.searchWindowInSeconds(dynamicSearchParamsCalculator.getSearchWindowSeconds()); } // We do not set the latest-arrival-time, because we do not want to limit the forward // multi-criteria search, it does not have much effect on the performance - we only risk // loosing optimal results. return builder.build(); } private void calculateDynamicSearchParametersFromHeuristics(@Nullable Heuristics heuristics) { if (heuristics != null) { dynamicSearchParamsCalculator .withHeuristics( heuristics.bestOverallJourneyTravelDuration(), heuristics.minWaitTimeForJourneysReachingDestination() ) .calculate(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy