Skip to content

Commit 4cb4c86

Browse files
committed
selected-link custom modification
this can be used as an experimental worker version with a custom modification of r5type select-link adresses #913
1 parent b3fa65b commit 4cb4c86

17 files changed

+749
-87
lines changed

src/main/java/com/conveyal/gtfs/GTFSCache.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ public class GTFSCache implements Component {
4848
// The following two caches hold spatial indexes of GTFS geometries for generating Mapbox vector tiles, one spatial
4949
// index per feed keyed on BundleScopedFeedId. They could potentially be combined such that cache values are a
5050
// compound type holding two indexes, or cache values are a single index containing a mix of different geometry
51-
// types that are filtered on iteration. They could also be integreated into the GTFSFeed values of the main
52-
// GTFSCache#cache. However GTFSFeed is already a very long class, and we may want to tune eviction parameters
51+
// types that are filtered on iteration. They could also be integrated into the GTFSFeed values of the main
52+
// GTFSCache#cache. However, GTFSFeed is already a very long class, and we may want to tune eviction parameters
5353
// separately for GTFSFeed and these indexes. While GTFSFeeds are expected to incur constant memory use, the
54-
// spatial indexes are potentially unlimited in size and we may want to evict them faster or limit their quantity.
54+
// spatial indexes are potentially unlimited in size, so we may want to evict them faster or limit their quantity.
5555
// We have decided to keep them as separate caches until we're certain of the chosen eviction tuning parameters.
5656

5757
/** A cache of spatial indexes of TripPattern shapes, keyed on the BundleScopedFeedId. */
@@ -127,6 +127,8 @@ public FileStorageKey getFileKey (String id, String extension) {
127127
// The feedId of the GTFSFeed objects may not be unique - we can have multiple versions of the same feed
128128
// covering different time periods, uploaded by different users. Therefore we record another ID here that is
129129
// known to be unique across the whole application - the ID used to fetch the feed.
130+
// NOTE as of 2023, this is no longer true. All uploaded feeds have assigned unique UUIDs so as far as I know
131+
// they can't collide, we don't need this uniqueId field, and we may not even need bundle-scoped feed IDs.
130132
feed.uniqueId = id;
131133
return feed;
132134
}

src/main/java/com/conveyal/gtfs/GTFSFeed.java

+8-6
Original file line numberDiff line numberDiff line change
@@ -85,16 +85,18 @@ public class GTFSFeed implements Cloneable, Closeable {
8585
/** The MapDB database handling persistence of Maps to a pair of disk files behind the scenes. */
8686
private DB db;
8787

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

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

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

src/main/java/com/conveyal/gtfs/GeometryCache.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* LoadingCache so should be thread safe and provide granular per-key locking, which is convenient when serving up
2020
* lots of simultaneous vector tile requests.
2121
*
22-
* This is currently used only for looking up geomertries when producing Mapbox vector map tiles, hence the single
22+
* This is currently used only for looking up geometries when producing Mapbox vector map tiles, hence the single
2323
* set of hard-wired cache eviction parameters. For more general use we'd want another constructor to change them.
2424
*/
2525
public class GeometryCache<T extends Geometry> {

src/main/java/com/conveyal/r5/analyst/Grid.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.util.ArrayList;
5454
import java.util.Arrays;
5555
import java.util.Collection;
56+
import java.util.Collections;
5657
import java.util.HashMap;
5758
import java.util.HashSet;
5859
import java.util.Iterator;
@@ -170,7 +171,8 @@ public List<PixelWeight> getPixelWeights (Geometry geometry, boolean relativeToP
170171

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

176178
if (area > MAX_FEATURE_AREA_SQ_DEG) {

src/main/java/com/conveyal/r5/analyst/cluster/PathResult.java

+50
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@
22

33
import com.conveyal.r5.analyst.StreetTimesAndModes;
44
import com.conveyal.r5.transit.TransitLayer;
5+
import com.conveyal.r5.transit.TripPattern;
56
import com.conveyal.r5.transit.path.Path;
67
import com.conveyal.r5.transit.path.PatternSequence;
78
import com.conveyal.r5.transit.path.RouteSequence;
89
import com.google.common.collect.HashMultimap;
910
import com.google.common.collect.Multimap;
1011
import gnu.trove.list.TIntList;
1112
import gnu.trove.list.array.TIntArrayList;
13+
import gnu.trove.set.TIntSet;
14+
import gnu.trove.set.hash.TIntHashSet;
1215
import org.apache.commons.lang3.ArrayUtils;
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
1318

19+
import java.awt.*;
20+
import java.lang.invoke.MethodHandles;
1421
import java.util.ArrayList;
22+
import java.util.Arrays;
1523
import java.util.Collection;
1624
import java.util.Comparator;
1725
import java.util.List;
@@ -32,6 +40,8 @@
3240

3341
public class PathResult {
3442

43+
private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
44+
3545
/**
3646
* The maximum number of destinations for which we'll generate detailed path information in a single request.
3747
* Detailed path information was added on to the original design, which returned a simple grid of travel times.
@@ -41,12 +51,14 @@ public class PathResult {
4151
public static final int MAX_PATH_DESTINATIONS = 5_000;
4252

4353
private final int nDestinations;
54+
4455
/**
4556
* Array with one entry per destination. Each entry is a map from a "path template" to the associated iteration
4657
* details. For now, the path template is a route-based path ignoring per-iteration details such as wait time.
4758
* With additional changes, patterns could be collapsed further to route combinations or modes.
4859
*/
4960
public final Multimap<RouteSequence, Iteration>[] iterationsForPathTemplates;
61+
5062
private final TransitLayer transitLayer;
5163

5264
public static final String[] DATA_COLUMNS = new String[]{
@@ -83,6 +95,15 @@ public PathResult(AnalysisWorkerTask task, TransitLayer transitLayer) {
8395
* pattern-based keys
8496
*/
8597
public void setTarget(int targetIndex, Multimap<PatternSequence, Iteration> patterns) {
98+
99+
// When selected link analysis is enabled, filter down the PatternSequence-Iteration Multimap to retain only
100+
// those keys passing through the selected links.
101+
// TODO Maybe selectedLink should be on TransitLayer, and somehow indicate the number of removed iterations.
102+
if (transitLayer.parentNetwork.selectedLink != null) {
103+
patterns = transitLayer.parentNetwork.selectedLink.filterPatterns(patterns);
104+
}
105+
106+
// The rest of this runs independent of whether a SelectedLink filtered down the patterns-iterations map.
86107
Multimap<RouteSequence, Iteration> routes = HashMultimap.create();
87108
patterns.forEach(((patternSeq, iteration) -> routes.put(new RouteSequence(patternSeq, transitLayer), iteration)));
88109
iterationsForPathTemplates[targetIndex] = routes;
@@ -103,6 +124,35 @@ public ArrayList<String[]>[] summarizeIterations(Stat stat) {
103124
summary[d] = new ArrayList<>();
104125
Multimap<RouteSequence, Iteration> iterationMap = iterationsForPathTemplates[d];
105126
if (iterationMap != null) {
127+
// SelectedLink case: collapse all RouteSequences and Iterations for this OD pair into one to simplify.
128+
// This could also be done by merging all Iterations under a single RouteSequence with all route IDs.
129+
if (transitLayer.parentNetwork.selectedLink != null) {
130+
int nIterations = 0;
131+
TIntSet allRouteIds = new TIntHashSet();
132+
double summedTotalTime = 0;
133+
for (RouteSequence routeSequence: iterationMap.keySet()) {
134+
Collection<Iteration> iterations = iterationMap.get(routeSequence);
135+
nIterations += iterations.size();
136+
allRouteIds.addAll(routeSequence.routes);
137+
summedTotalTime += iterations.stream().mapToInt(i -> i.totalTime).sum();
138+
}
139+
// Many destinations will have no iterations at all passing through the SelectedLink area.
140+
// Skip those to keep the CSV output short.
141+
if (nIterations > 0) {
142+
String[] row = new String[DATA_COLUMNS.length];
143+
Arrays.fill(row, "ALL");
144+
String allRouteIdsPipeSeparated = Arrays.stream(allRouteIds.toArray())
145+
.mapToObj(transitLayer.routes::get)
146+
.map(routeInfo -> routeInfo.route_id)
147+
.collect(Collectors.joining("|"));
148+
row[0] = allRouteIdsPipeSeparated;
149+
row[row.length - 1] = Integer.toString(nIterations);
150+
row[row.length - 2] = String.format("%.1f", summedTotalTime / nIterations / 60d); // Average total time
151+
summary[d].add(row);
152+
}
153+
continue;
154+
}
155+
// Standard (non SelectedLink) case.
106156
for (RouteSequence routeSequence: iterationMap.keySet()) {
107157
Collection<Iteration> iterations = iterationMap.get(routeSequence);
108158
int nIterations = iterations.size();

0 commit comments

Comments
 (0)