package org.opentripplanner.analyst.scenario; import com.google.common.collect.Lists; import gnu.trove.list.TIntList; import gnu.trove.list.array.TIntArrayList; import gnu.trove.set.TIntSet; import gnu.trove.set.hash.TIntHashSet; import org.onebusaway.gtfs.model.Stop; import org.onebusaway.gtfs.model.StopTime; import org.opentripplanner.model.StopPattern; import org.opentripplanner.routing.edgetype.TripPattern; import org.opentripplanner.routing.trippattern.Deduplicator; import org.opentripplanner.routing.trippattern.FrequencyEntry; import org.opentripplanner.routing.trippattern.TripTimes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; import java.util.Collection; import java.util.List; /** * Skip stops and associated dwell times. * * Skipped stops are no longer served by the matched trips, and and dwell time at a skipped stop is removed from the schedule. * If stops are skipped at the start of a trip, the start of the trip is simply removed; the remaining times are not shifted. */ public class SkipStop extends TripPatternFilter { public static final long serialVersionUID = 1L; private static final Logger LOG = LoggerFactory.getLogger(SkipStop.class); /** Stops to skip. Note that setting this to null as a wildcard is not supported, obviously */ public Collection<String> stopId; @Override public String getType() { return "skip-stop"; } @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; } public TripTimes omitStops (TripTimes tt, int... stopsToSkip) { TIntSet skipped = new TIntHashSet(stopsToSkip); List<StopTime> newSts = Lists.newArrayList(); int cumulativeTime = -1; for (int i = 0; i < tt.getNumStops(); i++) { int hopTime = i != 0 ? tt.getArrivalTime(i) - tt.getDepartureTime(i - 1) : 0; int dwellTime = tt.getDepartureTime(i) - tt.getArrivalTime(i); // handle the first stop(s) being skipped if (cumulativeTime != -1) // note that we include hopTime before the check if the stop is included but dwell time after, // the assumption being that there is no dwell at a skipped stop. cumulativeTime += hopTime; if (skipped.contains(i)) continue; // this stop is still part of the trip // if this stop is now the first stop, get the time if (cumulativeTime == -1) cumulativeTime = tt.getArrivalTime(i); StopTime stopTime = new StopTime(); stopTime.setArrivalTime(cumulativeTime); cumulativeTime += dwellTime; stopTime.setDepartureTime(cumulativeTime); stopTime.setStopSequence(tt.getStopSequence(i)); stopTime.setTimepoint(tt.isTimepoint(i) ? 1 : 0); newSts.add(stopTime); } TripTimes newtt = new TripTimes(tt.trip, newSts, new Deduplicator()); newtt.serviceCode = tt.serviceCode; return newtt; } }