Skip to content

Commit d4e60d3

Browse files
ansoncfitabyrdtrevorgerhardt
authored
Make zips of dual access grids (#989)
* draft for making zips of dual access grids * Fix: select dual access thresholds when generating single cutoff grids * eliminate tolerance of missing threshold parameter factor out repeated code blocks in query parameter validation * clean up and reduce repetition in validation code - factor out checks against arrays of valid values - use Arrays.toString, List.of, Ints.contains, static imports --------- Co-authored-by: Andrew Byrd <[email protected]> Co-authored-by: Trevor Gerhardt <[email protected]>
1 parent 382cffd commit d4e60d3

File tree

1 file changed

+51
-55
lines changed

1 file changed

+51
-55
lines changed

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

Lines changed: 51 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@
6060
import static com.conveyal.analysis.util.JsonUtil.toJson;
6161
import static com.conveyal.file.FileCategory.BUNDLES;
6262
import static com.conveyal.file.FileCategory.RESULTS;
63+
import static com.conveyal.file.FileStorageFormat.GEOTIFF;
64+
import static com.conveyal.file.FileStorageFormat.GRID;
65+
import static com.conveyal.file.FileStorageFormat.PNG;
6366
import static com.conveyal.file.UrlWithHumanName.filenameCleanString;
6467
import static com.conveyal.r5.transit.TransportNetworkCache.getScenarioFilename;
6568
import static com.google.common.base.Preconditions.checkArgument;
@@ -161,10 +164,10 @@ private RegionalAnalysis deleteRegionalAnalysis (Request req, Response res) {
161164
return analysis;
162165
}
163166

164-
private int getIntQueryParameter (Request req, String parameterName, int defaultValue) {
167+
private int getIntQueryParameter (Request req, String parameterName) {
165168
String paramValue = req.queryParams(parameterName);
166169
if (paramValue == null) {
167-
return defaultValue;
170+
throw new IllegalArgumentException("Must provide query parameter " + parameterName);
168171
}
169172
try {
170173
return Integer.parseInt(paramValue);
@@ -302,13 +305,13 @@ private Object getAllRegionalResults (Request req, Response res) throws IOExcept
302305
progressListener.beginTask("Creating and archiving geotiffs...", nSteps);
303306
// Iterate over all dest, cutoff, percentile combinations and generate one geotiff for each combination.
304307
List<HumanKey> humanKeys = new ArrayList<>();
308+
GridResultType gridResultType = determineGridResultType(analysis);
305309
for (String destinationPointSetId : analysis.destinationPointSetIds) {
306310
OpportunityDataset destinations = getDestinations(destinationPointSetId, userPermissions);
307-
// TODO handle dual access
308-
for (int cutoffMinutes : analysis.cutoffsMinutes) {
311+
for (int threshold : getValidThresholds(analysis)) {
309312
for (int percentile : analysis.travelTimePercentiles) {
310313
HumanKey gridKey = getSingleCutoffGrid(
311-
analysis, destinations, cutoffMinutes, percentile, GridResultType.ACCESS, FileStorageFormat.GEOTIFF
314+
analysis, destinations, threshold, percentile, gridResultType, GEOTIFF
312315
);
313316
humanKeys.add(gridKey);
314317
progressListener.increment();
@@ -384,59 +387,17 @@ private UrlWithHumanName getRegionalResults (Request req, Response res) throws I
384387
// expected to have no gridded results and cleanly return a 404?
385388
final String regionalAnalysisId = req.params("_id");
386389
FileStorageFormat format = FileStorageFormat.valueOf(req.params("format").toUpperCase());
387-
if (!FileStorageFormat.GRID.equals(format) && !FileStorageFormat.PNG.equals(format) && !FileStorageFormat.GEOTIFF.equals(format)) {
388-
throw AnalysisServerException.badRequest("Format \"" + format + "\" is invalid. Request format must be \"grid\", \"png\", or \"geotiff\".");
390+
if (!List.of(GRID, PNG, GEOTIFF).contains(format)) {
391+
throw AnalysisServerException.badRequest("Parameter 'format' must be one of [grid, png, geotiff].");
389392
}
390393
final UserPermissions userPermissions = UserPermissions.from(req);
391394
RegionalAnalysis analysis = getAnalysis(regionalAnalysisId, userPermissions);
392-
393-
// TODO handle a regional analysis that includes both regular accessibility and dual access results.
394-
GridResultType gridResultType = analysis.request.includeTemporalDensity ? GridResultType.DUAL_ACCESS : GridResultType.ACCESS;
395-
396-
// If a query parameter is supplied, range check it, otherwise use the middle value in the list.
397-
int threshold;
398-
if (gridResultType.equals(GridResultType.DUAL_ACCESS)) {
399-
int nThresholds = analysis.request.dualAccessThresholds.length;
400-
int[] thresholds = analysis.request.dualAccessThresholds;
401-
checkState(nThresholds > 0, "Regional analysis has no dual access thresholds.");
402-
threshold = getIntQueryParameter(req, "threshold", thresholds[nThresholds / 2]);
403-
checkArgument(new TIntArrayList(thresholds).contains(threshold),
404-
"Dual access thresholds for this regional analysis must be taken from this list: (%s)",
405-
Ints.join(", ", thresholds)
406-
);
407-
} else {
408-
// Handle newer regional analyses with multiple cutoffs in an array.
409-
// The cutoff variable holds the actual cutoff in minutes, not the position in the array of cutoffs.
410-
checkState(analysis.cutoffsMinutes != null, "Regional analysis has no cutoffs.");
411-
int nCutoffs = analysis.cutoffsMinutes.length;
412-
checkState(nCutoffs > 0, "Regional analysis has no cutoffs.");
413-
threshold = getIntQueryParameter(req, "threshold", analysis.cutoffsMinutes[nCutoffs / 2]);
414-
checkArgument(new TIntArrayList(analysis.cutoffsMinutes).contains(threshold),
415-
"Travel time cutoff for this regional analysis must be taken from this list: (%s)",
416-
Ints.join(", ", analysis.cutoffsMinutes)
417-
);
418-
}
419-
420-
// If a query parameter is supplied, range check it, otherwise use the middle value in the list.
421-
// The percentile variable holds the actual percentile (25, 50, 95) not the position in the array.
422-
int nPercentiles = analysis.travelTimePercentiles.length;
423-
checkState(nPercentiles > 0, "Regional analysis has no percentiles.");
424-
int percentile = getIntQueryParameter(req, "percentile", analysis.travelTimePercentiles[nPercentiles / 2]);
425-
checkArgument(new TIntArrayList(analysis.travelTimePercentiles).contains(percentile),
426-
"Percentile for this regional analysis must be taken from this list: (%s)",
427-
Ints.join(", ", analysis.travelTimePercentiles));
428-
429-
// Handle regional analyses with multiple destination pointsets per analysis.
430-
int nGrids = analysis.destinationPointSetIds.length;
431-
checkState(nGrids > 0, "Regional analysis has no grids.");
432-
String destinationPointSetId = req.queryParams("destinationPointSetId");
433-
if (destinationPointSetId == null) {
434-
destinationPointSetId = analysis.destinationPointSetIds[0];
435-
}
436-
checkArgument(Arrays.asList(analysis.destinationPointSetIds).contains(destinationPointSetId),
437-
"Destination gridId must be one of: %s",
438-
String.join(",", analysis.destinationPointSetIds));
439-
395+
GridResultType gridResultType = determineGridResultType(analysis);
396+
// The threshold parameter holds the value in minutes, not the position in the array of thresholds.
397+
int threshold = getAndValidateIntParameter(req, "threshold", getValidThresholds(analysis));
398+
int percentile = getAndValidateIntParameter(req, "percentile", analysis.travelTimePercentiles);
399+
String destinationPointSetId = getAndValidateStringParameter(
400+
req, "destinationPointSetId", analysis.destinationPointSetIds);
440401
// We started implementing the ability to retrieve and display partially completed analyses.
441402
// We eventually decided these should not be available here at the same endpoint as complete, immutable results.
442403
if (broker.findJob(regionalAnalysisId) != null) {
@@ -449,6 +410,41 @@ private UrlWithHumanName getRegionalResults (Request req, Response res) throws I
449410
return fileStorage.getJsonUrl(gridKey.storageKey, gridKey.humanName);
450411
}
451412

413+
private int[] getValidThresholds (RegionalAnalysis analysis) {
414+
return switch (determineGridResultType(analysis)) {
415+
case ACCESS -> analysis.cutoffsMinutes;
416+
case DUAL_ACCESS -> analysis.request.dualAccessThresholds;
417+
};
418+
}
419+
420+
// This assumes each set of regional analysis results has only primal or dual access, not both.
421+
// TODO handle regional analyses that include both regular accessibility and dual access results.
422+
private GridResultType determineGridResultType (RegionalAnalysis analysis) {
423+
return analysis.request.includeTemporalDensity ? GridResultType.DUAL_ACCESS : GridResultType.ACCESS;
424+
}
425+
426+
/// Get the value for a given query parameter name, check that it's non-null and can be parsed
427+
/// as an integer, and check that the value is present in an array of valid values.
428+
private int getAndValidateIntParameter (Request req, String parameterName, int[] allowedValues) {
429+
int value = getIntQueryParameter(req, parameterName);
430+
checkState(allowedValues != null && allowedValues.length > 0, "Lacking values for " + parameterName);
431+
checkArgument(Ints.contains(allowedValues, value), "Parameter '%s' must be one of: %s",
432+
parameterName, Arrays.toString(allowedValues));
433+
return value;
434+
}
435+
436+
/// Should behave identically to getAndValidateIntParameter, but for Strings.
437+
private String getAndValidateStringParameter (Request req, String parameterName, String[] allowedValues) {
438+
checkState(allowedValues != null && allowedValues.length > 0, "Lacking values for " + parameterName);
439+
String value = req.queryParams(parameterName);
440+
if (value == null || value.isEmpty()) {
441+
throw new IllegalArgumentException("Must provide query parameter " + parameterName);
442+
}
443+
checkArgument(List.of(allowedValues).contains(value), "Parameter '%s' must be one of: %s",
444+
parameterName, Arrays.toString(allowedValues));
445+
return value;
446+
}
447+
452448
private Object getCsvResults (Request req, Response res) {
453449
final String regionalAnalysisId = req.params("_id");
454450
final CsvResultType resultType = CsvResultType.valueOf(req.params("resultType").toUpperCase());

0 commit comments

Comments
 (0)