use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class ProfileRouter method findClosestPatterns.
/**
* @param stopClusters a multimap from stop clusters to one or more StopAtDistance objects at the corresponding cluster.
* @return for each TripPattern that passes through any of the supplied stop clusters, the stop cluster that is
* closest to the origin or destination point according to the distances in the StopAtDistance objects.
*
* In short, take a bunch of stop clusters near the origin or destination and return the quickest way to reach each
* pattern that passes through them.
* We want stop cluster references rather than indexes within the patterns because when a stop cluster appears more
* than once in a pattern, we want to consider boarding or alighting from that pattern at every index where the
* cluster occurs.
*/
public Multimap<TripPattern, StopAtDistance> findClosestPatterns(Multimap<StopCluster, StopAtDistance> stopClusters) {
SimpleIsochrone.MinMap<T2<TripPattern, QualifiedMode>, StopAtDistance> closest = new SimpleIsochrone.MinMap<>();
// Iterate over all StopAtDistance for all Stops. The fastest mode will win at each stop.
for (StopAtDistance stopDist : stopClusters.values()) {
for (Stop stop : stopDist.stopCluster.children) {
for (TripPattern pattern : graph.index.patternsForStop.get(stop)) {
closest.putMin(new T2(pattern, stopDist.qmode), stopDist);
}
}
}
/* Remove the QualifiedModes from the keys. Maybe it would be better to just return the result including them. */
Multimap<TripPattern, StopAtDistance> result = ArrayListMultimap.create();
for (Entry<T2<TripPattern, QualifiedMode>, StopAtDistance> entry : closest.entrySet()) {
result.put(entry.getKey().first, entry.getValue());
}
// We used to truncate long lists to include a mix of nearby bus and train patterns
// but this gives crazy results. Now we just warn on this condition.
final int MAX_PATTERNS = 1000;
if (result.size() > MAX_PATTERNS) {
LOG.warn("Excessively long list of patterns. {} patterns, max allowed is {}.", closest.size(), MAX_PATTERNS);
}
return result;
}
use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class GTFSPatternHopFactory method interline.
/**
* Identify interlined trips (where a physical vehicle continues on to another logical trip)
* and update the TripPatterns accordingly. This must be called after all the pattern edges and vertices
* are already created, because it creates interline dwell edges between existing pattern arrive/depart vertices.
*/
private void interline(Collection<TripPattern> tripPatterns, Graph graph) {
/* Record which Pattern each interlined TripTimes belongs to. */
Map<TripTimes, TripPattern> patternForTripTimes = Maps.newHashMap();
/* TripTimes grouped by the block ID and service ID of their trips. Must be a ListMultimap to allow sorting. */
ListMultimap<BlockIdAndServiceId, TripTimes> tripTimesForBlock = ArrayListMultimap.create();
LOG.info("Finding interlining trips based on block IDs.");
for (TripPattern pattern : tripPatterns) {
Timetable timetable = pattern.scheduledTimetable;
/* TODO: Block semantics seem undefined for frequency trips, so skip them? */
for (TripTimes tripTimes : timetable.tripTimes) {
Trip trip = tripTimes.trip;
if (!Strings.isNullOrEmpty(trip.getBlockId())) {
tripTimesForBlock.put(new BlockIdAndServiceId(trip), tripTimes);
// For space efficiency, only record times that are part of a block.
patternForTripTimes.put(tripTimes, pattern);
}
}
}
/* Associate pairs of TripPatterns with lists of trips that continue from one pattern to the other. */
Multimap<P2<TripPattern>, P2<Trip>> interlines = ArrayListMultimap.create();
/*
Sort trips within each block by first departure time, then iterate over trips in this block and service,
linking them. Has no effect on single-trip blocks.
*/
SERVICE_BLOCK: for (BlockIdAndServiceId block : tripTimesForBlock.keySet()) {
List<TripTimes> blockTripTimes = tripTimesForBlock.get(block);
Collections.sort(blockTripTimes);
TripTimes prev = null;
for (TripTimes curr : blockTripTimes) {
if (prev != null) {
if (prev.getDepartureTime(prev.getNumStops() - 1) > curr.getArrivalTime(0)) {
LOG.error("Trip times within block {} are not increasing on service {} after trip {}.", block.blockId, block.serviceId, prev.trip.getId());
continue SERVICE_BLOCK;
}
TripPattern prevPattern = patternForTripTimes.get(prev);
TripPattern currPattern = patternForTripTimes.get(curr);
Stop fromStop = prevPattern.getStop(prevPattern.getStops().size() - 1);
Stop toStop = currPattern.getStop(0);
double teleportationDistance = SphericalDistanceLibrary.fastDistance(fromStop.getLat(), fromStop.getLon(), toStop.getLat(), toStop.getLon());
if (teleportationDistance > maxInterlineDistance) {
// FIXME Trimet data contains a lot of these -- in their data, two trips sharing a block ID just
// means that they are served by the same vehicle, not that interlining is automatically allowed.
// see #1654
// LOG.error(graph.addBuilderAnnotation(new InterliningTeleport(prev.trip, block.blockId, (int)teleportationDistance)));
// Only skip this particular interline edge; there may be other valid ones in the block.
} else {
interlines.put(new P2<TripPattern>(prevPattern, currPattern), new P2<Trip>(prev.trip, curr.trip));
}
}
prev = curr;
}
}
/*
Create the PatternInterlineDwell edges linking together TripPatterns.
All the pattern vertices and edges must already have been created.
*/
for (P2<TripPattern> patterns : interlines.keySet()) {
TripPattern prevPattern = patterns.first;
TripPattern nextPattern = patterns.second;
// This is a single (uni-directional) edge which may be traversed forward and backward.
PatternInterlineDwell edge = new PatternInterlineDwell(prevPattern, nextPattern);
for (P2<Trip> trips : interlines.get(patterns)) {
edge.add(trips.first, trips.second);
}
}
LOG.info("Done finding interlining trips and creating the corresponding edges.");
}
use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class GraphIndex method initializeProfileTransfers.
/**
* Initialize transfer data needed for profile routing.
* Find the best transfers between each pair of patterns that pass near one another.
*/
public void initializeProfileTransfers() {
transfersFromStopCluster = HashMultimap.create();
// meters
final double TRANSFER_RADIUS = 500.0;
Map<P2<TripPattern>, ProfileTransfer.GoodTransferList> transfers = Maps.newHashMap();
LOG.info("Finding transfers between clusters...");
for (StopCluster sc0 : stopClusterForId.values()) {
Set<TripPattern> tripPatterns0 = patternsForStopCluster(sc0);
// Accounts for area-like (rather than point-like) nature of clusters
Map<StopCluster, Double> nearbyStopClusters = findNearbyStopClusters(sc0, TRANSFER_RADIUS);
for (StopCluster sc1 : nearbyStopClusters.keySet()) {
double distance = nearbyStopClusters.get(sc1);
Set<TripPattern> tripPatterns1 = patternsForStopCluster(sc1);
for (TripPattern tp0 : tripPatterns0) {
for (TripPattern tp1 : tripPatterns1) {
if (tp0 == tp1)
continue;
P2<TripPattern> pair = new P2<TripPattern>(tp0, tp1);
ProfileTransfer.GoodTransferList list = transfers.get(pair);
if (list == null) {
list = new ProfileTransfer.GoodTransferList();
transfers.put(pair, list);
}
list.add(new ProfileTransfer(tp0, tp1, sc0, sc1, (int) distance));
}
}
}
}
/* Now filter the transfers down to eliminate long series of transfers in shared trunks. */
LOG.info("Filtering out long series of transfers on trunks shared between patterns.");
for (P2<TripPattern> pair : transfers.keySet()) {
ProfileTransfer.GoodTransferList list = transfers.get(pair);
// TODO consider using second (think of express-local transfers in NYC)
TripPattern fromPattern = pair.first;
Map<StopCluster, ProfileTransfer> transfersByFromCluster = Maps.newHashMap();
for (ProfileTransfer transfer : list.good) {
transfersByFromCluster.put(transfer.sc1, transfer);
}
List<ProfileTransfer> retainedTransfers = Lists.newArrayList();
// true whenever a transfer existed for the last stop in the stop pattern
boolean inSeries = false;
for (Stop stop : fromPattern.stopPattern.stops) {
StopCluster cluster = this.stopClusterForStop.get(stop);
// LOG.info("stop {} cluster {}", stop, cluster.id);
ProfileTransfer transfer = transfersByFromCluster.get(cluster);
if (transfer == null) {
inSeries = false;
continue;
}
if (inSeries)
continue;
// Keep this transfer: it's not preceded by another stop with a transfer in this stop pattern
retainedTransfers.add(transfer);
inSeries = true;
}
// LOG.info("patterns {}, {} transfers", pair, retainedTransfers.size());
for (ProfileTransfer tr : retainedTransfers) {
transfersFromStopCluster.put(tr.sc1, tr);
// LOG.info(" {}", tr);
}
}
/*
* for (Stop stop : transfersForStop.keys()) { System.out.println("STOP " + stop); for
* (Transfer transfer : transfersForStop.get(stop)) { System.out.println(" " +
* transfer.toString()); } }
*/
LOG.info("Done finding transfers.");
}
use of org.opentripplanner.routing.edgetype.TripPattern 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;
}
use of org.opentripplanner.routing.edgetype.TripPattern in project OpenTripPlanner by opentripplanner.
the class OnBoardDepartServiceImpl method setupDepartOnBoard.
@Override
public Vertex setupDepartOnBoard(RoutingContext ctx) {
RoutingRequest opt = ctx.opt;
opt.rctx = ctx;
/* 1. Get the list of PatternHop for the given trip ID. */
AgencyAndId tripId = opt.startingTransitTripId;
Trip trip = ctx.graph.index.tripForId.get(tripId);
TripPattern tripPattern = ctx.graph.index.patternForTrip.get(trip);
if (tripPattern == null) {
// TODO Shouldn't we bailout on a normal trip plan here, returning null ?
throw new IllegalArgumentException("Unknown/invalid trip ID: " + tripId);
}
List<PatternHop> hops = tripPattern.getPatternHops();
// Origin point, optional
Double lon = opt.from.lng;
Double lat = opt.from.lat;
PatternStopVertex nextStop;
TripTimes bestTripTimes = null;
ServiceDay bestServiceDay = null;
int bestStopIndex = 0;
double fractionCovered;
LineString geomRemaining;
Coordinate point = lon == null || lat == null ? null : new Coordinate(lon, lat);
if (point != null) {
/*
* 2. Get the best hop from the list, given the parameters. Currently look for nearest hop,
* taking into account shape if available. If no shape are present, the computed hop and
* fraction may be a bit away from what it should be.
*/
PatternHop bestHop = null;
double minDist = Double.MAX_VALUE;
for (PatternHop hop : hops) {
LineString line = hop.getGeometry();
double dist = SphericalDistanceLibrary.fastDistance(point, line);
if (dist < minDist) {
minDist = dist;
bestHop = hop;
}
}
if (minDist > 1000)
LOG.warn("On-board depart: origin point suspiciously away from nearest trip shape ({} meters)", minDist);
else
LOG.info("On-board depart: origin point {} meters away from hop shape", minDist);
/*
* 3. Compute the fraction covered percentage of the current hop. This assume a constant
* trip speed alongside the whole hop: this should be quite precise for small hops
* (buses), a bit less for longer ones (long distance train). Shape linear distance is
* of no help here, as the unit is arbitrary (and probably usually a distance).
*/
LineString geometry = bestHop.getGeometry();
P2<LineString> geomPair = GeometryUtils.splitGeometryAtPoint(geometry, point);
geomRemaining = geomPair.second;
double total = SphericalDistanceLibrary.fastLength(geometry);
double remaining = SphericalDistanceLibrary.fastLength(geomRemaining);
fractionCovered = total > 0.0 ? (double) (1.0 - remaining / total) : 0.0;
nextStop = (PatternStopVertex) bestHop.getToVertex();
bestStopIndex = bestHop.getStopIndex();
/*
* 4. Compute service day based on given departure day/time relative to
* scheduled/real-time trip time for hop. This is needed as for some trips any service
* day can apply.
*/
int minDelta = Integer.MAX_VALUE;
int actDelta = 0;
for (ServiceDay serviceDay : ctx.serviceDays) {
TripPattern pattern = nextStop.getTripPattern();
Timetable timetable = pattern.getUpdatedTimetable(opt, serviceDay);
// Get the tripTimes including real-time updates for the serviceDay
TripTimes tripTimes = timetable.getTripTimes(timetable.getTripIndex(tripId));
int depTime = tripTimes.getDepartureTime(bestStopIndex);
int arrTime = tripTimes.getArrivalTime(bestStopIndex + 1);
int estTime = (int) Math.round(depTime * fractionCovered + arrTime * (1 - fractionCovered));
int time = serviceDay.secondsSinceMidnight(opt.dateTime);
/*
* TODO Weight differently early vs late time, as the probability of any transit
* being late is higher than being early. However, this has impact if your bus is
* more than 12h late, I don't think this would happen really often.
*/
int deltaTime = Math.abs(time - estTime);
if (deltaTime < minDelta) {
minDelta = deltaTime;
actDelta = time - estTime;
bestTripTimes = tripTimes;
bestServiceDay = serviceDay;
}
}
if (minDelta > 60000)
// Being more than 1h late should not happen often
LOG.warn("On-board depart: delta between scheduled/real-time and actual time suspiciously large: {} seconds.", actDelta);
else
LOG.info("On-board depart: delta between scheduled/real-time and actual time is {} seconds.", actDelta);
} else {
/* 2. Compute service day */
for (ServiceDay serviceDay : ctx.serviceDays) {
Timetable timetable = tripPattern.getUpdatedTimetable(opt, serviceDay);
// Get the tripTimes including real-time updates for the serviceDay
TripTimes tripTimes = timetable.getTripTimes(timetable.getTripIndex(tripId));
int depTime = tripTimes.getDepartureTime(0);
int arrTime = tripTimes.getArrivalTime(tripTimes.getNumStops() - 1);
int time = serviceDay.secondsSinceMidnight(opt.dateTime);
if (depTime <= time && time <= arrTime) {
bestTripTimes = tripTimes;
bestServiceDay = serviceDay;
}
}
if (bestServiceDay == null) {
throw new RuntimeException("Unable to determine on-board depart service day.");
}
int time = bestServiceDay.secondsSinceMidnight(opt.dateTime);
/*
* 3. Get the best hop from the list, given the parameters. This is done by finding the
* last hop that has not yet departed.
*/
PatternHop bestHop = null;
for (PatternHop hop : hops) {
int stopIndex = hop.getStopIndex();
int depTime = bestTripTimes.getDepartureTime(stopIndex);
int arrTime = bestTripTimes.getArrivalTime(stopIndex + 1);
if (time == arrTime) {
return ctx.graph.getVertex(hop.getEndStop().getId().toString());
} else if (depTime < time) {
bestHop = hop;
bestStopIndex = stopIndex;
} else if (time == depTime || bestTripTimes.getArrivalTime(bestStopIndex + 1) < time) {
return ctx.graph.getVertex(hop.getBeginStop().getId().toString());
} else {
break;
}
}
nextStop = (PatternStopVertex) bestHop.getToVertex();
LineString geometry = bestHop.getGeometry();
/*
* 4. Compute the fraction covered percentage of the current hop. Once again a constant
* trip speed is assumed. The linear distance of the shape is used, so the results are
* not 100% accurate. On the flip side, they are easy to compute and very well testable.
*/
int depTime = bestTripTimes.getDepartureTime(bestStopIndex);
int arrTime = bestTripTimes.getArrivalTime(bestStopIndex + 1);
fractionCovered = ((double) (time - depTime)) / ((double) (arrTime - depTime));
P2<LineString> geomPair = GeometryUtils.splitGeometryAtFraction(geometry, fractionCovered);
geomRemaining = geomPair.second;
if (geometry.isEmpty()) {
lon = Double.NaN;
lat = Double.NaN;
} else {
Coordinate start;
if (geomRemaining.isEmpty()) {
start = geometry.getCoordinateN(geometry.getNumPoints() - 1);
} else {
start = geomRemaining.getCoordinateN(0);
}
lon = start.x;
lat = start.y;
}
}
OnboardDepartVertex onboardDepart = new OnboardDepartVertex("on_board_depart", lon, lat);
OnBoardDepartPatternHop startHop = new OnBoardDepartPatternHop(onboardDepart, nextStop, bestTripTimes, bestServiceDay, bestStopIndex, fractionCovered);
startHop.setGeometry(geomRemaining);
return onboardDepart;
}
Aggregations