
org.opentripplanner.transit.model.timetable.ScheduledTripTimes Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
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.framework.lang.IntUtils;
import org.opentripplanner.framework.time.DurationUtils;
import org.opentripplanner.framework.time.TimeUtils;
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;
/**
* 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 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