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

org.opentripplanner.street.search.state.StateData Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.street.search.state;

import static org.opentripplanner.street.search.state.VehicleRentalState.BEFORE_RENTING;
import static org.opentripplanner.street.search.state.VehicleRentalState.HAVE_RENTED;
import static org.opentripplanner.street.search.state.VehicleRentalState.RENTING_FLOATING;
import static org.opentripplanner.street.search.state.VehicleRentalState.RENTING_FROM_STATION;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.street.model.RentalFormFactor;
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.request.StreetSearchRequest;

/**
 * StateData contains the components of search state that are unlikely to be changed as often as
 * time or weight. This avoids frequent duplication, which should have a positive impact on both
 * time and space use during searches.
 */
public class StateData implements Cloneable {

  protected boolean vehicleParked;

  protected VehicleRentalState vehicleRentalState;

  protected boolean mayKeepRentedVehicleAtDestination;

  protected CarPickupState carPickupState;

  /**
   * The preferred mode, which may differ from backMode when for example walking with a bike. It may
   * also change during traversal when switching between modes as in the case of Park & Ride or Kiss
   * & Ride.
   */
  protected TraverseMode currentMode;

  /**
   * The mode that was used to traverse the backEdge
   */
  protected TraverseMode backMode;

  protected boolean backWalkingBike;

  public String vehicleRentalNetwork;

  public RentalFormFactor rentalVehicleFormFactor;

  /** This boolean is set to true upon transition from a normal street to a no-through-traffic street. */
  protected boolean enteredNoThroughTrafficArea;

  protected boolean insideNoRentalDropOffArea = false;
  public Set noRentalDropOffZonesAtStartOfReverseSearch = Set.of();

  /** Private constructor, use static methods to get a set of initial states. */
  private StateData(StreetMode requestMode) {
    currentMode =
      switch (requestMode) {
        // when renting or using a flex vehicle, you start on foot until you have found the vehicle
        case NOT_SET, WALK, BIKE_RENTAL, SCOOTER_RENTAL, CAR_RENTAL, FLEXIBLE -> TraverseMode.WALK;
        // when cycling all the way or to a stop, you start on your own bike
        case BIKE, BIKE_TO_PARK -> TraverseMode.BICYCLE;
        // when driving (not car rental) you start in your own car or your driver's car
        case CAR, CAR_TO_PARK, CAR_PICKUP, CAR_HAILING -> TraverseMode.CAR;
      };
  }

  /**
   * Returns a set of initial StateDatas based on the options from the RouteRequest
   */
  public static List getInitialStateDatas(StreetSearchRequest request) {
    var rentalPreferences = request.preferences().rental(request.mode());
    return getInitialStateDatas(
      request.mode(),
      request.arriveBy(),
      rentalPreferences != null
        ? rentalPreferences.allowArrivingInRentedVehicleAtDestination()
        : false
    );
  }

  /**
   * Returns an initial StateData based on the options from the {@link StreetSearchRequest}. This returns always
   * only a single state, which is considered the "base case", should there be several possible for
   * the given {@code request}.
   */
  public static StateData getBaseCaseStateData(StreetSearchRequest request) {
    var rentalPreferences = request.preferences().rental(request.mode());
    var stateDatas = getInitialStateDatas(
      request.mode(),
      request.arriveBy(),
      rentalPreferences != null
        ? rentalPreferences.allowArrivingInRentedVehicleAtDestination()
        : false
    );

    var baseCaseDatas =
      switch (request.mode()) {
        case WALK, BIKE, BIKE_TO_PARK, CAR, CAR_TO_PARK, FLEXIBLE, NOT_SET -> stateDatas;
        case CAR_PICKUP, CAR_HAILING -> stateDatas
          .stream()
          .filter(d -> d.carPickupState == CarPickupState.IN_CAR)
          .toList();
        case BIKE_RENTAL, SCOOTER_RENTAL, CAR_RENTAL -> {
          if (request.arriveBy()) {
            yield stateDatas
              .stream()
              .filter(d ->
                d.vehicleRentalState == RENTING_FROM_STATION ||
                d.vehicleRentalState == RENTING_FLOATING
              )
              .toList();
          } else {
            yield stateDatas;
          }
        }
      };

    if (baseCaseDatas.size() != 1) {
      throw new IllegalStateException(
        "Unable to create only a single state for %s".formatted(request)
      );
    }
    return baseCaseDatas.get(0);
  }

