Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package org.opentripplanner.profile;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.opentripplanner.analyst.cluster.TaskStatistics;
import org.opentripplanner.analyst.scenario.AddTripPattern;
import org.opentripplanner.analyst.scenario.Scenario;
import org.opentripplanner.analyst.scenario.TransferRule;
import org.opentripplanner.analyst.scenario.TripFilter;
import org.opentripplanner.routing.edgetype.TripPattern;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.trippattern.FrequencyEntry;
import org.opentripplanner.routing.trippattern.TripTimes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* A RaptorWorkerTimetable is used by a RaptorWorker to perform large numbers of RAPTOR searches very quickly
* within a specific time window. It is used heavily in one-to-many profile routing, where we're interested in seeing
* how access to opportunities varies over time.
*
* This is an alternative representation of all the TripTimes in a single TripPattern that are running during a
* particular time range on a particular day. It packs the times a bit closer together and pre-filters the TripTimes based on
* whether they are running at all during the time window eliminates run-time checks that need to be performed when we
* search for the soonest departure. Note: we're not sure this actually provides a major speedup compared to operating
* directly on TripPatterns and TripTimes.
*
* Unlike in "normal" OTP searches, we assume non-overtaking (FIFO) vehicle behavior within a single TripPattern,
* which is generally the case in clean input data. One key difference here is that profile routing and spatial analysis
* do not need to take real-time (GTFS-RT) updates into account since they are intended to be generic results
* describing a scenario in the future.
*/
public class RaptorWorkerTimetable implements Serializable {
private static final Logger LOG = LoggerFactory.getLogger(RaptorWorkerTimetable.class);
/* Times for schedule-based trips/patterns are stored in a 2D array. */
int nTrips, nStops;
/* For each trip on this pattern, an packed array of (arrival, departure) time pairs. */
public int[][] timesPerTrip;
/* Times for frequency-based trips are stored in parallel arrays (a column store). */
/** Times (0-based) for frequency trips */
int[][] frequencyTrips;
/** Headways (seconds) for frequency trips, parallel to above. Note that frequency trips are unsorted. */
int[] headwaySecs;
/** Start times (seconds since noon - 12h) for frequency trips */
int[] startTimes;
/** End times for frequency trips */
int[] endTimes;
/** Indices of stops in parent data */
public int[] stopIndices;
/** parent raptorworkerdata of this timetable */
public RaptorWorkerData raptorData;
/** Mode of this pattern, see constants in com.conveyal.gtfs.model.Route */
public int mode;
/** Index of this pattern in RaptorData */
public int dataIndex;
/** for debugging, the ID of the route this represents */
public transient String routeId;
/** slack required when boarding a transit vehicle */
public static final int MIN_BOARD_TIME_SECONDS = 60;
public RaptorWorkerTimetable(int nTrips, int nStops) {
this.nTrips = nTrips;
this.nStops = nStops;
timesPerTrip = new int[nTrips][];
}
/**
* Return the trip index within the pattern of the soonest departure at the given stop number, requiring at least
* MIN_BOARD_TIME_SECONDS seconds of slack.
*/
public int findDepartureAfter(int stop, int time) {
for (int trip = 0; trip < timesPerTrip.length; trip++) {
if (getDeparture(trip, stop) > time + MIN_BOARD_TIME_SECONDS) {
return trip;
}
}
return -1;
}
public int getArrival (int trip, int stop) {
return timesPerTrip[trip][stop * 2];
}
public int getDeparture (int trip, int stop) {
return timesPerTrip[trip][stop * 2 + 1];
}
public int getFrequencyDeparture (int trip, int stop, int time, int previousPattern, FrequencyRandomOffsets offsets) {
return getFrequencyDeparture(trip, stop, time, previousPattern, offsets, null);
}
/**
* Get the departure on frequency trip trip at stop stop after time time,
* with the given boarding assumption. (Note that the boarding assumption specified may be overridden
* by transfer rules).
*/
public int getFrequencyDeparture (int trip, int stop, int time, int previousPattern, FrequencyRandomOffsets offsets, BoardingAssumption assumption) {
int timeToReachStop = frequencyTrips[trip][stop * 2 + 1];
// figure out if there is an applicable transfer rule
TransferRule transferRule = null;
if (previousPattern != -1) {
// this is a transfer
// stop index in Raptor data
int stopIndex = stopIndices[stop];
// first check for specific rules
// calling containsKey can be expensive so first check if list is empty
if (!raptorData.transferRules.isEmpty() && raptorData.transferRules.containsKey(stopIndex)) {
for (TransferRule tr : raptorData.transferRules.get(stopIndex)) {
if (tr.matches(raptorData.timetablesForPattern.get(previousPattern), this)) {
transferRule = tr;
break;
}
}
}
if (transferRule == null && !raptorData.baseTransferRules.isEmpty()) {
// look for global rules
// using declarative for loop because constructing a stream and doing a filter is
// slow.
for (TransferRule tr : raptorData.baseTransferRules) {
if (tr.matches(raptorData.timetablesForPattern.get(previousPattern), this)) {
transferRule = tr;
break;
}
}
}
}
if (assumption == null)
assumption = raptorData.boardingAssumption;
// if there is an applicable transfer rule, override anything that was specified elsewhere
if (transferRule != null)
assumption = transferRule.assumption;
if (assumption == BoardingAssumption.RANDOM) {
// We treat every frequency-based trip as a scheduled trip on each iteration of the Monte Carlo
// algorithm. The reason for this is thus: consider something like the Portland Transit Mall.
// There are many opportunities to transfer between vehicles. The transfer times between
// Line A and Line B are not independently random at each stop but rather are correlated
// along each line. Thus we randomize each pattern independently, not each boarding.
// Keep in mind that there could also be correlation between patterns, especially for rail
// vehicles that share trackage. Consider, for example, the Fredericksburg and Manassus
// lines on the Virginia Railway Express, at Alexandria. These are two diesel heavy-rail
// lines that share trackage. Thus the minimum transfer time between them is always at least
// 5 minutes or so as that's as close as you can run diesel trains to each other.
int minTime = startTimes[trip] + timeToReachStop + offsets.offsets.get(this.dataIndex)[trip];
// move time forward to an integer multiple of headway after the minTime
if (time < minTime)
time = minTime;
else
// add enough to time to make time - minTime an integer multiple of headway
// if the bus comes every 30 minutes and you arrive at the stop 35 minutes
// after it first came, you have to wait 25 minutes, or headway - time already elapsed
// time already elapsed is 35 % 30 = 5 minutes in this case.
time += headwaySecs[trip] - (time - minTime) % headwaySecs[trip];
}
else {
// move time forward if the frequency has not yet started.
// we do this before we add the wait time, because this is the earliest possible
// time a vehicle could reach the stop. The assumption is that the vehicle leaves
// the terminal between zero and headway_seconds seconds after the start_time of the
// frequency
if (timeToReachStop + startTimes[trip] > time)
time = timeToReachStop + startTimes[trip];
switch (assumption) {
case BEST_CASE:
// do nothing
break;
case WORST_CASE:
time += headwaySecs[trip];
break;
case HALF_HEADWAY:
time += headwaySecs[trip] / 2;
break;
case FIXED:
if (transferRule == null) throw new IllegalArgumentException("Cannot use boarding assumption FIXED without a transfer rule");
time += transferRule.transferTimeSeconds;
break;
case PROPORTION:
if (transferRule == null) throw new IllegalArgumentException("Cannot use boarding assumption PROPORTION without a transfer rule");
time += (int) (transferRule.waitProportion * headwaySecs[trip]);
break;
}
}
if (time > timeToReachStop + endTimes[trip])
return -1;
else
return time;
}
/**
* Get the travel time (departure to arrival) on frequency trip trip, from stop from to stop to.
*/
public int getFrequencyTravelTime (int trip, int from, int to) {
return frequencyTrips[trip][to * 2] - frequencyTrips[trip][from * 2 + 1];
}
/**
* Get the number of frequency trips on this pattern (i.e. the number of trips in trips.txt, not the number of trips by vehicles)
*/
public int getFrequencyTripCount () {
return headwaySecs.length;
}
/** Does this timetable have any frequency trips? */
public boolean hasFrequencyTrips () {
return this.headwaySecs != null && this.headwaySecs.length > 0;
}
/** does this timetable have any scheduled trips? */
public boolean hasScheduledTrips () {
return this.timesPerTrip != null && this.timesPerTrip.length > 0;
}
/**
* This is a factory function rather than a constructor to avoid calling the super constructor for rejected patterns.
* BannedRoutes is formatted as agencyid_routeid.
*/
public static RaptorWorkerTimetable forPattern (Graph graph, TripPattern pattern, TimeWindow window, Scenario scenario, TaskStatistics ts) {
// Filter down the trips to only those running during the window
// This filtering can reduce number of trips and run time by 80 percent
BitSet servicesRunning = window.servicesRunning;
List tripTimes = Lists.newArrayList();
TT: for (TripTimes tt : pattern.scheduledTimetable.tripTimes) {
if (servicesRunning.get(tt.serviceCode) &&
tt.getArrivalTime(0) < window.to &&
tt.getDepartureTime(tt.getNumStops() - 1) >= window.from) {
// apply scenario
// TODO: need to do this before filtering based on window!
if (scenario != null && scenario.modifications != null) {
for (TripFilter filter : Iterables.filter(scenario.modifications, TripFilter.class)) {
tt = filter.apply(tt.trip, pattern, tt);
if (tt == null)
continue TT;
}
}
tripTimes.add(tt);
}
}
// find frequency trips
List freqs = Lists.newArrayList();
FREQUENCIES: for (FrequencyEntry fe : pattern.scheduledTimetable.frequencyEntries) {
if (servicesRunning.get(fe.tripTimes.serviceCode) &&
fe.getMinDeparture() < window.to &&
fe.getMaxArrival() > window.from
) {
// this frequency entry has the potential to be used
if (fe.exactTimes) {
LOG.warn("Exact-times frequency trips not yet supported");
continue;
}
if (scenario != null && scenario.modifications != null) {
for (TripFilter filter : Iterables.filter(scenario.modifications, TripFilter.class)) {
fe = filter.apply(fe.tripTimes.trip, pattern, fe);
if (fe == null)
continue FREQUENCIES;
}
}
freqs.add(fe);
}
}
if (tripTimes.isEmpty() && freqs.isEmpty()) {
return null; // no trips active, don't bother storing a timetable
}
// Sort the trip times by their first arrival time
Collections.sort(tripTimes, new Comparator() {
@Override
public int compare(TripTimes tt1, TripTimes tt2) {
return (tt1.getArrivalTime(0) - tt2.getArrivalTime(0));
}
});
// Copy the times into the compacted table
RaptorWorkerTimetable rwtt = new RaptorWorkerTimetable(tripTimes.size(), pattern.getStops().size());
int t = 0;
for (TripTimes tt : tripTimes) {
int[] times = new int[rwtt.nStops * 2];
for (int s = 0; s < pattern.getStops().size(); s++) {
int arrival = tt.getArrivalTime(s);
int departure = tt.getDepartureTime(s);
times[s * 2] = arrival;
times[s * 2 + 1] = departure;
}
rwtt.timesPerTrip[t++] = times;
}
ts.scheduledTripCount += rwtt.timesPerTrip.length;
// save frequency times
rwtt.frequencyTrips = new int[freqs.size()][pattern.getStops().size() * 2];
rwtt.endTimes = new int[freqs.size()];
rwtt.startTimes = new int[freqs.size()];
rwtt.headwaySecs = new int[freqs.size()];
{
int i = 0;
for (FrequencyEntry fe : freqs) {
rwtt.headwaySecs[i] = fe.headway;
rwtt.startTimes[i] = fe.startTime;
rwtt.endTimes[i] = fe.endTime;
ts.frequencyTripCount += fe.numTrips();
int[] times = rwtt.frequencyTrips[i];
// It's generally considered good practice to have frequency trips start at midnight, however that is
// not always the case, and we need to preserve the original times so that we can update them in
// real time.
int startTime = fe.tripTimes.getArrivalTime(0);
for (int s = 0; s < fe.tripTimes.getNumStops(); s++) {
times[s * 2] = fe.tripTimes.getArrivalTime(s) - startTime;
times[s * 2 + 1] = fe.tripTimes.getDepartureTime(s) - startTime;
}
i++;
}
}
ts.frequencyEntryCount += rwtt.getFrequencyTripCount();
rwtt.mode = pattern.route.getType();
return rwtt;
}
/** Create a raptor worker timetable for an added pattern */
public static RaptorWorkerTimetable forAddedPattern(AddTripPattern atp, TimeWindow window, TaskStatistics ts) {
if (atp.temporaryStops.length < 2 || atp.timetables.isEmpty())
return null;
// filter down the timetable to only those running
Collection running = atp.timetables.stream()
// getValue yields one-based ISO days of week (monday = 1); convert to 0-based format
.filter(t -> t.days.get(window.dayOfWeek.getValue() - 1))
.collect(Collectors.toList());
if (running.isEmpty())
return null;
// TODO: filter with timewindow
Collection frequencies = running.stream()
.filter(t -> t.frequency)
.collect(Collectors.toList());
Collection timetables = running.stream()
.filter(t -> !t.frequency)
.sorted((t1, t2) -> t1.startTime - t2.startTime)
.collect(Collectors.toList());
RaptorWorkerTimetable rwtt = new RaptorWorkerTimetable(timetables.size(), atp.temporaryStops.length);
// create timetabled trips
int t = 0;
for (AddTripPattern.PatternTimetable pt : timetables) {
rwtt.timesPerTrip[t++] = timesForPatternTimetable(atp, pt);
}
ts.scheduledTripCount += rwtt.timesPerTrip.length;
// create frequency trips
rwtt.frequencyTrips = new int[frequencies.size()][atp.temporaryStops.length * 2];
rwtt.endTimes = new int[frequencies.size()];
rwtt.startTimes = new int[frequencies.size()];
rwtt.headwaySecs = new int[frequencies.size()];
t = 0;
for (AddTripPattern.PatternTimetable pt : frequencies) {
rwtt.frequencyTrips[t] = timesForPatternTimetable(atp, pt);
rwtt.startTimes[t] = pt.startTime;
rwtt.endTimes[t] = pt.endTime;
rwtt.headwaySecs[t++] = pt.headwaySecs;
ts.frequencyTripCount += (pt.endTime - pt.startTime) / pt.headwaySecs;
}
ts.frequencyEntryCount += frequencies.size();
rwtt.mode = atp.mode;
rwtt.routeId = atp.name;
return rwtt;
}
private static int[] timesForPatternTimetable (AddTripPattern atp, AddTripPattern.PatternTimetable pt) {
int[] times = new int[atp.temporaryStops.length * 2];
for (int s = 0; s < atp.temporaryStops.length; s++) {
// for a timetable route,
// arrival time is start time if it's the first stop, or the previous departure time plus the hop time
// For a frequency route the first departure is always 0
if (s == 0)
times[s * 2] = pt.frequency ? 0 : pt.startTime;
else
times[s * 2] = times[s * 2 - 1] + pt.hopTimes[s - 1];
times[s * 2 + 1] = times[s * 2] + pt.dwellTimes[s];
}
return times;
}
/** The assumptions made when boarding a frequency vehicle: best case (no wait), worst case (full headway) and half headway (in some sense the average). */
public static enum BoardingAssumption {
BEST_CASE, WORST_CASE, HALF_HEADWAY, FIXED, PROPORTION, RANDOM;
public static final long serialVersionUID = 1;
}
}