use of org.opentripplanner.routing.trippattern.FrequencyEntry 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.FrequencyEntry 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.FrequencyEntry 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.FrequencyEntry 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());
}
}
use of org.opentripplanner.routing.trippattern.FrequencyEntry in project OpenTripPlanner by opentripplanner.
the class AnalystProfileRouterPrototype method route.
public TimeSurface.RangeSet route() {
// NOT USED here, however FIXME this is not threadsafe, needs lock graph.index.clusterStopsAsNeeded();
LOG.info("access modes: {}", request.accessModes);
LOG.info("egress modes: {}", request.egressModes);
LOG.info("direct modes: {}", request.directModes);
// Establish search timeouts
searchBeginTime = System.currentTimeMillis();
abortTime = searchBeginTime + TIMEOUT * 1000;
// TimeWindow could constructed in the caller, which does have access to the graph index.
this.window = new TimeWindow(request.fromTime, request.toTime, graph.index.servicesRunning(request.date));
fromStops = findClosestStops(TraverseMode.WALK);
LOG.info("From patterns/stops: {}", fromStops);
/* Initialize time range tracker to begin the search. */
TimeRange.Tracker times = new TimeRange.Tracker();
for (Stop stop : fromStops.keySet()) {
times.set(stop, fromStops.get(stop));
}
Set<Stop> stopsUpdated = fromStops.keySet();
for (int round = 0; round < MAX_RIDES; round++) {
// TODO maybe even loop until no updates happen? That should happen automatically if MAX_RIDES is high enough.
/* Get all patterns passing through stops updated in the last round, then reinitialize the updated stops set. */
Set<TripPattern> patternsUpdated = uniquePatternsVisiting(stopsUpdated);
LOG.info("ROUND {} : {} stops and {} patterns to explore.", round, stopsUpdated.size(), patternsUpdated.size());
stopsUpdated = Sets.newHashSet();
/* RAPTOR style: iterate over each pattern once. */
for (TripPattern pattern : patternsUpdated) {
// checkTimeout();
TimeRange rangeBeingPropagated = null;
List<Stop> stops = pattern.getStops();
FrequencyEntry freq = pattern.getSingleFrequencyEntry();
if (freq == null)
continue;
TripTimes tt = freq.tripTimes;
int headway = freq.headway;
for (int sidx = 0; sidx < stops.size(); sidx++) {
Stop stop = stops.get(sidx);
TimeRange existingRange = times.get(stop);
TimeRange reBoardRange = (existingRange != null) ? existingRange.wait(headway) : null;
if (rangeBeingPropagated == null) {
// We do not yet have a range worth propagating
if (reBoardRange != null) {
// this is a fresh protective copy
rangeBeingPropagated = reBoardRange;
}
} else {
// We already have a range that is being propagated along the pattern.
// We are certain sidx >= 1 here because we have already boarded in a previous iteration.
TimeRange arrivalRange = rangeBeingPropagated.shift(tt.getRunningTime(sidx - 1));
if (times.add(stop, arrivalRange)) {
// The propagated time improved the best known time in some way.
stopsUpdated.add(stop);
}
// TODO handle case where arrival and departure are different
rangeBeingPropagated = arrivalRange.shift(tt.getDwellTime(sidx));
if (reBoardRange != null) {
rangeBeingPropagated.mergeIn(reBoardRange);
}
}
}
}
/* Transfer from updated stops to adjacent stops before beginning the next round.
Iterate over a protective copy because we add more stops to the updated list during iteration. */
if (!graph.hasDirectTransfers) {
throw new RuntimeException("Requires the SimpleTransfers generated in long distance mode.");
}
for (Stop stop : Lists.newArrayList(stopsUpdated)) {
Collection<Edge> outgoingEdges = graph.index.stopVertexForStop.get(stop).getOutgoing();
for (SimpleTransfer transfer : Iterables.filter(outgoingEdges, SimpleTransfer.class)) {
Stop targetStop = ((TransitStop) transfer.getToVertex()).getStop();
double walkTime = transfer.getDistance() / request.walkSpeed;
TimeRange rangeAfterTransfer = times.get(stop).shift((int) walkTime);
if (times.add(targetStop, rangeAfterTransfer)) {
stopsUpdated.add(targetStop);
}
}
}
}
LOG.info("Done with transit.");
LOG.info("Propagating from transit stops to the street network...");
// Grab a cached map of distances to street intersections from each transit stop
StopTreeCache stopTreeCache = graph.index.getStopTreeCache();
// Iterate over all stops that were reached in the transit part of the search
for (Stop stop : times) {
TransitStop tstop = graph.index.stopVertexForStop.get(stop);
// Iterate over street intersections in the vicinity of this particular transit stop.
// Shift the time range at this transit stop, merging it into that for all reachable street intersections.
TimeRange rangeAtTransitStop = times.get(stop);
// FIXME stopTreeCache.getDistancesForStop(tstop);
TObjectIntMap<Vertex> distanceToVertex = null;
for (TObjectIntIterator<Vertex> iter = distanceToVertex.iterator(); iter.hasNext(); ) {
iter.advance();
Vertex vertex = iter.key();
// distance in meters over walkspeed in meters per second --> seconds
int egressWalkTimeSeconds = (int) (iter.value() / request.walkSpeed);
if (egressWalkTimeSeconds > request.maxWalkTime * 60) {
continue;
}
TimeRange propagatedRange = rangeAtTransitStop.shift(egressWalkTimeSeconds);
TimeRange existingTimeRange = propagatedTimes.get(vertex);
if (existingTimeRange == null) {
propagatedTimes.put(vertex, propagatedRange);
} else {
existingTimeRange.mergeIn(propagatedRange);
}
}
}
LOG.info("Done with propagation.");
TimeSurface.RangeSet result = TimeSurface.makeSurfaces(this);
LOG.info("Done making time surfaces.");
return result;
}
Aggregations