Skip to content

Commit e857fcd

Browse files
authored
Merge pull request #908 from conveyal/simpson-regional-task
Use regional tasks in Simpson Desert tests
2 parents 0cc9bdd + 6f93c66 commit e857fcd

File tree

7 files changed

+52
-53
lines changed

7 files changed

+52
-53
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,11 +88,9 @@ public OneOriginResult computeTravelTimes() {
8888
) {
8989
// Freeform destinations. Destination PointSet was set by handleOneRequest in the main AnalystWorker.
9090
destinations = request.destinationPointSets[0];
91-
} else if (!isNullOrEmpty(request.destinationPointSets)) {
92-
LOG.warn("ONLY VALID IN TESTING: Using PointSet object embedded in request outside regional analysis.");
93-
destinations = request.destinationPointSets[0];
9491
} else {
9592
// Gridded (non-freeform) destinations. This method finds them differently for regional and single requests.
93+
// We don't support freeform destinations for single point requests, as they must also return gridded travel times.
9694
WebMercatorExtents destinationGridExtents = request.getWebMercatorExtents();
9795
// Make a WebMercatorGridPointSet with the right extents, referring to the network's base grid and linkage.
9896
destinations = AnalysisWorkerTask.gridPointSetCache.get(destinationGridExtents, network.fullExtentGridPointSet);

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@ public OneOriginResult finish () {
346346
/**
347347
* Sanity check: all opportunity data sets should have the same size and location as the points to which we'll
348348
* calculate travel times. They will only be used if we're calculating accessibility.
349+
* TODO: expand to cover all cases where destinationPointSets are present, not just accessibility to Mercator grids.
350+
* Need to verify first: should this be skipped for one-to-one regional requests on FreeFormPointSets?
349351
*/
350352
public void checkOpportunityExtents (PointSet travelTimePointSet) {
351353
if (calculateAccessibility) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public class RegionalTask extends AnalysisWorkerTask implements Cloneable {
4646
public boolean oneToOne = false;
4747

4848
/**
49-
* Whether to record travel times between origins and destinations
49+
* Whether to record travel times between origins and destinations. This is done automatically for
50+
* TravelTimeSurfaceTask (single point tasks) but must be manually enabled on RegionalTasks using this field.
5051
*/
5152
public boolean recordTimes;
5253

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

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,23 @@ public WebMercatorExtents getWebMercatorExtents() {
6060

6161
@Override
6262
public int nTargetsPerOrigin () {
63-
// In TravelTimeSurfaceTasks, the set of destinations is always determined by the web mercator extents in the
64-
// request. A single WebMercatorGridPointSet is created with those extents. A single small freeform PointSet may
65-
// also be present, but only in testing situations. The checkState assertions serve to verify assumptions that
66-
// destinationPointSets are always set and we can polymorphically fetch the number of items for all normal and
67-
// testing use cases. Once that is clearly working the assertions could be removed.
68-
checkState(!isNullOrEmpty(destinationPointSets), "Expected destination PointSets to be present.");
69-
checkState(destinationPointSets.length == 1, "Expected a single destination PointSet in TravelTimeSurfaceTask.");
70-
PointSet destinations = destinationPointSets[0];
71-
int nFeatures = destinations.featureCount();
72-
if (destinations instanceof FreeFormPointSet) {
73-
LOG.warn("Should currently be used only in testing: FreeFormPointSet specified in TravelTimeSurfaceTask.");
74-
checkState(nFeatures == 1);
75-
} else {
76-
checkState(nFeatures == width * height);
63+
// In TravelTimeSurfaceTasks (single-point or single-origin tasks), the set of destinations is always
64+
// determined by the web mercator extents in the request itself. In many cases the destinationPointSets field
65+
// is empty, but when destinationPointSets are present (when we're calculating accessibility), those PointSets
66+
// are required to be gridded and exactly match the task extents (e.g. Grids wrapped in GridTransformWrapper).
67+
// In normal usage this requirement is validated by TravelTimeReducer#checkOpportunityExtents but that function
68+
// only checks when we're computing accessibility, and is only called when destinations are gridded. The
69+
// assertions here serve to further verify this requirement, as we've seen it violated before in tests.
70+
final int nTargets = width * height;
71+
if (destinationPointSets != null) {
72+
for (PointSet pointSet : destinationPointSets) {
73+
checkState(
74+
pointSet.featureCount() == nTargets,
75+
"Destination PointSet expected to exactly match extents embedded in single point task."
76+
);
77+
}
7778
}
78-
return nFeatures;
79+
return nTargets;
7980
}
8081

8182
}

src/test/java/com/conveyal/r5/analyst/network/GridLayout.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
import com.conveyal.gtfs.GTFSFeed;
44
import com.conveyal.osmlib.OSM;
5-
import com.conveyal.r5.analyst.Grid;
6-
import com.conveyal.r5.analyst.WebMercatorGridPointSet;
7-
import com.conveyal.r5.analyst.cluster.AnalysisWorkerTask;
85
import com.conveyal.r5.common.SphericalDistanceLibrary;
96
import com.conveyal.r5.profile.StreetMode;
107
import com.conveyal.r5.transit.TransportNetwork;
@@ -13,13 +10,10 @@
1310
import org.locationtech.jts.geom.Envelope;
1411

1512
import java.util.ArrayList;
16-
import java.util.Arrays;
1713
import java.util.List;
1814
import java.util.UUID;
1915
import java.util.stream.Stream;
2016

21-
import static com.conveyal.r5.analyst.WebMercatorExtents.DEFAULT_ZOOM;
22-
2317
/**
2418
* This is used in testing, to represent and create gridded transport systems with very regular spacing of roads and
2519
* transit stops, yielding highly predictable travel times that can be tested against actual output from the router.
@@ -165,8 +159,8 @@ public void addVerticalFrequencyRoute (int col, int headwayMinutes) {
165159
}
166160

167161
/** Creates a builder for analysis worker tasks, which represent searches on this grid network. */
168-
public GridSinglePointTaskBuilder newTaskBuilder() {
169-
return new GridSinglePointTaskBuilder(this);
162+
public GridRegionalTaskBuilder newTaskBuilder() {
163+
return new GridRegionalTaskBuilder(this);
170164
}
171165

172166
/** Get the minimum envelope containing all the points in this grid. */

src/test/java/com/conveyal/r5/analyst/network/GridSinglePointTaskBuilder.java renamed to src/test/java/com/conveyal/r5/analyst/network/GridRegionalTaskBuilder.java

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package com.conveyal.r5.analyst.network;
22

33
import com.conveyal.r5.analyst.FreeFormPointSet;
4-
import com.conveyal.r5.analyst.Grid;
54
import com.conveyal.r5.analyst.PointSet;
65
import com.conveyal.r5.analyst.WebMercatorExtents;
76
import com.conveyal.r5.analyst.cluster.AnalysisWorkerTask;
8-
import com.conveyal.r5.analyst.cluster.TravelTimeSurfaceTask;
7+
import com.conveyal.r5.analyst.cluster.RegionalTask;
98
import com.conveyal.r5.analyst.decay.StepDecayFunction;
109
import com.conveyal.r5.api.util.LegMode;
1110
import com.conveyal.r5.api.util.TransitModes;
@@ -20,21 +19,25 @@
2019
import static com.conveyal.r5.analyst.network.GridGtfsGenerator.WEEKEND_DATE;
2120

2221
/**
23-
* This creates a task for use in tests. It uses a builder pattern but for a non-immutable task object.
24-
* It provides convenience methods to set all the necessary fields. This builder may be reused to produce
25-
* several tasks in a row with different settings, but only use the most recently produced one at any time.
26-
* See build() for further explanation.
22+
* This creates a task for use in tests. It uses a builder pattern but modifies a non-immutable task object. It
23+
* provides convenience methods to set all the necessary fields. This builder may be reused to produce several tasks in
24+
* a row with different settings, but only use the most recently produced one at any time. See build() for further
25+
* explanation. We want to use a limited number of destinations at exact points instead of Mercator gridded
26+
* destinations, which would not be exactly aligned with the desert grid. Therefore we create regional tasks rather
27+
* than single-point TravelTimeSurfaceTasks because single-point tasks always have gridded destinations (they always
28+
* return gridded travel times which must be exactly aligned with any accessibility destinations).
2729
*/
28-
public class GridSinglePointTaskBuilder {
30+
public class GridRegionalTaskBuilder {
2931

3032
public static final int DEFAULT_MONTE_CARLO_DRAWS = 4800; // 40 per minute over a two hour window.
3133
private final GridLayout gridLayout;
32-
private final AnalysisWorkerTask task;
3334

34-
public GridSinglePointTaskBuilder (GridLayout gridLayout) {
35+
private final RegionalTask task;
36+
37+
public GridRegionalTaskBuilder(GridLayout gridLayout) {
3538
this.gridLayout = gridLayout;
3639
// We will accumulate settings into this task.
37-
task = new TravelTimeSurfaceTask();
40+
task = new RegionalTask();
3841
task.date = WEEKDAY_DATE;
3942
// Set defaults that can be overridden by calling builder methods.
4043
task.accessModes = EnumSet.of(LegMode.WALK);
@@ -52,10 +55,11 @@ public GridSinglePointTaskBuilder (GridLayout gridLayout) {
5255
task.monteCarloDraws = DEFAULT_MONTE_CARLO_DRAWS;
5356
// By default, traverse one block in a round predictable number of seconds.
5457
task.walkSpeed = gridLayout.streetGridSpacingMeters / gridLayout.walkBlockTraversalTimeSeconds;
58+
// Unlike single point tasks, travel time recording must be enabled manually on regional tasks.
59+
task.recordTimes = true;
5560
// Record more detailed information to allow comparison to theoretical travel time distributions.
5661
task.recordTravelTimeHistograms = true;
57-
// Set the destination grid extents on the task, otherwise if no freeform PointSet is specified, the task will fail
58-
// checks on the grid dimensions and zoom level.
62+
// Set the grid extents on the task, otherwise the task will fail checks on the grid dimensions and zoom level.
5963
WebMercatorExtents extents = WebMercatorExtents.forWgsEnvelope(gridLayout.gridEnvelope(), DEFAULT_ZOOM);
6064
task.zoom = extents.zoom;
6165
task.north = extents.north;
@@ -64,38 +68,38 @@ public GridSinglePointTaskBuilder (GridLayout gridLayout) {
6468
task.height = extents.height;
6569
}
6670

67-
public GridSinglePointTaskBuilder setOrigin (int gridX, int gridY) {
71+
public GridRegionalTaskBuilder setOrigin (int gridX, int gridY) {
6872
Coordinate origin = gridLayout.getIntersectionLatLon(gridX, gridY);
6973
task.fromLat = origin.y;
7074
task.fromLon = origin.x;
7175
return this;
7276
}
7377

74-
public GridSinglePointTaskBuilder weekdayMorningPeak () {
78+
public GridRegionalTaskBuilder weekdayMorningPeak () {
7579
task.date = WEEKDAY_DATE;
7680
morningPeak();
7781
return this;
7882
}
7983

80-
public GridSinglePointTaskBuilder weekendMorningPeak () {
84+
public GridRegionalTaskBuilder weekendMorningPeak () {
8185
task.date = WEEKEND_DATE;
8286
morningPeak();
8387
return this;
8488
}
8589

86-
public GridSinglePointTaskBuilder morningPeak () {
90+
public GridRegionalTaskBuilder morningPeak () {
8791
task.fromTime = LocalTime.of(7, 00).toSecondOfDay();
8892
task.toTime = LocalTime.of(9, 00).toSecondOfDay();
8993
return this;
9094
}
9195

92-
public GridSinglePointTaskBuilder departureTimeWindow(int startHour, int startMinute, int durationMinutes) {
96+
public GridRegionalTaskBuilder departureTimeWindow(int startHour, int startMinute, int durationMinutes) {
9397
task.fromTime = LocalTime.of(startHour, startMinute).toSecondOfDay();
9498
task.toTime = LocalTime.of(startHour, startMinute + durationMinutes).toSecondOfDay();
9599
return this;
96100
}
97101

98-
public GridSinglePointTaskBuilder maxRides(int rides) {
102+
public GridRegionalTaskBuilder maxRides(int rides) {
99103
task.maxRides = rides;
100104
return this;
101105
}
@@ -105,7 +109,7 @@ public GridSinglePointTaskBuilder maxRides(int rides) {
105109
* Increasing the number of draws will yield a better approximation of the true travel time distribution
106110
* (while making the tests run slower).
107111
*/
108-
public GridSinglePointTaskBuilder monteCarloDraws (int draws) {
112+
public GridRegionalTaskBuilder monteCarloDraws (int draws) {
109113
task.monteCarloDraws = draws;
110114
return this;
111115
}
@@ -120,7 +124,7 @@ public GridSinglePointTaskBuilder monteCarloDraws (int draws) {
120124
* web Mercator grid pixels. Using a single measurement point also greatly reduces the amount of travel time
121125
* histograms that must be computed and retained, improving the memory and run time cost of tests.
122126
*/
123-
public GridSinglePointTaskBuilder singleFreeformDestination(int x, int y) {
127+
public GridRegionalTaskBuilder singleFreeformDestination(int x, int y) {
124128
FreeFormPointSet ps = new FreeFormPointSet(gridLayout.getIntersectionLatLon(x, y));
125129
// Downstream code expects to see the same number of keys and PointSet objects so initialize both.
126130
task.destinationPointSetKeys = new String[] { "POINT_SET" };

src/test/java/com/conveyal/r5/analyst/network/SimpsonDesertTests.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
* version to the next of R5 (a form of snapshot testing) this checks that they match theoretically expected travel
2424
* times given headways, transfer points, distances, common trunks and competing lines, etc.
2525
*
26-
* Originally we were using web Mercator gridded destinations, as this was the only option in single point analyses.
26+
* Originally we were using web Mercator gridded destinations, as this was the only option in single point tasks.
2727
* Because these tests record travel time distributions at the destinations using a large number of Monte Carlo draws,
2828
* this was doing a lot of work and storing a lot of data for up to thousands of destinations we weren't actually using.
29-
* A testing code path now exists to measure travel times to one or more freeform destinations in single point mode.
30-
* However, it is still possible to measure times to the whole grid if singleFreeformDestination is not called.
29+
* Regional tasks with freeform destinations are now used to measure travel times to a limited number of points.
30+
* This could be extended to use gridded destination PointSets for future tests.
3131
*/
3232
public class SimpsonDesertTests {
3333

@@ -64,8 +64,7 @@ public void testGridScheduled () throws Exception {
6464
// always 10 minutes. So scheduled range is expected to be 1 minute slack, 0-20 minutes wait, 10 minutes ride,
6565
// 10 minutes wait, 10 minutes ride, giving 31 to 51 minutes.
6666
// This estimation logic could be better codified as something like TravelTimeEstimate.waitWithHeadaway(20) etc.
67-
68-
// TODO For some reason observed is off by 1 minute, figure out why.
67+
// TODO: For some reason observed is dealyed by 1 minute. Figure out why, perhaps due to integer minute binning.
6968
Distribution expected = new Distribution(31, 20).delay(1);
7069
expected.multiAssertSimilar(oneOriginResult.travelTimes, 0);
7170
}
@@ -179,7 +178,7 @@ public void testOvertakingCases () throws Exception {
179178
TransportNetwork network = gridLayout.generateNetwork();
180179

181180
// 0. Reuse this task builder to produce several tasks. See caveats on build() method.
182-
GridSinglePointTaskBuilder taskBuilder = gridLayout.newTaskBuilder()
181+
GridRegionalTaskBuilder taskBuilder = gridLayout.newTaskBuilder()
183182
.departureTimeWindow(7, 0, 5)
184183
.maxRides(1)
185184
.setOrigin(30, 50)

0 commit comments

Comments
 (0)