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

org.opentripplanner.transit.model.timetable.ScheduledTripTimes Maven / Gradle / Ivy

The newest version!
package org.opentripplanner.transit.model.timetable;

import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_DWELL_TIME;
import static org.opentripplanner.transit.model.timetable.TimetableValidationError.ErrorCode.NEGATIVE_HOP_TIME;

import java.time.Duration;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.opentripplanner.framework.error.OtpError;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.transit.model.basic.Accessibility;
import org.opentripplanner.transit.model.framework.DataValidationException;
import org.opentripplanner.transit.model.framework.Deduplicator;
import org.opentripplanner.transit.model.framework.DeduplicatorService;
import org.opentripplanner.transit.model.timetable.booking.BookingInfo;
import org.opentripplanner.utils.lang.IntUtils;
import org.opentripplanner.utils.time.DurationUtils;
import org.opentripplanner.utils.time.TimeUtils;

/**
 * Regular/planed/scheduled read-only version of {@link TripTimes}. The set of static
 * trip-times are build during graph-build and can not be changed using real-time updates.
 *
 * @see RealTimeTripTimes for real-time version
 */
public final class ScheduledTripTimes implements TripTimes {

  /**
   * When time-shifting from one time-zone to another negative times may occur.
   */
  private static final int MIN_TIME = DurationUtils.durationInSeconds("-12h");

  /**
   * We allow a trip to last for maximum 20 days. In Norway the longest trip is 6 days.
   */
  private static final int MAX_TIME = DurationUtils.durationInSeconds("20d");

  /**
   * Implementation notes: This timeShift allows re-using the same scheduled arrival and departure
   * time arrays for many ScheduledTripTimes. It is also used in materializing frequency-based
   * ScheduledTripTimes.
   */
  private final int timeShift;
  private final int serviceCode;
  private final int[] arrivalTimes;
  private final int[] departureTimes;
  private final BitSet timepoints;
  private final Trip trip;
  private final List dropOffBookingInfos;
  private final List pickupBookingInfos;

  /**
   * Any number of array elements may point to the same I18NString instance if the headsign remains
   * unchanged between stops.
   */
  @Nullable
  private final I18NString[] headsigns;

  /**
   * A 2D array of String containing zero or more Via messages displayed at each stop in the
   * stop sequence. This reference be null if no stop in the entire sequence of stops has any via
   * strings. Any subarray may also be null or empty if no Via strings are displayed at that
   * particular stop. These nulls are allowed to conserve memory in the common case where there are
   * few or no via messages.
   */
  @Nullable
  private final String[][] headsignVias;

  private final int[] gtfsSequenceOfStopIndex;

  ScheduledTripTimes(ScheduledTripTimesBuilder builder) {
    this.timeShift = builder.timeShift();
    this.serviceCode = builder.serviceCode();
    this.arrivalTimes = Objects.requireNonNull(builder.arrivalTimes());
    this.departureTimes = Objects.requireNonNull(builder.departureTimes());
    this.timepoints = Objects.requireNonNull(builder.timepoints());
    this.trip = Objects.requireNonNull(builder.trip());
    this.pickupBookingInfos = Objects.requireNonNull(builder.pickupBookingInfos());
    this.dropOffBookingInfos = Objects.requireNonNull(builder.dropOffBookingInfos());
    this.headsigns = builder.headsigns();
    this.headsignVias = builder.headsignVias();
    this.gtfsSequenceOfStopIndex = builder.gtfsSequenceOfStopIndex();
    validate();
  }

  /**
   * Always provide a deduplicator when building the graph. No deduplication is ok when changing
   * simple fields like {@code timeShift} and {@code serviceCode} or even the prefered way in an
   * unittest.
   */
  public static ScheduledTripTimesBuilder of() {
    return new ScheduledTripTimesBuilder(null);
  }

  public static ScheduledTripTimesBuilder of(DeduplicatorService deduplicator) {
    return new ScheduledTripTimesBuilder(deduplicator);
  }

  public ScheduledTripTimesBuilder copyOf(Deduplicator deduplicator) {
    return new ScheduledTripTimesBuilder(
      timeShift,
      serviceCode,
      arrivalTimes,
      departureTimes,
      timepoints,
      trip,
      dropOffBookingInfos,
      pickupBookingInfos,
      headsigns,
      headsignVias,
      gtfsSequenceOfStopIndex,
      deduplicator
    );
  }

  /**
   * @see #copyOf(Deduplicator) copyOf(null)
   */
  public ScheduledTripTimesBuilder copyOfNoDuplication() {
    return copyOf(null);
  }

  @Override
  public RealTimeTripTimes copyScheduledTimes() {
    return RealTimeTripTimes.of(this);
  }

  @Override
  public TripTimes adjustTimesToGraphTimeZone(Duration shiftDelta) {
    return copyOfNoDuplication().plusTimeShift((int) shiftDelta.toSeconds()).build();
  }

  @Override
  public int getServiceCode() {
    return serviceCode;
  }

  @Override
  public int getScheduledArrivalTime(final int stop) {
    return timeShifted(arrivalTimes[stop]);
  }

  @Override
  public int getArrivalTime(final int stop) {
    return getScheduledArrivalTime(stop);
  }

  @Override
  public int getArrivalDelay(final int stop) {
    return getArrivalTime(stop) - timeShifted(arrivalTimes[stop]);
  }

  @Override
  public int getScheduledDepartureTime(final int stop) {
    return timeShifted(departureTimes[stop]);
  }

  @Override
  public int getDepartureTime(final int stop) {
    return getScheduledDepartureTime(stop);
  }

  @Override
  public int getDepartureDelay(final int stop) {
    return getDepartureTime(stop) - timeShifted(departureTimes[stop]);
  }

