Skip to content

Commit

Permalink
selected-link custom modification
Browse files Browse the repository at this point in the history
this can be used as an experimental worker version
with a custom modification of r5type select-link
adresses #913
  • Loading branch information
abyrd committed Jan 3, 2024
1 parent b3fa65b commit 4cb4c86
Show file tree
Hide file tree
Showing 17 changed files with 749 additions and 87 deletions.
8 changes: 5 additions & 3 deletions src/main/java/com/conveyal/gtfs/GTFSCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ public class GTFSCache implements Component {
// The following two caches hold spatial indexes of GTFS geometries for generating Mapbox vector tiles, one spatial
// index per feed keyed on BundleScopedFeedId. They could potentially be combined such that cache values are a
// compound type holding two indexes, or cache values are a single index containing a mix of different geometry
// types that are filtered on iteration. They could also be integreated into the GTFSFeed values of the main
// GTFSCache#cache. However GTFSFeed is already a very long class, and we may want to tune eviction parameters
// types that are filtered on iteration. They could also be integrated into the GTFSFeed values of the main
// GTFSCache#cache. However, GTFSFeed is already a very long class, and we may want to tune eviction parameters
// separately for GTFSFeed and these indexes. While GTFSFeeds are expected to incur constant memory use, the
// spatial indexes are potentially unlimited in size and we may want to evict them faster or limit their quantity.
// spatial indexes are potentially unlimited in size, so we may want to evict them faster or limit their quantity.
// We have decided to keep them as separate caches until we're certain of the chosen eviction tuning parameters.

/** A cache of spatial indexes of TripPattern shapes, keyed on the BundleScopedFeedId. */
Expand Down Expand Up @@ -127,6 +127,8 @@ public FileStorageKey getFileKey (String id, String extension) {
// The feedId of the GTFSFeed objects may not be unique - we can have multiple versions of the same feed
// covering different time periods, uploaded by different users. Therefore we record another ID here that is
// known to be unique across the whole application - the ID used to fetch the feed.
// NOTE as of 2023, this is no longer true. All uploaded feeds have assigned unique UUIDs so as far as I know
// they can't collide, we don't need this uniqueId field, and we may not even need bundle-scoped feed IDs.
feed.uniqueId = id;
return feed;
}
Expand Down
14 changes: 8 additions & 6 deletions src/main/java/com/conveyal/gtfs/GTFSFeed.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,18 @@ public class GTFSFeed implements Cloneable, Closeable {
/** The MapDB database handling persistence of Maps to a pair of disk files behind the scenes. */
private DB db;

/** An ID (sometimes declared by the feed itself) which may remain the same across successive feed versions. */
/**
* An ID (sometimes declared by the feed itself) which may remain the same across successive feed versions.
* In R5 as of 2023 this is always overwritten with a unique UUID to avoid problems with successive feed versions
* or edited/modified versions of the same feeds.
*/
public String feedId;

/**
* This field was merged in from the wrapper FeedSource. It is a unique identifier for this particular GTFS file.
* Successive versions of the data for the same operators, or even different copies of the same operator's data
* uploaded by different people, should have different uniqueIds.
* In practice this is mostly copied into WrappedGTFSEntity instances used in the Analysis GraphQL API.
* In R5 as of 2023, this field will contain the bundle-scoped feed ID used to fetch the feed object from the
* GTFSCache (but is not present on disk or before saving - only after it's been reloaded from a file by the cache).
*/
public transient String uniqueId; // set this to feedId until it is overwritten, to match FeedSource behavior
public transient String uniqueId;

// All tables below should be MapDB maps so the entire GTFSFeed is persistent and uses constant memory.

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/conveyal/gtfs/GeometryCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* LoadingCache so should be thread safe and provide granular per-key locking, which is convenient when serving up
* lots of simultaneous vector tile requests.
*
* This is currently used only for looking up geomertries when producing Mapbox vector map tiles, hence the single
* This is currently used only for looking up geometries when producing Mapbox vector map tiles, hence the single
* set of hard-wired cache eviction parameters. For more general use we'd want another constructor to change them.
*/
public class GeometryCache<T extends Geometry> {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/com/conveyal/r5/analyst/Grid.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
Expand Down Expand Up @@ -170,7 +171,8 @@ public List<PixelWeight> getPixelWeights (Geometry geometry, boolean relativeToP

double area = geometry.getArea();
if (area < 1e-12) {
throw new IllegalArgumentException("Feature geometry is too small");
LOG.warn("Discarding feature. Its area is too small to serve as a denominator ({} square degrees).", area);
return Collections.EMPTY_LIST;
}

if (area > MAX_FEATURE_AREA_SQ_DEG) {
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/com/conveyal/r5/analyst/cluster/PathResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,24 @@

import com.conveyal.r5.analyst.StreetTimesAndModes;
import com.conveyal.r5.transit.TransitLayer;
import com.conveyal.r5.transit.TripPattern;
import com.conveyal.r5.transit.path.Path;
import com.conveyal.r5.transit.path.PatternSequence;
import com.conveyal.r5.transit.path.RouteSequence;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.set.TIntSet;
import gnu.trove.set.hash.TIntHashSet;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.awt.*;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
Expand All @@ -32,6 +40,8 @@

public class PathResult {

private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

/**
* The maximum number of destinations for which we'll generate detailed path information in a single request.
* Detailed path information was added on to the original design, which returned a simple grid of travel times.
Expand All @@ -41,12 +51,14 @@ public class PathResult {
public static final int MAX_PATH_DESTINATIONS = 5_000;

private final int nDestinations;

/**
* Array with one entry per destination. Each entry is a map from a "path template" to the associated iteration
* details. For now, the path template is a route-based path ignoring per-iteration details such as wait time.
* With additional changes, patterns could be collapsed further to route combinations or modes.
*/
public final Multimap<RouteSequence, Iteration>[] iterationsForPathTemplates;

private final TransitLayer transitLayer;

public static final String[] DATA_COLUMNS = new String[]{
Expand Down Expand Up @@ -83,6 +95,15 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) {
* pattern-based keys
*/
public void setTarget(int targetIndex, Multimap<PatternSequence, Iteration> patterns) {

// When selected link analysis is enabled, filter down the PatternSequence-Iteration Multimap to retain only
// those keys passing through the selected links.
// TODO Maybe selectedLink should be on TransitLayer, and somehow indicate the number of removed iterations.
if (transitLayer.parentNetwork.selectedLink != null) {
patterns = transitLayer.parentNetwork.selectedLink.filterPatterns(patterns);
}

// The rest of this runs independent of whether a SelectedLink filtered down the patterns-iterations map.
Multimap<RouteSequence, Iteration> routes = HashMultimap.create();
patterns.forEach(((patternSeq, iteration) -> routes.put(new RouteSequence(patternSeq, transitLayer), iteration)));
iterationsForPathTemplates[targetIndex] = routes;
Expand All @@ -103,6 +124,35 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
summary[d] = new ArrayList<>();
Multimap<RouteSequence, Iteration> iterationMap = iterationsForPathTemplates[d];
if (iterationMap != null) {
// SelectedLink case: collapse all RouteSequences and Iterations for this OD pair into one to simplify.
// This could also be done by merging all Iterations under a single RouteSequence with all route IDs.
if (transitLayer.parentNetwork.selectedLink != null) {
int nIterations = 0;
TIntSet allRouteIds = new TIntHashSet();
double summedTotalTime = 0;
for (RouteSequence routeSequence: iterationMap.keySet()) {
Collection<Iteration> iterations = iterationMap.get(routeSequence);
nIterations += iterations.size();
allRouteIds.addAll(routeSequence.routes);
summedTotalTime += iterations.stream().mapToInt(i -> i.totalTime).sum();
}
// Many destinations will have no iterations at all passing through the SelectedLink area.
// Skip those to keep the CSV output short.
if (nIterations > 0) {
String[] row = new String[DATA_COLUMNS.length];
Arrays.fill(row, "ALL");
String allRouteIdsPipeSeparated = Arrays.stream(allRouteIds.toArray())
.mapToObj(transitLayer.routes::get)
.map(routeInfo -> routeInfo.route_id)
.collect(Collectors.joining("|"));
row[0] = allRouteIdsPipeSeparated;
row[row.length - 1] = Integer.toString(nIterations);
row[row.length - 2] = String.format("%.1f", summedTotalTime / nIterations / 60d); // Average total time
summary[d].add(row);
}
continue;
}
// Standard (non SelectedLink) case.
for (RouteSequence routeSequence: iterationMap.keySet()) {
Collection<Iteration> iterations = iterationMap.get(routeSequence);
int nIterations = iterations.size();
Expand Down
Loading

0 comments on commit 4cb4c86

Please sign in to comment.