use of org.opentripplanner.routing.trippattern.TripTimes in project OpenTripPlanner by opentripplanner.
the class SkipStop method apply.
@Override
public Collection<TripPattern> apply(TripPattern original) {
if (!couldMatch(original))
return Arrays.asList(original);
// figure out which stops we skip
TIntList skippedStops = new TIntArrayList();
// retained stops
// we use stop times to carry a little additional information, e.g. pickup/dropoff type.
List<StopTime> stopTimes = Lists.newArrayList();
{
int i = 0;
for (Stop stop : original.getStops()) {
if (stopId.contains(stop.getId().getId()))
skippedStops.add(i);
else {
// make a fake stop time
StopTime stopTime = new StopTime();
stopTime.setStop(stop);
stopTime.setPickupType(original.stopPattern.pickups[i]);
stopTime.setDropOffType(original.stopPattern.dropoffs[i]);
stopTimes.add(stopTime);
}
i++;
}
}
if (skippedStops.isEmpty()) {
LOG.warn("No stops found to skip on matched trip pattern {}", original);
return Arrays.asList(original);
}
if (original.getStops().size() - skippedStops.size() < 2) {
// TODO best way to handle this case?
LOG.warn("Trip with skipped stops would have less that two stops for TripPattern {}, not skipping stops", original);
return Arrays.asList(original);
}
// make the new stop pattern
StopPattern sp = new StopPattern(stopTimes);
TripPattern modified = new TripPattern(original.route, sp);
// Any trips that are not matched keep the original trip pattern, so put them here.
TripPattern originalClone = new TripPattern(original.route, original.stopPattern);
// keep track of what we have to return
boolean anyTripsMatched = false;
boolean allTripsMatched = true;
for (TripTimes tt : original.scheduledTimetable.tripTimes) {
if (!matches(tt.trip)) {
// this trip should not be modified
allTripsMatched = false;
originalClone.scheduledTimetable.addTripTimes(tt);
} else {
// This trip should be modified
anyTripsMatched = true;
modified.scheduledTimetable.addTripTimes(omitStops(tt, skippedStops.toArray()));
}
}
for (FrequencyEntry fe : original.scheduledTimetable.frequencyEntries) {
if (!matches(fe.tripTimes.trip)) {
allTripsMatched = false;
originalClone.scheduledTimetable.addFrequencyEntry(fe);
} else {
anyTripsMatched = true;
TripTimes newtt = omitStops(fe.tripTimes, skippedStops.toArray());
FrequencyEntry newfe = new FrequencyEntry(fe.startTime, fe.endTime, fe.headway, fe.exactTimes, newtt);
modified.scheduledTimetable.addFrequencyEntry(newfe);
}
}
if (!anyTripsMatched)
return Arrays.asList(original);
List<TripPattern> ret = Lists.newArrayList();
ret.add(modified);
if (!allTripsMatched)
ret.add(originalClone);
return ret;
}
use of org.opentripplanner.routing.trippattern.TripTimes in project OpenTripPlanner by opentripplanner.
the class Timetable method setServiceCodes.
/**
* Find and cache service codes. Duplicates information in trip.getServiceId for optimization.
*/
// TODO maybe put this is a more appropriate place
public void setServiceCodes(Map<AgencyAndId, Integer> serviceCodes) {
for (TripTimes tt : this.tripTimes) {
tt.serviceCode = serviceCodes.get(tt.trip.getServiceId());
}
// Repeated code... bad sign...
for (FrequencyEntry freq : this.frequencyEntries) {
TripTimes tt = freq.tripTimes;
tt.serviceCode = serviceCodes.get(tt.trip.getServiceId());
}
}
use of org.opentripplanner.routing.trippattern.TripTimes in project OpenTripPlanner by opentripplanner.
the class Timetable method getNextTrip.
/**
* Get the next (previous) trip that departs (arrives) from the specified stop at or after
* (before) the specified time.
* @return the TripTimes object representing the (possibly updated) best trip, or null if no
* trip matches both the time and other criteria.
*/
public TripTimes getNextTrip(State s0, ServiceDay serviceDay, int stopIndex, boolean boarding) {
/* Search at the state's time, but relative to midnight on the given service day. */
int time = serviceDay.secondsSinceMidnight(s0.getTimeSeconds());
// Adjust for possible boarding time TODO: This should be included in the trip and based on GTFS
if (boarding) {
time += s0.getOptions().getBoardTime(this.pattern.mode);
} else {
time -= s0.getOptions().getAlightTime(this.pattern.mode);
}
TripTimes bestTrip = null;
Stop currentStop = pattern.getStop(stopIndex);
// Linear search through the timetable looking for the best departure.
// We no longer use a binary search on Timetables because:
// 1. we allow combining trips from different service IDs on the same tripPattern.
// 2. We mix frequency-based and one-off TripTimes together on tripPatterns.
// 3. Stoptimes may change with realtime updates, and we cannot count on them being sorted.
// The complexity of keeping sorted indexes up to date does not appear to be worth the
// apparently minor speed improvement.
int bestTime = boarding ? Integer.MAX_VALUE : Integer.MIN_VALUE;
// We could invert this and skip some service days based on schedule overlap as in RRRR.
for (TripTimes tt : tripTimes) {
if (tt.isCanceled())
continue;
// TODO merge into call on next line
if (!serviceDay.serviceRunning(tt.serviceCode))
continue;
if (!tt.tripAcceptable(s0, stopIndex))
continue;
int adjustedTime = adjustTimeForTransfer(s0, currentStop, tt.trip, boarding, serviceDay, time);
if (adjustedTime == -1)
continue;
if (boarding) {
int depTime = tt.getDepartureTime(stopIndex);
// negative values were previously used for canceled trips/passed stops/skipped stops, but
if (depTime < 0)
continue;
// for canceled trips
if (depTime >= adjustedTime && depTime < bestTime) {
bestTrip = tt;
bestTime = depTime;
}
} else {
int arvTime = tt.getArrivalTime(stopIndex);
if (arvTime < 0)
continue;
if (arvTime <= adjustedTime && arvTime > bestTime) {
bestTrip = tt;
bestTime = arvTime;
}
}
}
// ACK all logic is identical to above.
// A sign that FrequencyEntries and TripTimes need a common interface.
FrequencyEntry bestFreq = null;
for (FrequencyEntry freq : frequencyEntries) {
TripTimes tt = freq.tripTimes;
if (tt.isCanceled())
continue;
// TODO merge into call on next line
if (!serviceDay.serviceRunning(tt.serviceCode))
continue;
if (!tt.tripAcceptable(s0, stopIndex))
continue;
int adjustedTime = adjustTimeForTransfer(s0, currentStop, tt.trip, boarding, serviceDay, time);
if (adjustedTime == -1)
continue;
LOG.debug(" running freq {}", freq);
if (boarding) {
// min transfer time included in search
int depTime = freq.nextDepartureTime(stopIndex, adjustedTime);
if (depTime < 0)
continue;
if (depTime >= adjustedTime && depTime < bestTime) {
bestFreq = freq;
bestTime = depTime;
}
} else {
// min transfer time included in search
int arvTime = freq.prevArrivalTime(stopIndex, adjustedTime);
if (arvTime < 0)
continue;
if (arvTime <= adjustedTime && arvTime > bestTime) {
bestFreq = freq;
bestTime = arvTime;
}
}
}
if (bestFreq != null) {
// A FrequencyEntry beat all the TripTimes.
// Materialize that FrequencyEntry entry at the given time.
bestTrip = bestFreq.tripTimes.timeShift(stopIndex, bestTime, boarding);
}
return bestTrip;
}
use of org.opentripplanner.routing.trippattern.TripTimes in project OpenTripPlanner by opentripplanner.
the class Timetable method createUpdatedTripTimes.
/**
* Apply the TripUpdate to the appropriate TripTimes from this Timetable. The existing TripTimes
* must not be modified directly because they may be shared with the underlying
* scheduledTimetable, or other updated Timetables. The {@link TimetableSnapshot} performs the
* protective copying of this Timetable. It is not done in this update method to avoid
* repeatedly cloning the same Timetable when several updates are applied to it at once. We
* assume here that all trips in a timetable are from the same feed, which should always be the
* case.
*
* @param tripUpdate GTFS-RT trip update
* @param timeZone time zone of trip update
* @param updateServiceDate service date of trip update
* @return new copy of updated TripTimes after TripUpdate has been applied on TripTimes of trip
* with the id specified in the trip descriptor of the TripUpdate; null if something
* went wrong
*/
public TripTimes createUpdatedTripTimes(TripUpdate tripUpdate, TimeZone timeZone, ServiceDate updateServiceDate) {
if (tripUpdate == null) {
LOG.error("A null TripUpdate pointer was passed to the Timetable class update method.");
return null;
}
// However, we want to apply trip updates on top of *scheduled* times
if (!tripUpdate.hasTrip()) {
LOG.error("TripUpdate object has no TripDescriptor field.");
return null;
}
TripDescriptor tripDescriptor = tripUpdate.getTrip();
if (!tripDescriptor.hasTripId()) {
LOG.error("TripDescriptor object has no TripId field");
return null;
}
String tripId = tripDescriptor.getTripId();
int tripIndex = getTripIndex(tripId);
if (tripIndex == -1) {
LOG.info("tripId {} not found in pattern.", tripId);
return null;
} else {
LOG.trace("tripId {} found at index {} in timetable.", tripId, tripIndex);
}
TripTimes newTimes = new TripTimes(getTripTimes(tripIndex));
if (tripDescriptor.hasScheduleRelationship() && tripDescriptor.getScheduleRelationship() == TripDescriptor.ScheduleRelationship.CANCELED) {
newTimes.cancel();
} else {
// The GTFS-RT reference specifies that StopTimeUpdates are sorted by stop_sequence.
Iterator<StopTimeUpdate> updates = tripUpdate.getStopTimeUpdateList().iterator();
if (!updates.hasNext()) {
LOG.warn("Won't apply zero-length trip update to trip {}.", tripId);
return null;
}
StopTimeUpdate update = updates.next();
int numStops = newTimes.getNumStops();
Integer delay = null;
for (int i = 0; i < numStops; i++) {
boolean match = false;
if (update != null) {
if (update.hasStopSequence()) {
match = update.getStopSequence() == newTimes.getStopSequence(i);
} else if (update.hasStopId()) {
match = pattern.getStop(i).getId().getId().equals(update.getStopId());
}
}
if (match) {
StopTimeUpdate.ScheduleRelationship scheduleRelationship = update.hasScheduleRelationship() ? update.getScheduleRelationship() : StopTimeUpdate.ScheduleRelationship.SCHEDULED;
if (scheduleRelationship == StopTimeUpdate.ScheduleRelationship.SKIPPED) {
LOG.warn("Partially canceled trips are unsupported by this method." + " Skipping TripUpdate.");
return null;
} else if (scheduleRelationship == StopTimeUpdate.ScheduleRelationship.NO_DATA) {
newTimes.updateArrivalDelay(i, 0);
newTimes.updateDepartureDelay(i, 0);
delay = 0;
} else {
long today = updateServiceDate.getAsDate(timeZone).getTime() / 1000;
if (update.hasArrival()) {
StopTimeEvent arrival = update.getArrival();
if (arrival.hasDelay()) {
delay = arrival.getDelay();
if (arrival.hasTime()) {
newTimes.updateArrivalTime(i, (int) (arrival.getTime() - today));
} else {
newTimes.updateArrivalDelay(i, delay);
}
} else if (arrival.hasTime()) {
newTimes.updateArrivalTime(i, (int) (arrival.getTime() - today));
delay = newTimes.getArrivalDelay(i);
} else {
LOG.error("Arrival time at index {} is erroneous.", i);
return null;
}
} else {
if (delay == null) {
newTimes.updateArrivalTime(i, TripTimes.UNAVAILABLE);
} else {
newTimes.updateArrivalDelay(i, delay);
}
}
if (update.hasDeparture()) {
StopTimeEvent departure = update.getDeparture();
if (departure.hasDelay()) {
delay = departure.getDelay();
if (departure.hasTime()) {
newTimes.updateDepartureTime(i, (int) (departure.getTime() - today));
} else {
newTimes.updateDepartureDelay(i, delay);
}
} else if (departure.hasTime()) {
newTimes.updateDepartureTime(i, (int) (departure.getTime() - today));
delay = newTimes.getDepartureDelay(i);
} else {
LOG.error("Departure time at index {} is erroneous.", i);
return null;
}
} else {
if (delay == null) {
newTimes.updateDepartureTime(i, TripTimes.UNAVAILABLE);
} else {
newTimes.updateDepartureDelay(i, delay);
}
}
}
if (updates.hasNext()) {
update = updates.next();
} else {
update = null;
}
} else {
if (delay == null) {
newTimes.updateArrivalTime(i, TripTimes.UNAVAILABLE);
newTimes.updateDepartureTime(i, TripTimes.UNAVAILABLE);
} else {
newTimes.updateArrivalDelay(i, delay);
newTimes.updateDepartureDelay(i, delay);
}
}
}
if (update != null) {
LOG.error("Part of a TripUpdate object could not be applied successfully to trip {}.", tripId);
return null;
}
}
if (!newTimes.timesIncreasing()) {
LOG.error("TripTimes are non-increasing after applying GTFS-RT delay propagation to trip {}.", tripId);
return null;
}
LOG.debug("A valid TripUpdate object was applied to trip {} using the Timetable class update method.", tripId);
return newTimes;
}
use of org.opentripplanner.routing.trippattern.TripTimes in project OpenTripPlanner by opentripplanner.
the class Timetable method finish.
/**
* Finish off a Timetable once all TripTimes have been added to it. This involves caching
* lower bounds on the running times and dwell times at each stop, and may perform other
* actions to compact the data structure such as trimming and deduplicating arrays.
*/
public void finish() {
int nStops = pattern.stopPattern.size;
int nHops = nStops - 1;
/* Find lower bounds on dwell and running times at each stop. */
minDwellTimes = new int[nHops];
minRunningTimes = new int[nHops];
Arrays.fill(minDwellTimes, Integer.MAX_VALUE);
Arrays.fill(minRunningTimes, Integer.MAX_VALUE);
// Concatenate raw TripTimes and those referenced from FrequencyEntries
List<TripTimes> allTripTimes = Lists.newArrayList(tripTimes);
for (FrequencyEntry freq : frequencyEntries) allTripTimes.add(freq.tripTimes);
for (TripTimes tt : allTripTimes) {
for (int h = 0; h < nHops; ++h) {
int dt = tt.getDwellTime(h);
if (minDwellTimes[h] > dt) {
minDwellTimes[h] = dt;
}
int rt = tt.getRunningTime(h);
if (minRunningTimes[h] > rt) {
minRunningTimes[h] = rt;
}
}
}
/* Find the time range over which this timetable is active. Allows departure search optimizations. */
minTime = Integer.MAX_VALUE;
maxTime = Integer.MIN_VALUE;
for (TripTimes tt : tripTimes) {
minTime = Math.min(minTime, tt.getDepartureTime(0));
maxTime = Math.max(maxTime, tt.getArrivalTime(nStops - 1));
}
// Again it seems reasonable to have a shared interface between FrequencyEntries and normal TripTimes.
for (FrequencyEntry freq : frequencyEntries) {
minTime = Math.min(minTime, freq.getMinDeparture());
maxTime = Math.max(maxTime, freq.getMaxArrival());
}
}
Aggregations