  @Override
  public boolean isTimepoint(final int stopIndex) {
    return timepoints.get(stopIndex);
  }

  @Override
  public Trip getTrip() {
    return trip;
  }

  @Override
  public int sortIndex() {
    return getDepartureTime(0);
  }

  @Override
  public BookingInfo getDropOffBookingInfo(int stop) {
    return dropOffBookingInfos.get(stop);
  }

  @Override
  public BookingInfo getPickupBookingInfo(int stop) {
    return pickupBookingInfos.get(stop);
  }

  @Override
  public boolean isScheduled() {
    return true;
  }

  @Override
  public boolean isCanceledOrDeleted() {
    return false;
  }

  @Override
  public boolean isCanceled() {
    return false;
  }

  @Override
  public boolean isDeleted() {
    return false;
  }

  @Override
  public RealTimeState getRealTimeState() {
    return RealTimeState.SCHEDULED;
  }

  @Override
  public boolean isCancelledStop(int stop) {
    return false;
  }

  @Override
  public boolean isRecordedStop(int stop) {
    return false;
  }

  @Override
  public boolean isNoDataStop(int stop) {
    return false;
  }

  @Override
  public boolean isPredictionInaccurate(int stop) {
    return false;
  }

  @Override
  public boolean isRealTimeUpdated(int stop) {
    return false;
  }

  @Override
  public I18NString getTripHeadsign() {
    return trip.getHeadsign();
  }

  @Override
  @Nullable
  public I18NString getHeadsign(final int stop) {
    return (headsigns != null && headsigns[stop] != null)
      ? headsigns[stop]
      : getTrip().getHeadsign();
  }

  @Override
  public List getHeadsignVias(final int stop) {
    if (headsignVias == null || headsignVias[stop] == null) {
      return List.of();
    }
    return List.of(headsignVias[stop]);
  }

  @Override
  public int getNumStops() {
    return arrivalTimes.length;
  }

  @Override
  public Accessibility getWheelchairAccessibility() {
    return trip.getWheelchairBoarding();
  }

  @Override
  public OccupancyStatus getOccupancyStatus(int ignore) {
    return OccupancyStatus.NO_DATA_AVAILABLE;
  }

  @Override
  public int gtfsSequenceOfStopIndex(final int stop) {
    return gtfsSequenceOfStopIndex[stop];
  }

  @Override
  public OptionalInt stopIndexOfGtfsSequence(int stopSequence) {
    if (gtfsSequenceOfStopIndex == null) {
      return OptionalInt.empty();
    }
    for (int i = 0; i < gtfsSequenceOfStopIndex.length; i++) {
      var sequence = gtfsSequenceOfStopIndex[i];
      if (sequence == stopSequence) {
        return OptionalInt.of(i);
      }
    }
    return OptionalInt.empty();
  }

  @Override
  public boolean equals(Object o) {
    throw new UnsupportedOperationException("Not implemented, implement if needed!");
  }

  @Override
  public int hashCode() {
    throw new UnsupportedOperationException("Not implemented, implement if needed!");
  }

  /* package local - only visible to timetable classes */

  int[] copyArrivalTimes() {
    return IntUtils.shiftArray(timeShift, arrivalTimes);
  }

  int[] copyDepartureTimes() {
    return IntUtils.shiftArray(timeShift, departureTimes);
  }

  I18NString[] copyHeadsigns(Supplier defaultValue) {
    return headsigns == null ? defaultValue.get() : Arrays.copyOf(headsigns, headsigns.length);
  }

  /* private methods */

  private void validate() {
    // Validate first departure time and last arrival time
    validateTimeInRange("departureTime", departureTimes, 0);
    validateTimeInRange("arrivalTime", arrivalTimes, arrivalTimes.length - 1);
    // TODO: This class is used by FLEX, so we can not validate increasing TripTimes
    // validateNonIncreasingTimes();
  }

  /**
   * When creating scheduled trip times we could potentially imply negative running or dwell times.
   * We really don't want those being used in routing. This method checks that all times are
   * increasing. The first stop arrival time and the last stops departure time is NOT checked -
   * these should be ignored by raptor.
   */
  private void validateNonIncreasingTimes() {
    final int lastStop = arrivalTimes.length - 1;

    // This check is currently used since Flex trips may have only one stop. This class should
    // not be used to represent FLEX, so remove this check and create new data classes for FLEX
    // trips.
    if (lastStop < 1) {
      return;
    }
    int prevDep = getDepartureTime(0);

    for (int i = 1; true; ++i) {
      final int arr = getArrivalTime(i);
      final int dep = getDepartureTime(i);

      if (prevDep > arr) {
        throw new DataValidationException(new TimetableValidationError(NEGATIVE_HOP_TIME, i, trip));
      }
      if (i == lastStop) {
        return;
      }
      if (dep < arr) {
        throw new DataValidationException(
          new TimetableValidationError(NEGATIVE_DWELL_TIME, i, trip)
        );
      }
      prevDep = dep;
    }
  }

  private int timeShifted(int time) {
    return timeShift + time;
  }

  private void validateTimeInRange(String field, int[] times, int stopPos) {
    int t = timeShifted(times[stopPos]);

    if (t < MIN_TIME || t > MAX_TIME) {
      throw new DataValidationException(
        OtpError.of(
          "TripTimeOutOfRange",
          "The %s is not in range[%s, %s]. Time: %s, stop-pos: %d, trip: %s.",
          field,
          DurationUtils.durationToStr(MIN_TIME),
          DurationUtils.durationToStr(MAX_TIME),
          TimeUtils.timeToStrLong(t),
          stopPos,
          trip.getId()
        )
      );
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy