Skip to content

Commit 5889f8f

Browse files
Merge pull request #948 from conveyal/dev
v7.3 Release
2 parents a96f49a + e5a9a26 commit 5889f8f

13 files changed

+244
-181
lines changed

build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ java {
1414
toolchain {
1515
languageVersion.set(JavaLanguageVersion.of(21))
1616
}
17+
withSourcesJar()
1718
}
1819

1920
jar {
@@ -164,7 +165,7 @@ dependencies {
164165
}
165166

166167
// Database driver.
167-
implementation 'org.mongodb:mongo-java-driver:3.11.0'
168+
implementation 'org.mongodb:mongodb-driver-legacy:5.2.0'
168169

169170
// Legacy system for storing Java objects, this functionality is now provided by the MongoDB driver itself.
170171
implementation 'org.mongojack:mongojack:2.10.1'

src/main/java/com/conveyal/analysis/controllers/BundleController.java

+44-10
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@
1313
import com.conveyal.file.FileUtils;
1414
import com.conveyal.gtfs.GTFSCache;
1515
import com.conveyal.gtfs.GTFSFeed;
16-
import com.conveyal.gtfs.error.GTFSError;
1716
import com.conveyal.gtfs.error.GeneralError;
1817
import com.conveyal.gtfs.model.Stop;
1918
import com.conveyal.gtfs.validator.PostLoadValidator;
2019
import com.conveyal.osmlib.Node;
2120
import com.conveyal.osmlib.OSM;
22-
import com.conveyal.r5.analyst.progress.ProgressInputStream;
2321
import com.conveyal.r5.analyst.cluster.TransportNetworkConfig;
22+
import com.conveyal.r5.analyst.progress.ProgressInputStream;
2423
import com.conveyal.r5.analyst.progress.Task;
2524
import com.conveyal.r5.streets.OSMCache;
2625
import com.conveyal.r5.util.ExceptionUtils;
@@ -81,6 +80,7 @@ public BundleController (BackendComponents components) {
8180
public void registerEndpoints (Service sparkService) {
8281
sparkService.path("/api/bundle", () -> {
8382
sparkService.get("", this::getBundles, toJson);
83+
sparkService.get("/:_id/config", this::getBundleConfig, toJson);
8484
sparkService.get("/:_id", this::getBundle, toJson);
8585
sparkService.post("", this::create, toJson);
8686
sparkService.put("/:_id", this::update, toJson);
@@ -110,15 +110,13 @@ private Bundle create (Request req, Response res) {
110110
try {
111111
bundle.name = files.get("bundleName").get(0).getString("UTF-8");
112112
bundle.regionId = files.get("regionId").get(0).getString("UTF-8");
113-
114113
if (files.get("osmId") != null) {
115114
bundle.osmId = files.get("osmId").get(0).getString("UTF-8");
116115
Bundle bundleWithOsm = Persistence.bundles.find(QueryBuilder.start("osmId").is(bundle.osmId).get()).next();
117116
if (bundleWithOsm == null) {
118117
throw AnalysisServerException.badRequest("Selected OSM does not exist.");
119118
}
120119
}
121-
122120
if (files.get("feedGroupId") != null) {
123121
bundle.feedGroupId = files.get("feedGroupId").get(0).getString("UTF-8");
124122
Bundle bundleWithFeed = Persistence.bundles.find(QueryBuilder.start("feedGroupId").is(bundle.feedGroupId).get()).next();
@@ -135,6 +133,13 @@ private Bundle create (Request req, Response res) {
135133
bundle.feedsComplete = bundleWithFeed.feedsComplete;
136134
bundle.totalFeeds = bundleWithFeed.totalFeeds;
137135
}
136+
if (files.get("config") != null) {
137+
// Validation by deserializing into a model class instance. Unknown fields are ignored to
138+
// allow sending config to custom or experimental workers with features unknown to the backend.
139+
// The fields specifying OSM and GTFS IDs are not expected here. They will be ignored and overwritten.
140+
String configString = files.get("config").get(0).getString();
141+
bundle.config = JsonUtil.objectMapper.readValue(configString, TransportNetworkConfig.class);
142+
}
138143
UserPermissions userPermissions = UserPermissions.from(req);
139144
bundle.accessGroup = userPermissions.accessGroup;
140145
bundle.createdBy = userPermissions.email;
@@ -274,15 +279,19 @@ private Bundle create (Request req, Response res) {
274279
return bundle;
275280
}
276281

282+
/** SIDE EFFECTS: This method will change the field bundle.config before writing it. */
277283
private void writeNetworkConfigToCache (Bundle bundle) throws IOException {
278-
TransportNetworkConfig networkConfig = new TransportNetworkConfig();
279-
networkConfig.osmId = bundle.osmId;
280-
networkConfig.gtfsIds = bundle.feeds.stream().map(f -> f.bundleScopedFeedId).collect(Collectors.toList());
281-
284+
// If the user specified additional network configuration options, they should already be in bundle.config.
285+
// If no custom options were specified, we start with a fresh, empty instance.
286+
if (bundle.config == null) {
287+
bundle.config = new TransportNetworkConfig();
288+
}
289+
// This will overwrite and override any inconsistent osm and gtfs IDs that were mistakenly supplied by the user.
290+
bundle.config.osmId = bundle.osmId;
291+
bundle.config.gtfsIds = bundle.feeds.stream().map(f -> f.bundleScopedFeedId).collect(Collectors.toList());
282292
String configFileName = bundle._id + ".json";
283293
File configFile = FileUtils.createScratchFile("json");
284-
JsonUtil.objectMapper.writeValue(configFile, networkConfig);
285-
294+
JsonUtil.objectMapper.writeValue(configFile, bundle.config);
286295
FileStorageKey key = new FileStorageKey(BUNDLES, configFileName);
287296
fileStorage.moveIntoStorage(key, configFile);
288297
}
@@ -312,6 +321,31 @@ private Bundle getBundle (Request req, Response res) {
312321
return bundle;
313322
}
314323

324+
/**
325+
* There are two copies of the Bundle/Network config: one in the Bundle entry in the database and one in a JSON
326+
* file (obtainable by the workers). This method always reads the one in the file, which has been around longer
327+
* and is considered the definitive source of truth. The entry in the database is a newer addition and has only
328+
* been around since September 2024.
329+
*/
330+
private TransportNetworkConfig getBundleConfig(Request request, Response res) {
331+
// Unfortunately this mimics logic in TransportNetworkCache. Deduplicate in a static utility method?
332+
String id = GTFSCache.cleanId(request.params("_id"));
333+
FileStorageKey key = new FileStorageKey(BUNDLES, id, "json");
334+
File networkConfigFile = fileStorage.getFile(key);
335+
if (!networkConfigFile.exists()) {
336+
throw AnalysisServerException.notFound("Bundle configuration file could not be found.");
337+
}
338+
339+
// Unlike in the worker, we expect the backend to have a model field for every known network/bundle option.
340+
// Therefore, use the default objectMapper that does not tolerate unknown fields.
341+
try {
342+
return JsonUtil.objectMapper.readValue(networkConfigFile, TransportNetworkConfig.class);
343+
} catch (Exception exception) {
344+
LOG.error("Exception deserializing stored network config", exception);
345+
return null;
346+
}
347+
}
348+
315349
private Collection<Bundle> getBundles (Request req, Response res) {
316350
return Persistence.bundles.findPermittedForQuery(req);
317351
}

src/main/java/com/conveyal/analysis/models/Bundle.java

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.conveyal.gtfs.error.GTFSError;
66
import com.conveyal.gtfs.model.FeedInfo;
77
import com.conveyal.gtfs.validator.model.Priority;
8+
import com.conveyal.r5.analyst.cluster.TransportNetworkConfig;
89
import com.fasterxml.jackson.annotation.JsonIgnore;
910

1011
import java.time.LocalDate;
@@ -47,6 +48,11 @@ public class Bundle extends Model implements Cloneable {
4748
public int feedsComplete;
4849
public int totalFeeds;
4950

51+
// The definitive TransportNetworkConfig is a JSON file stored alongside the feeds in file storage. It is
52+
// duplicated here to record any additional user-specified options that were supplied when the bundle was created.
53+
// It may contain redundant copies of information stored in the outer level Bundle such as OSM and GTFS feed IDs.
54+
public TransportNetworkConfig config;
55+
5056
public static String bundleScopeFeedId (String feedId, String feedGroupId) {
5157
return String.format("%s_%s", feedId, feedGroupId);
5258
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -110,11 +110,13 @@ protected TransportNetwork buildValue(Key key) {
110110

111111
// Get the set of points to which we are measuring travel time. Any smaller sub-grids created here will
112112
// reference the scenarioNetwork's built-in full-extent pointset, so can reuse its linkage.
113-
// TODO handle multiple destination grids.
113+
// FIXME handle multiple destination grids.
114114

115115
if (key.destinationGridExtents == null) {
116116
// Special (and ideally temporary) case for regional freeform destinations, where there is no grid to link.
117117
// The null destinationGridExtents are created by the WebMercatorExtents#forPointsets else clause.
118+
// FIXME there is no grid to link, but there are points and egress tables to make!
119+
// see com.conveyal.r5.analyst.cluster.AnalysisWorkerTask.loadAndValidateDestinationPointSets
118120
return scenarioNetwork;
119121
}
120122

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

+7-7
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@
2626
* scenarios from the backend instead of from S3.
2727
*
2828
* TODO merge this with TransportNetworkCache#resolveScenario into a single multi-level mem/disk/s3 cache.
29-
* Note that this cache is going to just grow indefinitely in size as a worker receives many iterations of the same
30-
* scenario - that could be a memory leak. Again multi level caching could releive those worries.
31-
* It's debatable whether we should be hanging on to scenarios passed with single point requests becuase they may never
32-
* be used again.
29+
* This cache grows in size without bound as a worker receives many iterations of the same scenario.
30+
* This is technically a sort of memory leak for long-lived workers. Multi-level caching could relieve those worries.
31+
* However, this cache stores only the Scenarios and Modifications, not any large egress tables or linkages.
32+
*
33+
* It's debatable whether we should be hanging on to scenarios passed with single point requests,
34+
* because they may never be used again.
3335
* Should we just always require a single point task to be sent to the cluster before a regional?
3436
* That would not ensure the scenario was present on all workers though.
35-
*
36-
* Created by abyrd on 2018-10-29
3737
*/
3838
public class ScenarioCache {
3939

@@ -44,7 +44,7 @@ public class ScenarioCache {
4444
public synchronized void storeScenario (Scenario scenario) {
4545
Scenario existingScenario = scenariosById.put(scenario.id, scenario);
4646
if (existingScenario != null) {
47-
LOG.debug("Scenario cache already contained a this scenario.");
47+
LOG.debug("Scenario cache already contained this scenario.");
4848
}
4949
}
5050

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,8 @@
22

33
import com.conveyal.r5.analyst.fare.InRoutingFareCalculator;
44
import com.conveyal.r5.analyst.scenario.Modification;
5-
import com.conveyal.r5.analyst.scenario.RasterCost;
6-
import com.conveyal.r5.analyst.scenario.ShapefileLts;
75
import com.conveyal.r5.profile.StreetMode;
8-
import com.fasterxml.jackson.annotation.JsonIgnore;
96
import com.fasterxml.jackson.annotation.JsonInclude;
10-
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
117

128
import java.util.List;
139
import java.util.Set;
@@ -54,4 +50,11 @@ public class TransportNetworkConfig {
5450
*/
5551
public Set<StreetMode> buildGridsForModes;
5652

53+
/**
54+
* Specifies which "labeler" to use when setting traversal mode permissions from OSM tags. For now, only
55+
* implemented with "sidewalk" to use the SidewalkTraversalPermissionLayer. This should eventually be cleaned up
56+
* (specifying different labelers, using enums).
57+
*/
58+
public String traversalPermissionLabeler;
59+
5760
}

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

-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ public class WorkerStatus {
3939
public String workerVersion;
4040
public String workerId;
4141
public Set<String> networks = new HashSet<>();
42-
public Set<String> scenarios = new HashSet<>();
4342
public double secondsSinceLastPoll;
4443
public Map<String, Integer> tasksPerMinuteByJobId;
4544
@JsonUnwrapped(prefix = "ec2")
@@ -86,7 +85,6 @@ public WorkerStatus (AnalysisWorker worker) {
8685
// networks = worker.networkPreloader.transportNetworkCache.getLoadedNetworkIds();
8786
// For now we report a single network, even before it's loaded.
8887
networks = Sets.newHashSet(worker.networkId);
89-
scenarios = worker.networkPreloader.transportNetworkCache.getAppliedScenarios();
9088
ec2 = worker.ec2info;
9189

9290
OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.conveyal.r5.labeling;
2+
3+
import com.conveyal.osmlib.Way;
4+
import com.conveyal.r5.streets.EdgeStore;
5+
6+
/**
7+
* Traversal permission labeler that restricts walking on most driving ways (useful for networks with complete
8+
* sidewalks). Also includes permissions for the United States (see USTraversalPermissionLabeler).
9+
*/
10+
public class SidewalkTraversalPermissionLabeler extends TraversalPermissionLabeler {
11+
static {
12+
addPermissions("pedestrian", "bicycle=yes");
13+
addPermissions("bridleway", "bicycle=yes;foot=yes"); //horse=yes but we don't support horse
14+
addPermissions("cycleway", "bicycle=yes;foot=yes");
15+
addPermissions("trunk|primary|secondary|tertiary|unclassified|residential|living_street|road|service|track",
16+
"access=yes");
17+
}
18+
19+
@Override
20+
public RoadPermission getPermissions(Way way) {
21+
RoadPermission rp = super.getPermissions(way);
22+
if (rp.forward.contains(EdgeStore.EdgeFlag.ALLOWS_CAR) ||
23+
rp.forward.contains(EdgeStore.EdgeFlag.NO_THRU_TRAFFIC_CAR) ||
24+
rp.backward.contains(EdgeStore.EdgeFlag.ALLOWS_CAR) ||
25+
rp.backward.contains(EdgeStore.EdgeFlag.NO_THRU_TRAFFIC_CAR)
26+
) {
27+
rp.forward.remove(EdgeStore.EdgeFlag.ALLOWS_PEDESTRIAN);
28+
rp.forward.remove(EdgeStore.EdgeFlag.NO_THRU_TRAFFIC_PEDESTRIAN);
29+
rp.backward.remove(EdgeStore.EdgeFlag.ALLOWS_PEDESTRIAN);
30+
rp.backward.remove(EdgeStore.EdgeFlag.NO_THRU_TRAFFIC_PEDESTRIAN);
31+
}
32+
return rp;
33+
}
34+
35+
}

src/main/java/com/conveyal/r5/streets/StreetLayer.java

+21-3
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
import com.conveyal.osmlib.OSMEntity;
77
import com.conveyal.osmlib.Relation;
88
import com.conveyal.osmlib.Way;
9+
import com.conveyal.r5.analyst.cluster.TransportNetworkConfig;
910
import com.conveyal.r5.analyst.scenario.PickupWaitTimes;
1011
import com.conveyal.r5.api.util.BikeRentalStation;
1112
import com.conveyal.r5.api.util.ParkRideParking;
1213
import com.conveyal.r5.common.GeometryUtils;
1314
import com.conveyal.r5.labeling.LevelOfTrafficStressLabeler;
1415
import com.conveyal.r5.labeling.RoadPermission;
16+
import com.conveyal.r5.labeling.SidewalkTraversalPermissionLabeler;
1517
import com.conveyal.r5.labeling.SpeedLabeler;
1618
import com.conveyal.r5.labeling.StreetClass;
1719
import com.conveyal.r5.labeling.TraversalPermissionLabeler;
@@ -132,9 +134,9 @@ public class StreetLayer implements Serializable, Cloneable {
132134
public TIntObjectMap<ParkRideParking> parkRideLocationsMap;
133135

134136
// TODO these are only needed when building the network, should we really be keeping them here in the layer?
135-
// We should instead have a network builder that holds references to this transient state.
136-
// TODO don't hardwire to US
137-
private transient TraversalPermissionLabeler permissionLabeler = new USTraversalPermissionLabeler();
137+
// We should instead have a network builder that holds references to this transient state. Note initial
138+
// approach of specifying a TraversalPermissionLabeler in TransportNetworkConfig.
139+
private transient TraversalPermissionLabeler permissionLabeler;
138140
private transient LevelOfTrafficStressLabeler stressLabeler = new LevelOfTrafficStressLabeler();
139141
private transient TypeOfEdgeLabeler typeOfEdgeLabeler = new TypeOfEdgeLabeler();
140142
private transient SpeedLabeler speedLabeler;
@@ -207,6 +209,22 @@ public class StreetLayer implements Serializable, Cloneable {
207209

208210
public StreetLayer() {
209211
speedLabeler = new SpeedLabeler(SpeedConfig.defaultConfig());
212+
permissionLabeler = new USTraversalPermissionLabeler();
213+
}
214+
215+
public StreetLayer(TransportNetworkConfig config) {
216+
this();
217+
if (config != null) {
218+
permissionLabeler = switch (config.traversalPermissionLabeler) {
219+
case "sidewalk" -> new SidewalkTraversalPermissionLabeler();
220+
case null -> new USTraversalPermissionLabeler();
221+
default -> throw new IllegalArgumentException(
222+
"Unknown traversal permission labeler: " + config.traversalPermissionLabeler
223+
);
224+
};
225+
} else {
226+
permissionLabeler = new USTraversalPermissionLabeler();
227+
}
210228
}
211229

212230
/** Load street layer from an OSM-lib OSM DB */

src/main/java/com/conveyal/r5/streets/StreetRouter.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ public int getTravelTimeToVertex (int vertexIndex) {
742742
* fragments from the vertices at either end of the edge up to the destination split point.
743743
* If no states can be produced return null.
744744
*
745-
* Note that this is only used by the point to point street router, not by LinkedPointSets (which have equivalent
745+
* NOTE that this is ONLY USED BY the point to point street router, NOT BY LinkedPointSets (which have equivalent
746746
* logic in their eval method). The PointSet implementation only needs to produce times, not States. But ideally
747747
* some common logic can be factored out.
748748
*/

0 commit comments

Comments
 (0)