use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class GTFSPatternHopFactory method findOrCreateTripPattern.
private TripPattern findOrCreateTripPattern(StopPattern stopPattern, Route route, int directionId) {
for (TripPattern tripPattern : tripPatterns.get(stopPattern)) {
if (tripPattern.route.equals(route) && tripPattern.directionId == directionId) {
return tripPattern;
}
}
TripPattern tripPattern = new TripPattern(route, stopPattern);
tripPattern.directionId = directionId;
tripPatterns.put(stopPattern, tripPattern);
return tripPattern;
}
use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class GTFSPatternHopFactory method run.
/**
* Generate the edges. Assumes that there are already vertices in the graph for the stops.
*/
public void run(Graph graph) {
if (fareServiceFactory == null) {
fareServiceFactory = new DefaultFareServiceFactory();
}
fareServiceFactory.processGtfs(_dao);
// TODO: Why are we loading stops? The Javadoc above says this method assumes stops are aleady loaded.
loadStops(graph);
loadPathways(graph);
loadFeedInfo(graph);
loadAgencies(graph);
// TODO: Why is there cached "data", and why are we clearing it? Due to a general lack of comments, I have no idea.
// Perhaps it is to allow name collisions with previously loaded feeds.
clearCachedData();
/* Assign 0-based numeric codes to all GTFS service IDs. */
for (AgencyAndId serviceId : _dao.getAllServiceIds()) {
// TODO: FIX Service code collision for multiple feeds.
graph.serviceCodes.put(serviceId, graph.serviceCodes.size());
}
LOG.debug("building hops from trips");
Collection<Trip> trips = _dao.getAllTrips();
int tripCount = 0;
/* First, record which trips are used by one or more frequency entries.
* These trips will be ignored for the purposes of non-frequency routing, and
* all the frequency entries referencing the same trip can be added at once to the same
* Timetable/TripPattern.
*/
ListMultimap<Trip, Frequency> frequenciesForTrip = ArrayListMultimap.create();
for (Frequency freq : _dao.getAllFrequencies()) {
frequenciesForTrip.put(freq.getTrip(), freq);
}
/* Then loop over all trips, handling each one as a frequency-based or scheduled trip. */
int freqCount = 0;
int nonFreqCount = 0;
/* The hops don't actually exist when we build their geometries, but we have to build their geometries
* below, before we throw away the modified stopTimes, saving only the tripTimes (which don't have enough
* information to build a geometry). So we keep them here.
*
* A trip pattern actually does not have a single geometry, but one per hop, so we store an array.
* FIXME _why_ doesn't it have a single geometry?
*/
Map<TripPattern, LineString[]> geometriesByTripPattern = Maps.newHashMap();
TRIP: for (Trip trip : trips) {
if (++tripCount % 100000 == 0) {
LOG.debug("loading trips {}/{}", tripCount, trips.size());
}
// TODO: move to a validator module
if (!_calendarService.getServiceIds().contains(trip.getServiceId())) {
LOG.warn(graph.addBuilderAnnotation(new TripUndefinedService(trip)));
// Invalid trip, skip it, it will break later
continue TRIP;
}
/* Fetch the stop times for this trip. Copy the list since it's immutable. */
List<StopTime> stopTimes = new ArrayList<StopTime>(_dao.getStopTimesForTrip(trip));
/* GTFS stop times frequently contain duplicate, missing, or incorrect entries. Repair them. */
TIntList removedStopSequences = removeRepeatedStops(stopTimes);
if (!removedStopSequences.isEmpty()) {
LOG.warn(graph.addBuilderAnnotation(new RepeatedStops(trip, removedStopSequences)));
}
filterStopTimes(stopTimes, graph);
interpolateStopTimes(stopTimes);
/* If after filtering this trip does not contain at least 2 stoptimes, it does not serve any purpose. */
if (stopTimes.size() < 2) {
LOG.warn(graph.addBuilderAnnotation(new TripDegenerate(trip)));
continue TRIP;
}
/* Try to get the direction id for the trip, set to -1 if not found */
int directionId;
try {
directionId = Integer.parseInt(trip.getDirectionId());
} catch (NumberFormatException e) {
LOG.debug("Trip {} does not have direction id, defaults to -1");
directionId = -1;
}
/* Get the existing TripPattern for this filtered StopPattern, or create one. */
StopPattern stopPattern = new StopPattern(stopTimes);
TripPattern tripPattern = findOrCreateTripPattern(stopPattern, trip.getRoute(), directionId);
/* Create a TripTimes object for this list of stoptimes, which form one trip. */
TripTimes tripTimes = new TripTimes(trip, stopTimes, graph.deduplicator);
/* If this trip is referenced by one or more lines in frequencies.txt, wrap it in a FrequencyEntry. */
List<Frequency> frequencies = frequenciesForTrip.get(trip);
if (frequencies != null && !(frequencies.isEmpty())) {
for (Frequency freq : frequencies) {
tripPattern.add(new FrequencyEntry(freq, tripTimes));
freqCount++;
}
// TODO replace: createGeometry(graph, trip, stopTimes, hops);
} else /* This trip was not frequency-based. Add the TripTimes directly to the TripPattern's scheduled timetable. */
{
tripPattern.add(tripTimes);
nonFreqCount++;
}
// there would be a trip pattern with no geometry yet because it failed some of these tests
if (!geometriesByTripPattern.containsKey(tripPattern) && trip.getShapeId() != null && trip.getShapeId().getId() != null && !trip.getShapeId().getId().equals("")) {
// save the geometry to later be applied to the hops
geometriesByTripPattern.put(tripPattern, createGeometry(graph, trip, stopTimes));
}
}
// end foreach TRIP
LOG.info("Added {} frequency-based and {} single-trip timetable entries.", freqCount, nonFreqCount);
graph.hasFrequencyService = graph.hasFrequencyService || freqCount > 0;
graph.hasScheduledService = graph.hasScheduledService || nonFreqCount > 0;
/* Generate unique human-readable names for all the TableTripPatterns. */
TripPattern.generateUniqueNames(tripPatterns.values());
/* Generate unique short IDs for all the TableTripPatterns. */
TripPattern.generateUniqueIds(tripPatterns.values());
/* Loop over all new TripPatterns, creating edges, setting the service codes and geometries, etc. */
for (TripPattern tripPattern : tripPatterns.values()) {
tripPattern.makePatternVerticesAndEdges(graph, context.stationStopNodes);
// Add the geometries to the hop edges.
LineString[] geom = geometriesByTripPattern.get(tripPattern);
if (geom != null) {
for (int i = 0; i < tripPattern.hopEdges.length; i++) {
tripPattern.hopEdges[i].setGeometry(geom[i]);
}
// Make a geometry for the whole TripPattern from all its constituent hops.
// This happens only if geometry is found in geometriesByTripPattern,
// because that means that geometry was created from shapes instead "as crow flies"
tripPattern.makeGeometry();
}
// TODO this could be more elegant
tripPattern.setServiceCodes(graph.serviceCodes);
/* Iterate over all stops in this pattern recording mode information. */
TraverseMode mode = GtfsLibrary.getTraverseMode(tripPattern.route);
for (TransitStop tstop : tripPattern.stopVertices) {
tstop.addMode(mode);
if (mode == TraverseMode.SUBWAY) {
tstop.setStreetToStopTime(subwayAccessTime);
}
graph.addTransitMode(mode);
}
}
/* Identify interlined trips and create the necessary edges. */
interline(tripPatterns.values(), graph);
/* Interpret the transfers explicitly defined in transfers.txt. */
loadTransfers(graph);
/* Store parent stops in graph, even if not linked. These are needed for clustering*/
for (TransitStationStop stop : context.stationStopNodes.values()) {
if (stop instanceof TransitStation) {
TransitStation parentStopVertex = (TransitStation) stop;
graph.parentStopById.put(parentStopVertex.getStopId(), parentStopVertex.getStop());
}
}
// it is already done at deserialization, but standalone mode allows using graphs without serializing them.
for (TripPattern tableTripPattern : tripPatterns.values()) {
tableTripPattern.scheduledTimetable.finish();
}
// eh?
clearCachedData();
graph.putService(FareService.class, fareServiceFactory.makeFareService());
graph.putService(OnBoardDepartService.class, new OnBoardDepartServiceImpl());
}
use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class RoundBasedProfileRouter method route.
public void route() {
LOG.info("access modes: {}", request.accessModes);
LOG.info("egress modes: {}", request.egressModes);
LOG.info("direct modes: {}", request.directModes);
// 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));
// Establish search timeouts
long searchBeginTime = System.currentTimeMillis();
long abortTime = searchBeginTime + TIMEOUT * 1000;
LOG.info("Finding access/egress paths.");
// Look for stops that are within a given time threshold of the origin and destination
// Find the closest stop on each pattern near the origin and destination
// TODO consider that some stops may be closer by one mode than another
// and that some stops may be accessible by one mode but not another
ProfileStateStore store = RETAIN_PATTERNS ? new MultiProfileStateStore() : new SingleProfileStateStore();
for (ProfileState ps : findInitialStops(false)) {
store.put(ps);
}
LOG.info("Found {} initial stops", store.size());
// we don't want to generate trips that are artificially forced to go past a transit stop.
ROUNDS: for (int round = 0; round < MAX_ROUNDS; round++) {
long roundStart = System.currentTimeMillis();
LOG.info("Begin round {}; {} stops to explore", round, store.size());
ProfileStateStore previousStore = store;
store = RETAIN_PATTERNS ? new MultiProfileStateStore((MultiProfileStateStore) store) : new SingleProfileStateStore((SingleProfileStateStore) store);
Set<TripPattern> patternsToExplore = Sets.newHashSet();
// explore all of the patterns at the stops visited on the previous round
for (TransitStop tstop : previousStore.keys()) {
Collection<TripPattern> patterns = graph.index.patternsForStop.get(tstop.getStop());
patternsToExplore.addAll(patterns);
}
LOG.info("Exploring {} patterns", patternsToExplore.size());
// propagate all of the bounds down each pattern
PATTERNS: for (final TripPattern pattern : patternsToExplore) {
STOPS: for (int i = 0; i < pattern.stopVertices.length; i++) {
if (!previousStore.containsKey(pattern.stopVertices[i]))
continue STOPS;
Collection<ProfileState> statesToPropagate;
// only propagate nondominated states
statesToPropagate = previousStore.get(pattern.stopVertices[i]);
// don't propagate states that use the same pattern
statesToPropagate = Collections2.filter(statesToPropagate, new Predicate<ProfileState>() {
@Override
public boolean apply(ProfileState input) {
// don't reboard same pattern, and don't board patterns that are better boarded elsewhere
return !input.containsPattern(pattern) && (input.targetPatterns == null || input.targetPatterns.contains(pattern));
}
});
if (statesToPropagate.isEmpty())
continue STOPS;
int minWaitTime = Integer.MAX_VALUE;
int maxWaitTime = Integer.MIN_VALUE;
// (i.e. the transfer time is different for the initial boarding than transfers)
for (FrequencyEntry freq : pattern.scheduledTimetable.frequencyEntries) {
if (freq.exactTimes) {
throw new IllegalStateException("Exact times not yet supported in profile routing.");
}
int overlap = window.overlap(freq.startTime, freq.endTime, freq.tripTimes.serviceCode);
if (overlap > 0) {
if (freq.headway > maxWaitTime)
maxWaitTime = freq.headway;
// if any frequency-based trips are running a wait of 0 is always possible, because it could come
// just as you show up at the stop.
minWaitTime = 0;
}
}
DESTSTOPS: for (int j = i + 1; j < pattern.stopVertices.length; j++) {
// how long does it take to ride this trip from i to j?
int minRideTime = Integer.MAX_VALUE;
int maxRideTime = Integer.MIN_VALUE;
// how long does it take to get to stop j from stop i?
for (TripTimes tripTimes : pattern.scheduledTimetable.tripTimes) {
int depart = tripTimes.getDepartureTime(i);
int arrive = tripTimes.getArrivalTime(j);
if (window.includes(depart) && window.includes(arrive) && window.servicesRunning.get(tripTimes.serviceCode)) {
int t = arrive - depart;
if (t < minRideTime)
minRideTime = t;
if (t > maxRideTime)
maxRideTime = t;
}
}
/* Do the same thing for any frequency-based trips. */
for (FrequencyEntry freq : pattern.scheduledTimetable.frequencyEntries) {
TripTimes tt = freq.tripTimes;
int overlap = window.overlap(freq.startTime, freq.endTime, tt.serviceCode);
if (overlap == 0)
continue;
int depart = tt.getDepartureTime(i);
int arrive = tt.getArrivalTime(j);
int t = arrive - depart;
if (t < minRideTime)
minRideTime = t;
if (t > maxRideTime)
maxRideTime = t;
}
if (minWaitTime == Integer.MAX_VALUE || maxWaitTime == Integer.MIN_VALUE || minRideTime == Integer.MAX_VALUE || maxRideTime == Integer.MIN_VALUE)
// no trips in window that arrive at stop
continue DESTSTOPS;
if (minRideTime < 0 || maxRideTime < 0) {
LOG.error("Pattern {} travels backwards in time between stop {} and {}", pattern, pattern.stopVertices[i].getStop(), pattern.stopVertices[j].getStop());
continue DESTSTOPS;
}
// we've already checked to ensure we're not reboarding the same pattern
for (ProfileState ps : statesToPropagate) {
ProfileState ps2 = ps.propagate(minWaitTime + minRideTime, maxWaitTime + maxRideTime);
if (ps2.upperBound > CUTOFF_SECONDS)
continue;
ps2.stop = pattern.stopVertices[j];
ps2.accessType = Type.TRANSIT;
if (RETAIN_PATTERNS)
ps2.patterns = new TripPattern[] { pattern };
store.put(ps2);
}
}
}
}
// merge states that came from the same stop.
if (RETAIN_PATTERNS) {
LOG.info("Round completed, merging similar states");
((MultiProfileStateStore) store).mergeStates();
}
for (ProfileState ps : store.getAll()) {
retainedStates.put(ps.stop, ps);
}
if (round == MAX_ROUNDS - 1) {
LOG.info("Finished round {} in {} seconds", round, (System.currentTimeMillis() - roundStart) / 1000);
break ROUNDS;
}
// propagate states to nearby stops (transfers)
LOG.info("Finding transfers . . .");
// avoid concurrent modification
Set<TransitStop> touchedStopKeys = new HashSet<TransitStop>(store.keys());
for (TransitStop tstop : touchedStopKeys) {
List<Tuple2<TransitStop, Integer>> accessTimes = Lists.newArrayList();
// find transfers for the stop
for (Edge e : tstop.getOutgoing()) {
if (e instanceof SimpleTransfer) {
SimpleTransfer t = (SimpleTransfer) e;
int time = (int) (t.getDistance() / request.walkSpeed);
accessTimes.add(new Tuple2((TransitStop) e.getToVertex(), time));
}
}
// only transfer from nondominated states. only transfer to each pattern once
Collection<ProfileState> statesAtStop = store.get(tstop);
TObjectIntHashMap<TripPattern> minBoardTime = new TObjectIntHashMap<TripPattern>(1000, .75f, Integer.MAX_VALUE);
Map<TripPattern, ProfileState> optimalBoardState = Maps.newHashMap();
List<ProfileState> xferStates = Lists.newArrayList();
// make a hashset of the patterns that stop here, because we don't want to transfer to them at another stop
HashSet<TripPattern> patternsAtSource = new HashSet<TripPattern>(graph.index.patternsForStop.get(tstop.getStop()));
for (ProfileState ps : statesAtStop) {
for (Tuple2<TransitStop, Integer> atime : accessTimes) {
ProfileState ps2 = ps.propagate(atime.b);
ps2.accessType = Type.TRANSFER;
ps2.stop = atime.a;
for (TripPattern patt : graph.index.patternsForStop.get(atime.a.getStop())) {
// don't transfer to patterns that we can board at this stop.
if (patternsAtSource.contains(patt))
continue;
if (atime.b < minBoardTime.get(patt)) {
minBoardTime.put(patt, atime.b);
optimalBoardState.put(patt, ps2);
}
}
xferStates.add(ps2);
}
}
for (Entry<TripPattern, ProfileState> e : optimalBoardState.entrySet()) {
ProfileState ps = e.getValue();
if (ps.targetPatterns == null)
ps.targetPatterns = Sets.newHashSet();
ps.targetPatterns.add(e.getKey());
}
for (ProfileState ps : xferStates) {
if (ps.targetPatterns != null && !ps.targetPatterns.isEmpty()) {
store.put(ps);
}
}
}
LOG.info("Finished round {} in {} seconds", round, (System.currentTimeMillis() - roundStart) / 1000);
}
LOG.info("Finished profile routing in {} seconds", (System.currentTimeMillis() - searchBeginTime) / 1000);
makeSurfaces();
LOG.info("Finished analyst request in {} seconds total", (System.currentTimeMillis() - searchBeginTime) / 1000);
}
use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class RoundBasedProfileRouter method findInitialStops.
/**
* find the boarding stops
*/
private Collection<ProfileState> findInitialStops(boolean dest) {
double lat = dest ? request.toLat : request.fromLat;
double lon = dest ? request.toLon : request.fromLon;
QualifiedModeSet modes = dest ? request.accessModes : request.egressModes;
List<ProfileState> stops = Lists.newArrayList();
RoutingRequest rr = new RoutingRequest(TraverseMode.WALK);
rr.dominanceFunction = new DominanceFunction.EarliestArrival();
rr.batch = true;
rr.from = new GenericLocation(lat, lon);
rr.walkSpeed = request.walkSpeed;
rr.to = rr.from;
rr.setRoutingContext(graph);
// RoutingRequest dateTime defaults to currentTime.
// If elapsed time is not capped, searches are very slow.
rr.worstTime = (rr.dateTime + request.maxWalkTime * 60);
AStar astar = new AStar();
rr.longDistance = true;
rr.setNumItineraries(1);
// timeout in seconds
ShortestPathTree spt = astar.getShortestPathTree(rr, 5);
for (TransitStop tstop : graph.index.stopVertexForStop.values()) {
State s = spt.getState(tstop);
if (s != null) {
ProfileState ps = new ProfileState();
ps.lowerBound = ps.upperBound = (int) s.getElapsedTimeSeconds();
ps.stop = tstop;
ps.accessType = Type.STREET;
stops.add(ps);
}
}
Map<TripPattern, ProfileState> optimalBoardingLocation = Maps.newHashMap();
TObjectIntMap<TripPattern> minBoardTime = new TObjectIntHashMap<TripPattern>(100, 0.75f, Integer.MAX_VALUE);
// Only board patterns at the closest possible stop
for (ProfileState ps : stops) {
for (TripPattern pattern : graph.index.patternsForStop.get(ps.stop.getStop())) {
if (ps.lowerBound < minBoardTime.get(pattern)) {
optimalBoardingLocation.put(pattern, ps);
minBoardTime.put(pattern, ps.lowerBound);
}
}
ps.targetPatterns = Sets.newHashSet();
}
LOG.info("Found {} reachable stops, filtering to only board at closest stops", stops.size());
for (Entry<TripPattern, ProfileState> e : optimalBoardingLocation.entrySet()) {
e.getValue().targetPatterns.add(e.getKey());
}
for (Iterator<ProfileState> it = stops.iterator(); it.hasNext(); ) {
if (it.next().targetPatterns.isEmpty())
it.remove();
}
rr.cleanup();
return stops;
}
use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class GraphIndex method getStopTimesForStop.
/**
* Get a list of all trips that pass through a stop during a single ServiceDate. Useful when creating complete stop
* timetables for a single day.
*
* @param stop Stop object to perform the search for
* @param serviceDate Return all departures for the specified date
* @return
*/
public List<StopTimesInPattern> getStopTimesForStop(Stop stop, ServiceDate serviceDate, boolean omitNonPickups) {
List<StopTimesInPattern> ret = new ArrayList<>();
TimetableSnapshot snapshot = null;
if (graph.timetableSnapshotSource != null) {
snapshot = graph.timetableSnapshotSource.getTimetableSnapshot();
}
Collection<TripPattern> patterns = patternsForStop.get(stop);
for (TripPattern pattern : patterns) {
StopTimesInPattern stopTimes = new StopTimesInPattern(pattern);
Timetable tt;
if (snapshot != null) {
tt = snapshot.resolve(pattern, serviceDate);
} else {
tt = pattern.scheduledTimetable;
}
ServiceDay sd = new ServiceDay(graph, serviceDate, calendarService, pattern.route.getAgency().getId());
int sidx = 0;
for (Stop currStop : pattern.stopPattern.stops) {
if (currStop == stop) {
if (omitNonPickups && pattern.stopPattern.pickups[sidx] == pattern.stopPattern.PICKDROP_NONE)
continue;
for (TripTimes t : tt.tripTimes) {
if (!sd.serviceRunning(t.serviceCode))
continue;
stopTimes.times.add(new TripTimeShort(t, sidx, stop, sd));
}
}
sidx++;
}
ret.add(stopTimes);
}
return ret;
}
Aggregations