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

org.opentripplanner.service.vehiclerental.street.VehicleRentalEdge Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.service.vehiclerental.street;

import java.util.Collections;
import java.util.Set;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.routing.api.request.StreetMode;
import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace;
import org.opentripplanner.street.model.RentalFormFactor;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.street.search.state.StateEditor;

/**
 * Renting or dropping off a rented vehicle edge.
 *
 * @author laurent
 */
public class VehicleRentalEdge extends Edge {

  public final RentalFormFactor formFactor;

  private VehicleRentalEdge(VehicleRentalPlaceVertex vertex, RentalFormFactor formFactor) {
    super(vertex, vertex);
    this.formFactor = formFactor;
  }

  public static VehicleRentalEdge createVehicleRentalEdge(
    VehicleRentalPlaceVertex vertex,
    RentalFormFactor formFactor
  ) {
    return connectToGraph(new VehicleRentalEdge(vertex, formFactor));
  }

  @Override
  public State[] traverse(State s0) {
    if (!s0.getRequest().mode().includesRenting()) {
      return State.empty();
    }

    var allowedRentalFormFactors = allowedModes(s0.getRequest().mode());
    if (!allowedRentalFormFactors.isEmpty() && !allowedRentalFormFactors.contains(formFactor)) {
      return State.empty();
    }

    StateEditor s1 = s0.edit(this);

    VehicleRentalPlaceVertex stationVertex = (VehicleRentalPlaceVertex) tov;
    VehicleRentalPlace station = stationVertex.getStation();
    String network = station.getNetwork();
    var preferences = s0.getPreferences().rental(formFactor.traverseMode);
    boolean realtimeAvailability = preferences.useAvailabilityInformation();

    if (station.networkIsNotAllowed(preferences)) {
      return State.empty();
    }

    boolean pickedUp;
    if (s0.getRequest().arriveBy()) {
      switch (s0.getVehicleRentalState()) {
        case BEFORE_RENTING -> {
          return State.empty();
        }
        case HAVE_RENTED -> {
          if (
            (realtimeAvailability && !station.allowDropoffNow()) ||
            !station.getAvailableDropoffFormFactors(realtimeAvailability).contains(formFactor)
          ) {
            return State.empty();
          }
          s1.dropOffRentedVehicleAtStation(formFactor, network, true);
          pickedUp = false;
        }
        case RENTING_FLOATING -> {
          // a very special case: an arriveBy search has started in a no-drop-off zone.
          // in such a case we mark this case in the state that speculatively picks up a vehicle
          // when leaving the no-drop-off zone (has no network) and check it here so that we cannot
          // begin the rental.
          // reminder: in an arriveBy search we traverse backwards so beginFloatingVehicle means
          // traversing from renting to walking.
          if (
            s0.stateData.noRentalDropOffZonesAtStartOfReverseSearch.contains(network) ||
            !station.getAvailablePickupFormFactors(realtimeAvailability).contains(formFactor)
          ) {
            return State.empty();
          }
          if (station.isFloatingVehicle()) {
            s1.beginFloatingVehicleRenting(formFactor, network, true);
            pickedUp = true;
          } else {
            return State.empty();
          }
        }
        case RENTING_FROM_STATION -> {
          if (
            (realtimeAvailability && !station.allowPickupNow()) ||
            !station.getAvailablePickupFormFactors(realtimeAvailability).contains(formFactor)
          ) {
            return State.empty();
          }
          // For arriveBy searches mayKeepRentedVehicleAtDestination is only set in State#getInitialStates(),
          // and so here it is checked if this bicycle could have been kept at the destination
          if (
            s0.mayKeepRentedVehicleAtDestination() &&
            !station.isArrivingInRentalVehicleAtDestinationAllowed()
          ) {
            return State.empty();
          }
          if (!hasCompatibleNetworks(network, s0.getVehicleRentalNetwork())) {
            return State.empty();
          }
          s1.beginVehicleRentingAtStation(formFactor, network, false, true);
          pickedUp = true;
        }
        default -> throw new IllegalStateException();
      }
    } else {
      switch (s0.getVehicleRentalState()) {
        case BEFORE_RENTING -> {
          if (
            (realtimeAvailability && !station.allowPickupNow()) ||
            !station.getAvailablePickupFormFactors(realtimeAvailability).contains(formFactor)
          ) {
            return State.empty();
          }
          if (station.isFloatingVehicle()) {
            s1.beginFloatingVehicleRenting(formFactor, network, false);
          } else {
            boolean mayKeep =
              preferences.allowArrivingInRentedVehicleAtDestination() &&
              station.isArrivingInRentalVehicleAtDestinationAllowed();
            s1.beginVehicleRentingAtStation(formFactor, network, mayKeep, false);
          }
          pickedUp = true;
        }
        case HAVE_RENTED -> {
          return State.empty();
        }
        case RENTING_FLOATING, RENTING_FROM_STATION -> {
          if (!hasCompatibleNetworks(network, s0.getVehicleRentalNetwork())) {
            return State.empty();
          }
          var formFactors = station.getAvailableDropoffFormFactors(realtimeAvailability);
          if (
            (realtimeAvailability && !station.allowDropoffNow()) ||
            !formFactors.contains(formFactor)
          ) {
            return State.empty();
          }
          if (
            !allowedRentalFormFactors.isEmpty() &&
            Collections.disjoint(allowedRentalFormFactors, formFactors)
          ) {
            return State.empty();
          }
          s1.dropOffRentedVehicleAtStation(formFactor, network, false);
          pickedUp = false;
        }
        default -> throw new IllegalStateException();
      }
    }

    s1.incrementWeight(
      pickedUp ? preferences.pickupCost().toSeconds() : preferences.dropOffCost().toSeconds()
    );
    s1.incrementTimeInMilliseconds(
      pickedUp ? preferences.pickupTime().toMillis() : preferences.dropOffTime().toMillis()
    );
    s1.setBackMode(null);
    return s1.makeStateArray();
  }

  @Override
  public I18NString getName() {
    return getToVertex().getName();
  }

  /**
   * @param stationNetwork The station network where we want to drop the bike off.
   * @param rentedNetwork  The networks of the station we rented the bike from.
   * @return true if the bike can be dropped off here, false if not.
   */
  private boolean hasCompatibleNetworks(String stationNetwork, String rentedNetwork) {
    /*
     * Special case for "null" networks ("catch-all" network defined).
     */
    if (rentedNetwork == null) {
      return true;
    }

    return rentedNetwork.equals(stationNetwork);
  }

  private static Set allowedModes(StreetMode streetMode) {
    return switch (streetMode) {
      case BIKE_RENTAL -> Set.of(RentalFormFactor.BICYCLE, RentalFormFactor.CARGO_BICYCLE);
      case SCOOTER_RENTAL -> Set.of(
        RentalFormFactor.SCOOTER,
        RentalFormFactor.SCOOTER_SEATED,
        RentalFormFactor.SCOOTER_STANDING
      );
      case CAR_RENTAL -> Set.of(RentalFormFactor.CAR);
      default -> Set.of();
    };
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy