From 92056d60e5698303b5dc44c2a1bf985fc5199145 Mon Sep 17 00:00:00 2001 From: Peter Powers <pmpowers@usgs.gov> Date: Thu, 11 Feb 2021 09:37:20 -0700 Subject: [PATCH] controller/service updates; truncation and maxdir --- .../earthquake/nshmp/www/Application.java | 5 +- .../nshmp/www/DeaggEpsilonController.java | 2 +- .../nshmp/www/HazardController.java | 104 ++++---------- .../www/services/DeaggEpsilonService.java | 2 +- .../nshmp/www/services/HazardService.java | 136 +++++++++++------- 5 files changed, 114 insertions(+), 135 deletions(-) diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/Application.java b/src/main/java/gov/usgs/earthquake/nshmp/www/Application.java index 2e03a7758..4c63d414b 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/Application.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/Application.java @@ -6,9 +6,8 @@ import io.swagger.v3.oas.annotations.info.Info; @OpenAPIDefinition( info = @Info( - title = "NSHMP NSHM Services", - description = "### Services related to National Seismic Hazard Models:\n" + - "* Source Model: Get the metadata about current installed NSHM")) + title = "USGS NSHM Services", + description = "National Seismic Hazard Model (NSHM) hazard calculations and queries.")) public class Application { public static void main(String[] args) { diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/DeaggEpsilonController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/DeaggEpsilonController.java index 478f8087a..592309fb3 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/DeaggEpsilonController.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/DeaggEpsilonController.java @@ -33,7 +33,7 @@ public class DeaggEpsilonController { @Get(uri = "/usage", produces = MediaType.APPLICATION_JSON) public HttpResponse<String> doGetUsage(HttpRequest<?> request) { var urlHelper = servlet.urlHelper(request); - return HazardService.handleDoGetUsage(urlHelper); + return HazardService.handleDoGetMetadata(urlHelper); } /** diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java index d678f3a50..d6d381b18 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java @@ -1,7 +1,5 @@ package gov.usgs.earthquake.nshmp.www; -import java.util.Optional; - import javax.annotation.Nullable; import javax.inject.Inject; @@ -11,7 +9,6 @@ import gov.usgs.earthquake.nshmp.www.services.HazardService.QueryParameters; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; -import io.micronaut.http.MediaType; import io.micronaut.http.annotation.Controller; import io.micronaut.http.annotation.Get; import io.micronaut.http.annotation.PathVariable; @@ -25,104 +22,59 @@ import io.swagger.v3.oas.annotations.tags.Tag; * Micronaut controller for probabilistic seismic hazard calculations. * * @see HazardService - * * @author U.S. Geological Survey */ @Tag( - name = "Hazard Service", - description = "USGS NSHMP Hazard Curve Calculator") + name = "Hazard", + description = "USGS NSHMP hazard calculation service") @Controller("/hazard") public class HazardController { @Inject private NshmpMicronautServlet servlet; - /** - * GET method to return the hazard usage. - * - * @param request The HTTP request - */ @Operation( - summary = "Returns the hazard service usage", - description = "Returns the installed model and supported:\n" + - "* Region bounds\n * Return periods\n * Vs30s", - operationId = "hazard_doGetUsage") + summary = "Hazard model and service metadata", + description = "Returns details of the installed model and service request parameters", + operationId = "hazard_doGetMetadata") @ApiResponse( - description = "Hazard usage", + description = "Hazard service metadata", responseCode = "200") - @Get(uri = "/usage", produces = MediaType.APPLICATION_JSON) - public HttpResponse<String> doGetUsage(HttpRequest<?> request) { + @Get + public HttpResponse<String> doGetMetadata(HttpRequest<?> request) { var urlHelper = servlet.urlHelper(request); - return HazardService.handleDoGetUsage(urlHelper); + return HazardService.handleDoGetMetadata(urlHelper); } /** - * GET method to return usage or hazard curves, query based. - * - * @param request The HTTP request - * @param longitude Longitude (in decimal degrees) ([-360, 360]) - * @param latitude Latitude (in decimal degrees) ([-90, 90]) - * @param vs30 The site soil class + * @param longitude Longitude in decimal degrees [-360..360] + * @param latitude Latitude in decimal degrees [-90..90] + * @param vs30 Site Vs30 value in m/s [150..3000] + * @param truncate Truncate curves at return periods below ~10,000 years + * @param maxdir Apply max-direction scaling */ @Operation( - summary = "Compute hazards", - description = "Compute hazard curves given longitude, latitude, and Vs30", + summary = "Compute probabilisitic hazard at a site", + description = "Returns hazard curves computed from the installed model", operationId = "hazard_doGetHazard") @ApiResponse( description = "Hazard curves", responseCode = "200") - @Get // (uri = "{?longitude,latitude,vs30}") + @Get(uri = "/{longitude}/{latitude}/{vs30}{?truncate,maxdir}") public HttpResponse<String> doGetHazard( HttpRequest<?> request, - @Schema( - required = true, - minimum = "-360", - maximum = "360") @QueryValue @Nullable Optional<Double> longitude, - @Schema( - required = true, - minimum = "-90", - maximum = "90") @QueryValue @Nullable Optional<Double> latitude, - @Schema( - required = true) @QueryValue @Nullable Optional<Integer> vs30) { + @Schema(minimum = "-360", maximum = "360") @PathVariable double longitude, + @Schema(minimum = "-90", maximum = "90") @PathVariable double latitude, + @Schema(minimum = "150", maximum = "3000") @PathVariable int vs30, + @QueryValue(defaultValue = "false") @Nullable boolean truncate, + @QueryValue(defaultValue = "false") @Nullable boolean maxdir) { - var query = new QueryParameters(longitude, latitude, vs30); - return HazardService.handleDoGetHazard( - query, - servlet.urlHelper(request)); - } - - /** - * GET method to return usage or hazard curves, slash based. - * - * @param request The HTTP request - * @param longitude Longitude (in decimal degrees) ([-360, 360]) - * @param latitude Latitude (in decimal degrees) ([-90, 90]) - * @param vs30 The site soil class - */ - @Operation( - summary = "Compute hazards", - description = "Compute hazard curves given longitude, latitude, and Vs30", - operationId = "hazard_doGetHazardSlash") - @ApiResponse( - description = "Hazard curves", - responseCode = "200") - @Get(uri = "{/longitude}{/latitude}{/vs30}") - public HttpResponse<String> doGetHazardSlash( - HttpRequest<?> request, - @Schema( - required = true, - minimum = "-360", - maximum = "360") @PathVariable @Nullable Optional<Double> longitude, - @Schema( - required = true, - minimum = "-90", - maximum = "90") @PathVariable @Nullable Optional<Double> latitude, - @Schema( - required = true) @PathVariable @Nullable Optional<Integer> vs30) { + /* + * @Schema annotation parameter constraints only affect Swagger service + * index page behavior; still need to validate against model. TODO + */ - var query = new QueryParameters(longitude, latitude, vs30); - return HazardService.handleDoGetHazard( - query, - servlet.urlHelper(request)); + var query = new QueryParameters(longitude, latitude, vs30, truncate, maxdir); + return HazardService.handleDoGetHazard(query, servlet.urlHelper(request)); } } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/DeaggEpsilonService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/DeaggEpsilonService.java index 02cb1f53b..a2b851e62 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/DeaggEpsilonService.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/DeaggEpsilonService.java @@ -54,7 +54,7 @@ public final class DeaggEpsilonService { var timer = ServletUtil.timer(); if (query.isNull()) { - return HazardService.handleDoGetUsage(urlHelper); + return HazardService.handleDoGetMetadata(urlHelper); } query.checkValues(); diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java index 8c40a32ca..8405bbdc7 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java @@ -1,14 +1,13 @@ package gov.usgs.earthquake.nshmp.www.services; -import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static gov.usgs.earthquake.nshmp.calc.HazardExport.curvesBySource; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.function.Function; @@ -46,22 +45,10 @@ import io.micronaut.http.HttpResponse; */ public final class HazardService { - /* - * Developer notes: - * - * Calculations are designed to leverage all available processors by default, - * distributing work using the ServletUtil.CALC_EXECUTOR. - */ - private static final String NAME = "Hazard Service"; - /** - * Handler for {@link HazardController#doGetUsage}. Returns the usage for the - * hazard service. - * - * @param urlHelper The URL helper - */ - public static HttpResponse<String> handleDoGetUsage(UrlHelper urlHelper) { + /** HazardController.doGetUsage() handler. */ + public static HttpResponse<String> handleDoGetMetadata(UrlHelper urlHelper) { try { var usage = new RequestMetadata(ServletUtil.model());// SourceServices.ResponseData(); var response = new Response<>(Status.USAGE, NAME, urlHelper.url, usage, urlHelper); @@ -72,22 +59,17 @@ public final class HazardService { } } - /** - * Handler for {@link HazardController#doGetHazard}. Returns the usage or the - * hazard result. - * - * @param query The query - * @param urlHelper The URL helper - */ + /** HazardController.doGetHazard() handler. */ public static HttpResponse<String> handleDoGetHazard( QueryParameters query, UrlHelper urlHelper) { try { - if (query.isEmpty()) { - return handleDoGetUsage(urlHelper); - } - query.checkParameters(); + // TODO still need to validate + // if (query.isEmpty()) { + // return handleDoGetUsage(urlHelper); + // } + // query.checkParameters(); var data = new RequestData(query); var response = process(data, urlHelper); var svcResponse = ServletUtil.GSON.toJson(response); @@ -142,35 +124,37 @@ public final class HazardService { public static class QueryParameters { - Optional<Double> longitude; - Optional<Double> latitude; - Optional<Integer> vs30; + final double longitude; + final double latitude; + final int vs30; + final boolean truncate; + final boolean maxdir; public QueryParameters( - Optional<Double> longitude, - Optional<Double> latitude, - Optional<Integer> vs30) { + double longitude, + double latitude, + int vs30, + boolean truncate, + boolean maxdir) { this.longitude = longitude; this.latitude = latitude; this.vs30 = vs30; + this.truncate = truncate; + this.maxdir = maxdir; } - public boolean isEmpty() { - return longitude.isEmpty() && latitude.isEmpty() && vs30.isEmpty(); - } - - public void checkParameters() { - checkParameter(longitude, "longitude"); - checkParameter(latitude, "latitude"); - checkParameter(vs30, "vs30"); - } + // void checkParameters() { + // checkParameter(longitude, "longitude"); + // checkParameter(latitude, "latitude"); + // checkParameter(vs30, "vs30"); + // } } - private static void checkParameter(Object param, String id) { - checkNotNull(param, "Missing parameter: %s", id); - // TODO check range here - } + // private static void checkParameter(Object param, String id) { + // checkNotNull(param, "Missing parameter: %s", id); + // // TODO check range here + // } /* Service request and model metadata */ static class RequestMetadata { @@ -208,11 +192,15 @@ public final class HazardService { final double longitude; final double latitude; final double vs30; + final boolean truncate; + final boolean maxdir; RequestData(QueryParameters query) { - this.longitude = query.longitude.orElseThrow(); - this.latitude = query.latitude.orElseThrow(); - this.vs30 = query.vs30.orElseThrow(); + this.longitude = query.longitude; + this.latitude = query.latitude; + this.vs30 = query.vs30; + this.truncate = query.truncate; + this.maxdir = query.maxdir; } } @@ -271,9 +259,7 @@ public final class HazardService { Curve(String component, XySequence values) { this.component = component; - this.values = XySequence.create( - values.xValues().map(Math::exp).toArray(), - values.yValues().toArray()); + this.values = values; } } @@ -341,12 +327,16 @@ public final class HazardService { var curves = new ArrayList<Curve>(); // total curve - curves.add(new Curve(TOTAL_KEY, totalMap.get(imt))); + curves.add(new Curve( + TOTAL_KEY, + updateCurve(request, totalMap.get(imt), imt))); // component curves var typeMap = componentMaps.get(imt); for (SourceType type : typeMap.keySet()) { - curves.add(new Curve(type.toString(), typeMap.get(type))); + curves.add(new Curve( + type.toString(), + updateCurve(request, typeMap.get(type), imt))); } hazards.add(new HazardResponse(imt, List.copyOf(curves))); @@ -359,6 +349,44 @@ public final class HazardService { } } + private static final double TRUNCATION_LIMIT = 1e-4; + + /* Convert to linear and possibly truncate and scale to max-direction. */ + private static XySequence updateCurve( + RequestData request, + XySequence curve, + Imt imt) { + + /* + * If entire curve is <1e-4, this method will return a curve consisting of + * just the first point in the supplied curve. + * + * TODO We probably want to move the TRUNCATION_LIMIT out to a config. + */ + + double[] yValues = curve.yValues().toArray(); + int limit = request.truncate ? truncationLimit(yValues) : yValues.length; + yValues = Arrays.copyOf(yValues, limit); + + double scale = request.maxdir ? MaxDirection.FACTORS.get(imt) : 1.0; + double[] xValues = curve.xValues() + .limit(yValues.length) + .map(Math::exp) + .map(x -> x * scale) + .toArray(); + + return XySequence.create(xValues, yValues); + } + + private static int truncationLimit(double[] yValues) { + int limit = 1; + double y = yValues[0]; + while (y >= TRUNCATION_LIMIT && limit < yValues.length) { + y = yValues[limit++]; + } + return limit; + } + @Deprecated public static class Query extends ServiceQueryData { Integer vs30; -- GitLab