  private static List getInitialStateDatas(
    StreetMode requestMode,
    boolean arriveBy,
    boolean allowArrivingInRentedVehicleAtDestination
  ) {
    List res = new ArrayList<>();
    var proto = new StateData(requestMode);

    // carPickup searches may start and end in two distinct states:
    //   - CAR / IN_CAR where pickup happens directly at the bus stop
    //   - WALK / WALK_FROM_DROP_OFF or WALK_TO_PICKUP for cases with an initial walk
    // For forward/reverse searches to be symmetric both initial states need to be created.
    if (requestMode.includesPickup()) {
      var inCarPickupStateData = proto.clone();
      inCarPickupStateData.carPickupState = CarPickupState.IN_CAR;
      inCarPickupStateData.currentMode = TraverseMode.CAR;
      res.add(inCarPickupStateData);
      var walkingPickupStateData = proto.clone();
      walkingPickupStateData.carPickupState =
        arriveBy ? CarPickupState.WALK_FROM_DROP_OFF : CarPickupState.WALK_TO_PICKUP;
      walkingPickupStateData.currentMode = TraverseMode.WALK;
      res.add(walkingPickupStateData);
    }
    // Vehicle rental searches may end in four states (see State#isFinal()):
    // When searching forward:
    //   - RENTING_FROM_STATION when allowKeepingRentedVehicleAtDestination is set
    //   - RENTING_FLOATING
    //   - HAVE_RENTED
    // When searching backwards:
    //   - BEFORE_RENTING
    else if (requestMode.includesRenting()) {
      if (arriveBy) {
        if (allowArrivingInRentedVehicleAtDestination) {
          var keptVehicleStateData = proto.clone();
          keptVehicleStateData.vehicleRentalState = RENTING_FROM_STATION;
          keptVehicleStateData.currentMode = TraverseMode.BICYCLE;
          keptVehicleStateData.mayKeepRentedVehicleAtDestination = true;
          res.add(keptVehicleStateData);
        }
        var floatingRentalStateData = proto.clone();
        floatingRentalStateData.vehicleRentalState = RENTING_FLOATING;
        floatingRentalStateData.rentalVehicleFormFactor = toFormFactor(requestMode);
        floatingRentalStateData.currentMode = TraverseMode.BICYCLE;
        res.add(floatingRentalStateData);
        var stationReturnedStateData = proto.clone();
        stationReturnedStateData.vehicleRentalState = HAVE_RENTED;
        stationReturnedStateData.currentMode = TraverseMode.WALK;
        res.add(stationReturnedStateData);
      } else {
        var beforeRentalStateData = proto.clone();
        beforeRentalStateData.vehicleRentalState = BEFORE_RENTING;
        res.add(beforeRentalStateData);
      }
    }
    // If the itinerary is to begin with a car that is parked for transit the initial state is
    //   - In arriveBy searches is with the car already "parked" and in WALK mode
    //   - In departAt searches, we are in CAR mode and "unparked".
    else if (requestMode.includesParking()) {
      var parkAndRideStateData = proto.clone();
      parkAndRideStateData.vehicleParked = arriveBy;
      parkAndRideStateData.currentMode =
        parkAndRideStateData.vehicleParked
          ? TraverseMode.WALK
          : requestMode.includesBiking() ? TraverseMode.BICYCLE : TraverseMode.CAR;
      res.add(parkAndRideStateData);
    } else {
      res.add(proto.clone());
    }

    return res;
  }

  private static RentalFormFactor toFormFactor(StreetMode streetMode) {
    return switch (streetMode) {
      case BIKE_RENTAL -> RentalFormFactor.BICYCLE;
      case SCOOTER_RENTAL -> RentalFormFactor.SCOOTER;
      case CAR_RENTAL -> RentalFormFactor.CAR;
      // there is no default here, so you get a compiler error when you add a new value to the enum
      case NOT_SET,
        WALK,
        BIKE,
        BIKE_TO_PARK,
        CAR,
        CAR_TO_PARK,
        CAR_PICKUP,
        CAR_HAILING,
        FLEXIBLE -> throw new IllegalStateException(
        "Cannot convert street mode %s to a form factor".formatted(streetMode)
      );
    };
  }

  protected StateData clone() {
    try {
      return (StateData) super.clone();
    } catch (CloneNotSupportedException e1) {
      throw new IllegalStateException("This is not happening");
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy