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

org.opentripplanner.ext.flex.template.FlexDirectPathFactory Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.ext.flex.template;

import static org.opentripplanner.model.StopTime.MISSING_VALUE;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
import org.opentripplanner.routing.graphfinder.NearbyStop;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.state.EdgeTraverser;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.booking.RoutingBookingInfo;

public class FlexDirectPathFactory {

  private final FlexAccessEgressCallbackAdapter callbackService;
  private final FlexPathCalculator accessPathCalculator;
  private final FlexPathCalculator egressPathCalculator;
  private final Duration maxTransferDuration;

  public FlexDirectPathFactory(
    FlexAccessEgressCallbackAdapter callbackService,
    FlexPathCalculator accessPathCalculator,
    FlexPathCalculator egressPathCalculator,
    Duration maxTransferDuration
  ) {
    this.callbackService = callbackService;
    this.accessPathCalculator = accessPathCalculator;
    this.egressPathCalculator = egressPathCalculator;
    this.maxTransferDuration = maxTransferDuration;
  }

  public Collection calculateDirectFlexPaths(
    Collection streetAccesses,
    Collection streetEgresses,
    List dates,
    int requestTime,
    boolean arriveBy
  ) {
    Collection directFlexPaths = new ArrayList<>();

    var flexAccessTemplates = new FlexAccessFactory(
      callbackService,
      accessPathCalculator,
      maxTransferDuration
    ).calculateFlexAccessTemplates(streetAccesses, dates);

    var flexEgressTemplates = new FlexEgressFactory(
      callbackService,
      egressPathCalculator,
      maxTransferDuration
    ).calculateFlexEgressTemplates(streetEgresses, dates);

    Multimap streetEgressByStop = HashMultimap.create();
    streetEgresses.forEach(it -> streetEgressByStop.put(it.stop, it));

    for (FlexAccessTemplate template : flexAccessTemplates) {
      StopLocation transferStop = template.getTransferStop();

      // TODO: Document or reimplement this. Why are we using the egress to see if the
      //      access-transfer-stop (last-stop) is used by at least one egress-template?
      //      Is it because:
      //      - of the group-stop expansion?
      //      - of the alight-restriction check?
      //      - nearest stop to trip match?
      //      Fix: Find out why and refactor out the business logic and reuse it.
      //      Problem: Any asymmetrical restriction witch apply/do not apply to the egress,
      //               but do not apply/apply to the access, like booking-notice.
      if (
        flexEgressTemplates.stream().anyMatch(t -> t.getAccessEgressStop().equals(transferStop))
      ) {
        for (NearbyStop egress : streetEgressByStop.get(transferStop)) {
          createDirectGraphPath(template, egress, arriveBy, requestTime).ifPresent(
            directFlexPaths::add
          );
        }
      }
    }

    return directFlexPaths;
  }

  private Optional createDirectGraphPath(
    FlexAccessTemplate accessTemplate,
    NearbyStop egress,
    boolean arriveBy,
    int requestTime
  ) {
    var accessNearbyStop = accessTemplate.accessEgress;
    var trip = accessTemplate.trip;
    int accessBoardStopPosition = accessTemplate.boardStopPosition;
    int accessAlightStopPosition = accessTemplate.alightStopPosition;
    int requestedBookingTime = accessTemplate.requestedBookingTime;

    var flexToVertex = egress.state.getVertex();

    if (!isRouteable(accessTemplate, flexToVertex)) {
      return Optional.empty();
    }

    var flexEdge = accessTemplate.getFlexEdge(flexToVertex, egress.stop);

    if (flexEdge == null) {
      return Optional.empty();
    }

    final State[] afterFlexState = flexEdge.traverse(accessNearbyStop.state);

    var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState[0], egress.edges);

    if (finalStateOpt.isEmpty()) {
      return Optional.empty();
    }

    var finalState = finalStateOpt.get();
    var flexDurations = accessTemplate.calculateFlexPathDurations(flexEdge, finalState);

    int timeShift;

    if (arriveBy) {
      int lastStopArrivalTime = flexDurations.mapToFlexTripArrivalTime(requestTime);
      int latestArrivalTime = trip.latestArrivalTime(
        lastStopArrivalTime,
        accessBoardStopPosition,
        accessAlightStopPosition,
        flexDurations.trip()
      );

      if (latestArrivalTime == MISSING_VALUE) {
        return Optional.empty();
      }

      // No need to time-shift latestArrivalTime for meeting the min-booking notice restriction,
      // the time is already as-late-as-possible
      var bookingInfo = RoutingBookingInfo.of(
        requestedBookingTime,
        trip.getPickupBookingInfo(accessTemplate.boardStopPosition)
      );
      if (bookingInfo.exceedsMinimumBookingNotice(latestArrivalTime)) {
        return Optional.empty();
      }

      // Shift from departing at departureTime to arriving at departureTime
      timeShift = flexDurations.mapToRouterArrivalTime(latestArrivalTime) - flexDurations.total();
    } else {
      int firstStopDepartureTime = flexDurations.mapToFlexTripDepartureTime(requestTime);

      // Time-shift departure so the minimum-booking-notice restriction is honored.
      var bookingInfo = trip.getPickupBookingInfo(accessBoardStopPosition);
      firstStopDepartureTime = RoutingBookingInfo.of(
        requestedBookingTime,
        bookingInfo
      ).earliestDepartureTime(firstStopDepartureTime);

      int earliestDepartureTime = trip.earliestDepartureTime(
        firstStopDepartureTime,
        accessBoardStopPosition,
        accessAlightStopPosition,
        flexDurations.trip()
      );

      if (earliestDepartureTime == MISSING_VALUE) {
        return Optional.empty();
      }

      timeShift = flexDurations.mapToRouterDepartureTime(earliestDepartureTime);
    }

    return Optional.of(new DirectFlexPath(timeShift, finalState));
  }

  protected boolean isRouteable(FlexAccessTemplate accessTemplate, Vertex flexVertex) {
    if (accessTemplate.accessEgress.state.getVertex() == flexVertex) {
      return false;
    } else return (
      accessTemplate.calculator.calculateFlexPath(
        accessTemplate.accessEgress.state.getVertex(),
        flexVertex,
        accessTemplate.boardStopPosition,
        accessTemplate.alightStopPosition
      ) !=
      null
    );
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy