Skip to content

Commit

Permalink
Handle thresholds getRegionalResults and getSingleCutoffGrid
Browse files Browse the repository at this point in the history
Handle `threshold` taken as the query parameter and when to check for `cutoffsMinutes` vs `dualAccessibilityThresholds`.
  • Loading branch information
trevorgerhardt committed Mar 4, 2025
1 parent 44cc656 commit 8e5586d
Showing 1 changed file with 29 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -192,28 +192,31 @@ private record HumanKey(FileStorageKey storageKey, String humanName) { };
private HumanKey getSingleCutoffGrid (
RegionalAnalysis analysis,
OpportunityDataset destinations,
int cutoffMinutes,
int threshold,
int percentile,
FileStorageFormat fileFormat
) throws IOException {
final String regionalAnalysisId = analysis._id;
final String destinationPointSetId = destinations._id;
// Selecting the zeroth cutoff still makes sense for older analyses that don't allow an array of N cutoffs.
int cutoffIndex = 0;
if (analysis.cutoffsMinutes != null) {
cutoffIndex = new TIntArrayList(analysis.cutoffsMinutes).indexOf(cutoffMinutes);
checkState(cutoffIndex >= 0);
int thresholdIndex = 0;
if (analysis.request.includeTemporalDensity) {
thresholdIndex = new TIntArrayList(analysis.request.dualAccessibilityThresholds).indexOf(threshold);
checkState(thresholdIndex >= 0);
} else if (analysis.cutoffsMinutes != null) {
thresholdIndex = new TIntArrayList(analysis.cutoffsMinutes).indexOf(threshold);
checkState(thresholdIndex >= 0);
}
LOG.info(
"Returning {} minute accessibility to pointset {} (percentile {}) for regional analysis {} in format {}.",
cutoffMinutes, destinationPointSetId, percentile, regionalAnalysisId, fileFormat
threshold, destinationPointSetId, percentile, regionalAnalysisId, fileFormat
);
// Analysis grids now have the percentile and cutoff in their S3 key, because there can be many of each.
// We do this even for results generated by older workers, so they will be re-extracted with the new name.
// These grids are reasonably small, we may be able to just send all cutoffs to the UI instead of selecting.
String singleCutoffKey = String.format(
"%s_%s_P%d_C%d.%s",
regionalAnalysisId, destinationPointSetId, percentile, cutoffMinutes,
regionalAnalysisId, destinationPointSetId, percentile, threshold,
fileFormat.extension.toLowerCase(Locale.ROOT)
);
FileStorageKey singleCutoffFileStorageKey = new FileStorageKey(RESULTS, singleCutoffKey);
Expand Down Expand Up @@ -247,7 +250,7 @@ private HumanKey getSingleCutoffGrid (
LOG.debug("Single-cutoff grid {} not found on S3, deriving it from {}.", singleCutoffKey, multiCutoffKey);

InputStream multiCutoffInputStream = new FileInputStream(fileStorage.getFile(multiCutoffFileStorageKey));
Grid grid = new SelectingGridReducer(cutoffIndex).compute(multiCutoffInputStream);
Grid grid = new SelectingGridReducer(thresholdIndex).compute(multiCutoffInputStream);

File localFile = FileUtils.createScratchFile(fileFormat.toString());
FileOutputStream fos = new FileOutputStream(localFile);
Expand All @@ -270,7 +273,7 @@ private HumanKey getSingleCutoffGrid (
String analysisHumanName = humanNameForEntity(analysis);
String destinationHumanName = humanNameForEntity(destinations);
String resultHumanFilename = filenameCleanString(
String.format("%s_%s_P%d_C%d", analysisHumanName, destinationHumanName, percentile, cutoffMinutes)
String.format("%s_%s_P%d_C%d", analysisHumanName, destinationHumanName, percentile, threshold)
) + "." + fileFormat.extension.toLowerCase(Locale.ROOT);
// Note that the returned human filename already contains the appropriate extension.
return new HumanKey(singleCutoffFileStorageKey, resultHumanFilename);
Expand Down Expand Up @@ -405,17 +408,26 @@ private UrlWithHumanName getRegionalResults (Request req, Response res) throws I
// For newer analyses that have multiple cutoffs, percentiles, or destination pointsets, these initial values
// are coming from deprecated fields, are not meaningful and will be overwritten below from query parameters.
int percentile = analysis.travelTimePercentile;
int cutoffMinutes = analysis.cutoffMinutes;
int threshold = analysis.cutoffMinutes;
String destinationPointSetId = analysis.grid;

// Handle newer regional analyses with multiple cutoffs in an array.
// If a query parameter is supplied, range check it, otherwise use the middle value in the list.
// The cutoff variable holds the actual cutoff in minutes, not the position in the array of cutoffs.
if (analysis.cutoffsMinutes != null) {
if (analysis.request.includeTemporalDensity) {
int nThresholds = analysis.request.dualAccessibilityThresholds.length;
int[] thresholds = analysis.request.dualAccessibilityThresholds;
checkState(nThresholds > 0, "Regional analysis has no thresholds.");
threshold = getIntQueryParameter(req, "threshold", thresholds[nThresholds / 2]);
checkArgument(new TIntArrayList(thresholds).contains(threshold),
"Dual accessibility thresholds for this regional analysis must be taken from this list: (%s)",
Ints.join(", ", thresholds)
);
} else if (analysis.cutoffsMinutes != null) {
// Handle newer regional analyses with multiple cutoffs in an array.
// If a query parameter is supplied, range check it, otherwise use the middle value in the list.
// The cutoff variable holds the actual cutoff in minutes, not the position in the array of cutoffs.
int nCutoffs = analysis.cutoffsMinutes.length;
checkState(nCutoffs > 0, "Regional analysis has no cutoffs.");
cutoffMinutes = getIntQueryParameter(req, "cutoff", analysis.cutoffsMinutes[nCutoffs / 2]);
checkArgument(new TIntArrayList(analysis.cutoffsMinutes).contains(cutoffMinutes),
threshold = getIntQueryParameter(req, "cutoff", analysis.cutoffsMinutes[nCutoffs / 2]);
checkArgument(new TIntArrayList(analysis.cutoffsMinutes).contains(threshold),
"Travel time cutoff for this regional analysis must be taken from this list: (%s)",
Ints.join(", ", analysis.cutoffsMinutes)
);
Expand Down Expand Up @@ -452,7 +464,7 @@ private UrlWithHumanName getRegionalResults (Request req, Response res) throws I
}
// Significant overhead here: UI contacts backend, backend calls S3, backend responds to UI, UI contacts S3.
OpportunityDataset destinations = getDestinations(destinationPointSetId, userPermissions);
HumanKey gridKey = getSingleCutoffGrid(analysis, destinations, cutoffMinutes, percentile, format);
HumanKey gridKey = getSingleCutoffGrid(analysis, destinations, threshold, percentile, format);
res.type(APPLICATION_JSON.asString());
return fileStorage.getJsonUrl(gridKey.storageKey, gridKey.humanName);
}
Expand Down

0 comments on commit 8e5586d

Please sign in to comment.