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

org.opentripplanner.ext.ridehailing.RideHailingAccessShifter Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.ext.ridehailing;

import java.time.Duration;
import java.time.Instant;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.opentripplanner.ext.ridehailing.model.ArrivalTime;
import org.opentripplanner.framework.geometry.WgsCoordinate;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.RoutingAccessEgress;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.transit.model.framework.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Utility method to shift the start of the journey to the earliest time that a vehicle can arrive.
 */
public class RideHailingAccessShifter {

  private static final Logger LOG = LoggerFactory.getLogger(RideHailingAccessShifter.class);
  /**
   * When is a start time far enough in the future so that we don't need to check the service and
   * simply presume that a vehicle can arrive on time.
   */
  private static final Duration MAX_DURATION_FROM_NOW = Duration.ofMinutes(30);

  /**
   * Given a list of {@link RoutingAccessEgress}, shift the access ones that contain driving
   * so that they only start at the time when the ride hailing vehicle can actually be there
   * to pick up passengers.
   */
  public static List shiftAccesses(
    boolean isAccess,
    List results,
    List services,
    RouteRequest request,
    Instant now
  ) {
    return results
      .stream()
      .map(ae -> {
        // only time-shift access legs on a car
        // (there could be walk-only accesses if you're close to the stop)
        if (isAccess && ae.getLastState().containsModeCar()) {
          var duration = fetchArrivalDelay(services, request, now);
          if (duration.isSuccess()) {
            return new RideHailingAccessAdapter(ae, duration.successValue());
          } else {
            return null;
          }
          // if it is an egress leg, we pretend that it arrives on time,
          // and we don't need to time-shift
        } else {
          return ae;
        }
      })
      .filter(Objects::nonNull)
      .collect(Collectors.toList());
  }

  /**
   * When you start a car hailing search for right now (which is common) you cannot assume to leave
   * right away but have to take into account the duration it takes for the hailing vehicle to
   * arrive.
   * 

* This method shifts the departure time by the appropriate amount so that the correct * access/egresses can be calculated. */ protected static Result arrivalDelay( RouteRequest req, List services, Instant now ) { if (shouldShift(req, now)) { return shiftTime(req, services, now); } else { return Result.success(Duration.ZERO); } } private static Result fetchArrivalDelay( List services, RouteRequest request, Instant now ) { // we have to shift the start time of a car hailing request because often we cannot leave right // away if (RideHailingAccessShifter.shouldShift(request, Instant.now())) { var shiftingResult = RideHailingAccessShifter.arrivalDelay(request, services, now); if (shiftingResult.isSuccess()) { return Result.success(shiftingResult.successValue()); } else { LOG.error( "Could not fetch arrival time for car hailing service: {}", shiftingResult.failureValue() ); return Result.failure(Error.TECHNICAL_ERROR); } } else { return Result.success(Duration.ZERO); } } private static boolean shouldShift(RouteRequest req, Instant now) { return ( req.journey().modes().accessMode == StreetMode.CAR_HAILING && req.dateTime().isBefore(now.plus(MAX_DURATION_FROM_NOW)) && !req.arriveBy() ); } private static Result shiftTime( RouteRequest req, List services, Instant now ) { try { var service = services.get(0); var arrivalTimeOpt = service .arrivalTimes(new WgsCoordinate(req.from().getCoordinate()), req.wheelchair()) .stream() .min(Comparator.comparing(ArrivalTime::duration)); if (arrivalTimeOpt.isPresent()) { var earliestArrival = arrivalTimeOpt.get(); var duration = earliestArrival.duration().minus(Duration.between(now, req.dateTime())); if (duration.isNegative()) { duration = Duration.ZERO; } return Result.success(duration); } else { return Result.failure(Error.NO_ARRIVAL_FOR_LOCATION); } } catch (ExecutionException e) { return Result.failure(Error.TECHNICAL_ERROR); } } enum Error { NO_ARRIVAL_FOR_LOCATION, TECHNICAL_ERROR, } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy