diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index 2a81e3a09e2c2fb9f3dfbaa7a03a4c847efc020d..af19505cd1bef520ab22beb648c802e286e8c3b5 100644 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -1,6 +1,8 @@ dependencies { + // NSHMP + // implementation files('../nshmp-lib/build/libs/nshmp-lib.jar') implementation "ghsc:nshmp-lib:${nshmpLibVersion}" implementation "ghsc:nshmp-ws-utils:${nshmpWsUtilsVersion}" diff --git a/settings.gradle b/settings.gradle index 90c2faad1fbd25d62f6b46e92c5d1c0eaf72b53d..0daffad9b55fb3fc714dee47c7498518e07b9de6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,6 +20,9 @@ git { fetch("https://code.usgs.gov/ghsc/nshmp/nshms/nshm-hawaii.git", { name "nshmp-haz-dep--nshm-hi-2021" tag "2.0.0" + // fetch("https://code.usgs.gov/ghsc/nshmp/nshms/nshm-conus.git", { + // name "nshmp-haz-dep--nshm-conus-2018" + // tag "main" }) } } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/DisaggEpsilon.java b/src/main/java/gov/usgs/earthquake/nshmp/DisaggEpsilon.java index d10e9eda757c0772d7979048479886afb6c1b511..e4d039601024e06a0ac5b2df0f01b3bf06aeaed1 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/DisaggEpsilon.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/DisaggEpsilon.java @@ -146,6 +146,7 @@ public class DisaggEpsilon { log.info("Spectra: " + imtImlMaps.size()); checkArgument(sites.size() == imtImlMaps.size(), "Sites and spectra lists different sizes"); + // Spectra should be checked against IMTs supported by model GMMs Path out = calc(model, config, sites, imtImlMaps, log); diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java deleted file mode 100644 index fdc74747815374a10fb5977cf022ceb16a05319a..0000000000000000000000000000000000000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java +++ /dev/null @@ -1,81 +0,0 @@ -package gov.usgs.earthquake.nshmp.www; - -import gov.usgs.earthquake.nshmp.www.services.HazardService; -import gov.usgs.earthquake.nshmp.www.services.HazardService.QueryParameters; - -import io.micronaut.http.HttpRequest; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.PathVariable; -import io.micronaut.http.annotation.QueryValue; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Inject; - -/** - * Micronaut controller for probabilistic seismic hazard calculations. - * - * @see HazardService - * @author U.S. Geological Survey - */ -@Tag( - name = "Hazard", - description = "USGS NSHMP hazard calculation service") -@Controller("/hazard") -public class HazardController { - - @Inject - private NshmpMicronautServlet servlet; - - @Operation( - summary = "Hazard model and service metadata", - description = "Returns details of the installed model and service request parameters", - operationId = "hazard_doGetMetadata") - @ApiResponse( - description = "Hazard service metadata", - responseCode = "200") - @Get - public HttpResponse<String> doGetMetadata(HttpRequest<?> request) { - return HazardService.handleDoGetMetadata(request); - } - - /** - * @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 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}{?truncate,maxdir}") - public HttpResponse<String> doGetHazard( - HttpRequest<?> request, - - @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") boolean truncate, - - @QueryValue(defaultValue = "false") boolean maxdir) { - - /* - * @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, truncate, maxdir); - return HazardService.handleDoGetHazard(request, query); - } -} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/ServicesUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java similarity index 70% rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/ServicesUtil.java rename to src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java index b2da3f0917178c67b8acb98f9da86f899e1ec652..3bdcab696e214e4d4940244a3ad557070552a54b 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/ServicesUtil.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java @@ -1,41 +1,19 @@ -package gov.usgs.earthquake.nshmp.www.services; +package gov.usgs.earthquake.nshmp.www; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Function; -import com.google.gson.GsonBuilder; - import gov.usgs.earthquake.nshmp.calc.CalcConfig; import gov.usgs.earthquake.nshmp.calc.Hazard; import gov.usgs.earthquake.nshmp.calc.HazardCalcs; import gov.usgs.earthquake.nshmp.calc.Site; import gov.usgs.earthquake.nshmp.model.HazardModel; -import gov.usgs.earthquake.nshmp.www.ResponseBody; -import gov.usgs.earthquake.nshmp.www.WsUtils; - -import io.micronaut.http.HttpResponse; public class ServicesUtil { - public static HttpResponse<String> handleError( - Throwable e, - String name, - String url) { - var msg = e.getMessage() + " (see logs)"; - var svcResponse = ResponseBody.error() - .name(name) - .url(url) - .request(url) - .response(msg) - .build(); - var gson = new GsonBuilder().setPrettyPrinting().create(); - var response = gson.toJson(svcResponse); - e.printStackTrace(); - return HttpResponse.serverError(response); - } - - static Hazard calcHazard( + @Deprecated + public static Hazard calcHazard( Function<HazardModel, CalcConfig> configFunction, Function<CalcConfig, Site> siteFunction) throws InterruptedException, ExecutionException { @@ -47,12 +25,12 @@ public class ServicesUtil { } @Deprecated - static class ServiceQueryData implements ServiceQuery { + public static class ServiceQueryData implements ServiceQuery { public final Double longitude; public final Double latitude; - ServiceQueryData(Double longitude, Double latitude) { + public ServiceQueryData(Double longitude, Double latitude) { this.longitude = longitude; this.latitude = latitude; } @@ -70,7 +48,7 @@ public class ServicesUtil { } @Deprecated - static class ServiceRequestData { + public static class ServiceRequestData { public final double longitude; public final double latitude; @@ -81,7 +59,7 @@ public class ServicesUtil { } } - enum Key { + public enum Key { EDITION, REGION, MODEL, @@ -114,6 +92,7 @@ public class ServicesUtil { void checkValues(); } + @Deprecated private static CompletableFuture<Hazard> calcHazard( HazardModel model, CalcConfig config, diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/ServletUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java similarity index 67% rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/ServletUtil.java rename to src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java index 81651afd7d44ac3d968008892b7e7d1dc03700eb..3e3b7425d8474fe6e6d111083ad8b550da182080 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/ServletUtil.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java @@ -1,4 +1,4 @@ -package gov.usgs.earthquake.nshmp.www.services; +package gov.usgs.earthquake.nshmp.www; import static java.lang.Runtime.getRuntime; @@ -14,6 +14,10 @@ import java.util.HashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.Gson; @@ -27,12 +31,12 @@ import gov.usgs.earthquake.nshmp.calc.Site; import gov.usgs.earthquake.nshmp.calc.ValueFormat; import gov.usgs.earthquake.nshmp.gmm.Imt; import gov.usgs.earthquake.nshmp.model.HazardModel; -import gov.usgs.earthquake.nshmp.www.WsUtils; import gov.usgs.earthquake.nshmp.www.meta.MetaUtil; import io.micronaut.context.annotation.Value; import io.micronaut.context.event.ShutdownEvent; import io.micronaut.context.event.StartupEvent; +import io.micronaut.http.HttpResponse; import io.micronaut.runtime.event.annotation.EventListener; import jakarta.inject.Singleton; @@ -45,11 +49,14 @@ import jakarta.inject.Singleton; public class ServletUtil { public static final Gson GSON; + public static final Gson GSON2; + + public static final ListeningExecutorService CALC_EXECUTOR; + public static final ExecutorService TASK_EXECUTOR; - static final ListeningExecutorService CALC_EXECUTOR; - static final ExecutorService TASK_EXECUTOR; + public static final int THREAD_COUNT; - static final int THREAD_COUNT; + private static final Logger LOGGER = LoggerFactory.getLogger(ServletUtil.class); @Value("${nshmp-haz.model-path}") private Path modelPath; @@ -71,9 +78,20 @@ public class ServletUtil { .serializeNulls() .setPrettyPrinting() .create(); + + // removed old IMT and ValueFormat enum serialization + GSON2 = new GsonBuilder() + .registerTypeAdapter(Double.class, new WsUtils.DoubleSerializer()) + .registerTypeAdapter(Site.class, new MetaUtil.SiteSerializer()) + .registerTypeHierarchyAdapter(Path.class, new PathConverter()) + .disableHtmlEscaping() + .serializeNulls() + .setPrettyPrinting() + .create(); + } - static HazardModel model() { + public static HazardModel model() { return HAZARD_MODEL; } @@ -141,4 +159,47 @@ public class ServletUtil { } } + public static HttpResponse<String> error( + Logger logger, + Throwable e, + String name, + String url) { + var msg = e.getMessage() + " (see logs)"; + var svcResponse = ResponseBody.error() + .name(name) + .url(url) + .request(url) + .response(msg) + .build(); + var response = GSON2.toJson(svcResponse); + logger.error("Servlet error", e); + return HttpResponse.serverError(response); + } + + public static String imtShortLabel(Imt imt) { + if (imt.equals(Imt.PGA) || imt.equals(Imt.PGV)) { + return imt.name(); + } else if (imt.isSA()) { + return imt.period() + " s"; + } + return imt.toString(); + } + + public static Object serverData(int threads, Stopwatch timer) { + return new Server(threads, timer); + } + + private static class Server { + + final int threads; + final String timer; + final String version; + + Server(int threads, Stopwatch timer) { + this.threads = threads; + this.timer = timer.toString(); + this.version = "TODO where to get version?"; + } + } + } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java index c2251caba5d3ed3612ed43e1aa8ca14404f7f381..dc4d6fb3cdf414424b09634b25c7d34bdd67f97c 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java @@ -3,9 +3,9 @@ package gov.usgs.earthquake.nshmp.www; import java.nio.charset.StandardCharsets; import java.util.stream.Collectors; -import com.google.common.io.Resources; +import org.slf4j.LoggerFactory; -import gov.usgs.earthquake.nshmp.www.services.ServicesUtil; +import com.google.common.io.Resources; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; @@ -40,7 +40,9 @@ public class SwaggerController { .collect(Collectors.joining("\n")); return HttpResponse.ok(yml); } catch (Exception e) { - return ServicesUtil.handleError(e, "Swagger", request.getUri().getPath()); + return ServletUtil.error( + LoggerFactory.getLogger("Swagger"), + e, "Swagger", request.getUri().toString()); } } } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java new file mode 100644 index 0000000000000000000000000000000000000000..680d946ad73c54669b62afa1ebf762f3b5810331 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java @@ -0,0 +1,156 @@ +package gov.usgs.earthquake.nshmp.www.hazard; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.Map; +import java.util.Set; + +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet; +import gov.usgs.earthquake.nshmp.www.ServletUtil; + +import io.micronaut.core.annotation.Nullable; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.PathVariable; +import io.micronaut.http.annotation.QueryValue; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; + +/** + * Micronaut web service controller for disaggregation of probabilistic seismic + * hazard. + * + * @author U.S. Geological Survey + */ +@Tag( + name = "Disaggregation", + description = "USGS NSHMP hazard disaggregation service") +@Controller("/disagg") +public class DisaggController { + + @Inject + private NshmpMicronautServlet servlet; + + @Operation( + summary = "Disaggregation model and service metadata", + description = "Returns details of the installed model and service request parameters") + @ApiResponse( + description = "Disaggregation service metadata", + responseCode = "200") + @Get + public HttpResponse<String> doGetMetadata(HttpRequest<?> http) { + try { + return DisaggService.getMetadata(http); + } catch (Exception e) { + return ServletUtil.error( + DisaggService.LOG, e, + DisaggService.NAME, + http.getUri().toString()); + } + } + + /** + * @param longitude Longitude in the range [-360..360]°. + * @param latitude Latitude in the range [-90..90]°. + * @param vs30 Site Vs30 value in the range [150..3000] m/s. + * @param returnPeriod The return period of the target ground motion, or + * intensity measure level (IML), in the range [1..20000] years. + * @param imt Optional IMTs at which to compute hazard. If none are supplied, + * then the supported set for the installed model is used. Note that a + * model may not support all the values listed below (see + * disagreggation metadata). Responses for numerous IMT's are quite + * large, on the order of MB. Multiple IMTs may be comma delimited, + * e.g. ?imt=PGA,SA0p2,SA1P0. + */ + @Operation( + summary = "Disaggregate hazard at a specified return period", + description = "Returns a hazard disaggregation computed from the installed model") + @ApiResponse( + description = "Disaggregation", + responseCode = "200") + @Get(uri = "rp/{longitude}/{latitude}/{vs30}/{returnPeriod}{?imt}") + public HttpResponse<String> doGetDisaggReturnPeriod( + HttpRequest<?> http, + @Schema( + minimum = "-360", + maximum = "360") @PathVariable double longitude, + @Schema( + minimum = "-90", + maximum = "90") @PathVariable double latitude, + @Schema( + minimum = "150", + maximum = "3000") @PathVariable double vs30, + @Schema( + minimum = "150", + maximum = "3000") @PathVariable double returnPeriod, + @Schema() @QueryValue @Nullable Set<Imt> imt) { + try { + Set<Imt> imts = HazardService.readImts(http); + DisaggService.RequestRp request = new DisaggService.RequestRp( + http, + longitude, latitude, vs30, + returnPeriod, + imts); + return DisaggService.getDisaggRp(request); + } catch (Exception e) { + return ServletUtil.error( + DisaggService.LOG, e, + DisaggService.NAME, + http.getUri().toString()); + } + } + + /** + * @param longitude Longitude in the range [-360..360]°. + * @param latitude Latitude in decimal degrees [-90..90]°. + * @param vs30 Site Vs30 value in the range [150..3000] m/s. + */ + @Operation( + summary = "Disaggregate hazard at specified IMLs", + description = "Returns a hazard disaggregation computed from the installed model") + @ApiResponse( + description = "Disaggregation", + responseCode = "200") + @Get(uri = "iml/{longitude}/{latitude}/{vs30}") + public HttpResponse<String> doGetDisaggIml( + HttpRequest<?> http, + @Schema( + minimum = "-360", + maximum = "360") @PathVariable double longitude, + @Schema( + minimum = "-90", + maximum = "90") @PathVariable double latitude, + @Schema( + minimum = "150", + maximum = "3000") @PathVariable double vs30) { + + /* + * Developer notes: + * + * It is awkward to support IMT=#; numerous unique keys that may or may not + * be present yields a clunky swagger interface. The disagg-iml endpoint + * requires one or more IMT=# query arguments. Documented in example. + */ + + try { + Map<Imt, Double> imtImlMap = http.getParameters().asMap(Imt.class, Double.class); + checkArgument(!imtImlMap.isEmpty(), "No IMLs supplied"); + DisaggService.RequestIml request = new DisaggService.RequestIml( + http, + longitude, latitude, vs30, + imtImlMap); + return DisaggService.getDisaggIml(request); + } catch (Exception e) { + return ServletUtil.error( + DisaggService.LOG, e, + DisaggService.NAME, + http.getUri().toString()); + } + } +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java new file mode 100644 index 0000000000000000000000000000000000000000..ac428621e93f6cbe4ac2dfcaf088ef0e9b2ca297 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java @@ -0,0 +1,319 @@ +package gov.usgs.earthquake.nshmp.www.hazard; + +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.Range; + +import gov.usgs.earthquake.nshmp.calc.CalcConfig; +import gov.usgs.earthquake.nshmp.calc.Disaggregation; +import gov.usgs.earthquake.nshmp.calc.Hazard; +import gov.usgs.earthquake.nshmp.calc.HazardCalcs; +import gov.usgs.earthquake.nshmp.calc.Site; +import gov.usgs.earthquake.nshmp.geo.Location; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.model.HazardModel; +import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.hazard.HazardService.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Parameter; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import jakarta.inject.Singleton; + +/** + * Disaggregation service. + * + * @see DisaggController + * @author U.S. Geological Survey + */ +@Singleton +public final class DisaggService { + + /* + * Developer notes: + * + * Same query structure as hazard service, but either return period and imt(s) + * OR imt=iml pairs + */ + + static final String NAME = "Disaggregation Service"; + static final Logger LOG = LoggerFactory.getLogger(DisaggService.class); + + private static Range<Double> rpRange = Range.closed(1.0, 20000.0); + private static Range<Double> imlRange = Range.closed(0.0001, 8.0); + + /** HazardController.doGetMetadata() handler. */ + public static HttpResponse<String> getMetadata(HttpRequest<?> request) { + var url = request.getUri().toString(); + var usage = new Metadata(ServletUtil.model()); + var response = ResponseBody.usage() + .name(NAME) + .url(url) + .request(url) + .response(usage) + .build(); + var svcResponse = ServletUtil.GSON.toJson(response); + return HttpResponse.ok(svcResponse); + } + + /** HazardController.doGetDisaggIml() handler. */ + public static HttpResponse<String> getDisaggIml(RequestIml request) + throws InterruptedException, ExecutionException { + var stopwatch = Stopwatch.createStarted(); + var disagg = calcDisaggIml(request); + var response = new Response.Builder() + .timer(stopwatch) + .request(request) + .disagg(disagg) + .build(); + var body = ResponseBody.success() + .name(NAME) + .url(request.http.getUri().toString()) + .request(request) + .response(response) + .build(); + String svcResponse = ServletUtil.GSON2.toJson(body); + return HttpResponse.ok(svcResponse); + } + + /** HazardController.doGetDisaggRp() handler. */ + public static HttpResponse<String> getDisaggRp(RequestRp request) + throws InterruptedException, ExecutionException { + var stopwatch = Stopwatch.createStarted(); + var disagg = calcDisaggRp(request); + var response = new Response.Builder() + .timer(stopwatch) + .request(request) + .disagg(disagg) + .build(); + var body = ResponseBody.success() + .name(NAME) + .url(request.http.getUri().toString()) + .request(request) + .response(response) + .build(); + String svcResponse = ServletUtil.GSON2.toJson(body); + return HttpResponse.ok(svcResponse); + } + + /* + * Developer notes: + * + * If disaggIml, we need to do the calculation for single XySeqs if disaggRp, + * we don't know the imls so must compute hazard over the full curve + * + */ + + static Disaggregation calcDisaggIml(RequestIml request) + throws InterruptedException, ExecutionException { + + HazardModel model = ServletUtil.model(); + + // modify config to include service endpoint arguments + CalcConfig config = CalcConfig.copyOf(model.config()) + .imts(request.imls.keySet()) + .build(); + + // TODO this needs to pick up SiteData, centralize + Site site = Site.builder() + .location(Location.create(request.longitude, request.latitude)) + .vs30(request.vs30) + .build(); + + // use HazardService.calcHazard() instead? + CompletableFuture<Hazard> hazFuture = CompletableFuture.supplyAsync( + () -> HazardCalcs.hazard( + model, config, site, + ServletUtil.CALC_EXECUTOR), + ServletUtil.TASK_EXECUTOR); + + Hazard hazard = hazFuture.get(); + + CompletableFuture<Disaggregation> disaggfuture = CompletableFuture.supplyAsync( + () -> Disaggregation.atImls( + hazard, request.imls, + ServletUtil.CALC_EXECUTOR), + ServletUtil.TASK_EXECUTOR); + + Disaggregation disagg = disaggfuture.get(); + + return disagg; + } + + static Disaggregation calcDisaggRp(RequestRp request) + throws InterruptedException, ExecutionException { + + HazardModel model = ServletUtil.model(); + + // modify config to include service endpoint arguments + CalcConfig config = CalcConfig.copyOf(model.config()) + .imts(request.imts) + .build(); + + // TODO this needs to pick up SiteData, centralize + Site site = Site.builder() + .location(Location.create(request.longitude, request.latitude)) + .vs30(request.vs30) + .build(); + + CompletableFuture<Hazard> hazFuture = CompletableFuture.supplyAsync( + () -> HazardCalcs.hazard( + model, config, site, + ServletUtil.CALC_EXECUTOR), + ServletUtil.TASK_EXECUTOR); + + Hazard hazard = hazFuture.get(); + + CompletableFuture<Disaggregation> disaggfuture = CompletableFuture.supplyAsync( + () -> Disaggregation.atReturnPeriod( + hazard, request.returnPeriod, + ServletUtil.CALC_EXECUTOR), + ServletUtil.TASK_EXECUTOR); + + Disaggregation disagg = disaggfuture.get(); + + return disagg; + } + + static final class RequestIml { + + final transient HttpRequest<?> http; + final double longitude; + final double latitude; + final double vs30; + final Map<Imt, Double> imls; + + RequestIml( + HttpRequest<?> http, + double longitude, + double latitude, + double vs30, + Map<Imt, Double> imls) { + + this.http = http; + this.longitude = longitude; + this.latitude = latitude; + this.vs30 = vs30; + this.imls = imls; + } + } + + static final class RequestRp { + + final transient HttpRequest<?> http; + final double longitude; + final double latitude; + final double vs30; + final double returnPeriod; + final Set<Imt> imts; + + RequestRp( + HttpRequest<?> http, + double longitude, + double latitude, + double vs30, + double returnPeriod, + Set<Imt> imts) { + + this.http = http; + this.longitude = longitude; + this.latitude = latitude; + this.vs30 = vs30; + this.returnPeriod = returnPeriod; + this.imts = imts.isEmpty() + ? ServletUtil.model().config().hazard.imts + : imts; + } + } + + private static final class Response { + final Response.Metadata metadata; + final List<ImtDisagg> disaggs; + + Response(Response.Metadata metadata, List<ImtDisagg> disaggs) { + this.metadata = metadata; + this.disaggs = disaggs; + } + + private static final class Metadata { + final Object server; + final String rlabel = "Closest Distance, rRup (km)"; + final String mlabel = "Magnitude (Mw)"; + final String εlabel = "% Contribution to Hazard"; + final Object εbins; + + Metadata(Object server, Object εbins) { + this.server = server; + this.εbins = εbins; + } + } + + private static final class Builder { + + Stopwatch timer; + Optional<RequestRp> requestRp = Optional.empty(); + Optional<RequestIml> requestIml = Optional.empty(); + Disaggregation disagg; + + Builder timer(Stopwatch timer) { + this.timer = timer; + return this; + } + + Builder request(Object request) { + if (request instanceof RequestRp) { + requestRp = Optional.of((RequestRp) request); + return this; + } + requestIml = Optional.of((RequestIml) request); + return this; + } + + Builder disagg(Disaggregation disagg) { + this.disagg = disagg; + return this; + } + + Response build() { + + Set<Imt> imts = requestRp.isPresent() + ? requestRp.orElseThrow().imts + : requestIml.orElseThrow().imls.keySet(); + + List<ImtDisagg> disaggs = imts.stream() + .map(imt -> new ImtDisagg(imt, disagg.toJson(imt))) + .collect(toList()); + + Object server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer); + + return new Response( + new Response.Metadata(server, disagg.εBins()), + disaggs); + } + } + } + + private static final class ImtDisagg { + final Parameter imt; + final Object data; + + ImtDisagg(Imt imt, Object data) { + this.imt = new Parameter( + ServletUtil.imtShortLabel(imt), + imt.name()); + this.data = data; + } + } +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java new file mode 100644 index 0000000000000000000000000000000000000000..0fe36de8adaceffb0519da84c7c51e24e7ce5cf1 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java @@ -0,0 +1,107 @@ +package gov.usgs.earthquake.nshmp.www.hazard; + +import java.util.Set; + +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet; +import gov.usgs.earthquake.nshmp.www.ServletUtil; + +import io.micronaut.core.annotation.Nullable; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.PathVariable; +import io.micronaut.http.annotation.QueryValue; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; + +/** + * Micronaut web service controller for probabilistic seismic hazard + * calculations. + * + * @author U.S. Geological Survey + */ +@Tag( + name = "Hazard", + description = "USGS NSHMP hazard calculation service") +@Controller("/hazard") +public class HazardController { + + @Inject + private NshmpMicronautServlet servlet; + + @Operation( + summary = "Hazard model and service metadata", + description = "Returns details of the installed model and service request parameters") + @ApiResponse( + description = "Hazard service metadata", + responseCode = "200") + @Get + public HttpResponse<String> doGetMetadata(HttpRequest<?> http) { + try { + return HazardService.getMetadata(http); + } catch (Exception e) { + return ServletUtil.error( + HazardService.LOG, e, + HazardService.NAME, + http.getUri().toString()); + } + } + + /** + * @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 + * @param imt Optional IMTs at which to compute hazard. If none are supplied, + * then the supported set for the installed model is used. Note that a + * model may not support all the values listed below (see + * disagreggation metadata). Responses for numerous IMT's are quite + * large, on the order of MB. Multiple IMTs may be comma delimited, + * e.g. ?imt=PGA,SA0p2,SA1P0. + * + */ + @Operation( + summary = "Compute probabilisitic hazard at a site", + description = "Returns hazard curves computed from the installed model") + @ApiResponse( + description = "Hazard curves", + responseCode = "200") + @Get(uri = "/{longitude}/{latitude}/{vs30}{?truncate,maxdir,imt}") + public HttpResponse<String> doGetHazard( + HttpRequest<?> http, + @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, + @QueryValue @Nullable Set<Imt> imt) { + try { + Set<Imt> imts = HazardService.readImts(http); + HazardService.Request request = new HazardService.Request( + http, + longitude, latitude, vs30, + truncate, maxdir, + imts); + return HazardService.getHazard(request); + } catch (Exception e) { + return ServletUtil.error( + HazardService.LOG, e, + HazardService.NAME, + http.getUri().toString()); + } + } +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java new file mode 100644 index 0000000000000000000000000000000000000000..f668b016e76468c696e0759fa3cee55077c2451e --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java @@ -0,0 +1,358 @@ +package gov.usgs.earthquake.nshmp.www.hazard; + +import static com.google.common.base.Preconditions.checkState; +import static gov.usgs.earthquake.nshmp.calc.HazardExport.curvesBySource; +import static gov.usgs.earthquake.nshmp.data.DoubleData.checkInRange; +import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLatitude; +import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLongitude; +import static java.util.stream.Collectors.toCollection; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Stopwatch; + +import gov.usgs.earthquake.nshmp.calc.CalcConfig; +import gov.usgs.earthquake.nshmp.calc.Hazard; +import gov.usgs.earthquake.nshmp.calc.HazardCalcs; +import gov.usgs.earthquake.nshmp.calc.Site; +import gov.usgs.earthquake.nshmp.data.MutableXySequence; +import gov.usgs.earthquake.nshmp.data.XySequence; +import gov.usgs.earthquake.nshmp.geo.Coordinates; +import gov.usgs.earthquake.nshmp.geo.Location; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.model.HazardModel; +import gov.usgs.earthquake.nshmp.model.SourceType; +import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; +import gov.usgs.earthquake.nshmp.www.meta.Parameter; +import gov.usgs.earthquake.nshmp.www.services.SourceServices.SourceModel; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import jakarta.inject.Singleton; + +/** + * Hazard service. + * + * @see HazardController + * @author U.S. Geological Survey + */ +@Singleton +public final class HazardService { + + static final String NAME = "Hazard Service"; + static final Logger LOG = LoggerFactory.getLogger(HazardService.class); + + private static final String TOTAL_KEY = "Total"; + + /** HazardController.doGetUsage() handler. */ + public static HttpResponse<String> getMetadata(HttpRequest<?> request) { + var url = request.getUri().toString(); + var usage = new Metadata(ServletUtil.model()); + var body = ResponseBody.usage() + .name(NAME) + .url(url) + .request(url) + .response(usage) + .build(); + var json = ServletUtil.GSON2.toJson(body); + return HttpResponse.ok(json); + } + + /** HazardController.doGetHazard() handler. */ + public static HttpResponse<String> getHazard(Request request) + throws InterruptedException, ExecutionException { + var stopwatch = Stopwatch.createStarted(); + var hazard = calcHazard(request); + var response = new Response.Builder() + .timer(stopwatch) + .request(request) + .hazard(hazard) + .build(); + var body = ResponseBody.success() + .name(NAME) + .url(request.http.getUri().toString()) + .request(request) + .response(response) + .build(); + String json = ServletUtil.GSON2.toJson(body); + return HttpResponse.ok(json); + } + + /* + * Developer notes: + * + * Future calculation configuration options: vertical GMs + * + * NSHM Hazard Tool will not pass truncation and maxdir args/flags as the apps + * apply truncation and scaling on the client. + */ + + public static Hazard calcHazard(Request request) + throws InterruptedException, ExecutionException { + + HazardModel model = ServletUtil.model(); + + // modify config to include service endpoint arguments + CalcConfig config = CalcConfig.copyOf(model.config()) + .imts(request.imts) + .build(); + + // TODO this needs to pick up SiteData, centralize + Site site = Site.builder() + .location(Location.create(request.longitude, request.latitude)) + .vs30(request.vs30) + .build(); + + CompletableFuture<Hazard> future = CompletableFuture.supplyAsync( + () -> HazardCalcs.hazard( + model, config, site, + ServletUtil.CALC_EXECUTOR), + ServletUtil.TASK_EXECUTOR); + + return future.get(); + } + + static class Metadata { + + final SourceModel model; + final DoubleParameter longitude; + final DoubleParameter latitude; + final DoubleParameter vs30; + + Metadata(HazardModel model) { + this.model = new SourceModel(model); + // TODO need min max from model + longitude = new DoubleParameter( + "Longitude", + "°", + Coordinates.LON_RANGE.lowerEndpoint(), + Coordinates.LON_RANGE.upperEndpoint()); + + latitude = new DoubleParameter( + "Latitude", + "°", + Coordinates.LAT_RANGE.lowerEndpoint(), + Coordinates.LAT_RANGE.upperEndpoint()); + + vs30 = new DoubleParameter( + "Vs30", + "m/s", + 150, + 1500); + } + } + + public static final class Request { + + final transient HttpRequest<?> http; + final double longitude; + final double latitude; + final double vs30; + final boolean truncate; + final boolean maxdir; + final Set<Imt> imts; + + public Request( + HttpRequest<?> http, + double longitude, + double latitude, + int vs30, + boolean truncate, + boolean maxdir, + Set<Imt> imts) { + + this.http = http; + this.longitude = checkLongitude(longitude); + this.latitude = checkLatitude(latitude); + this.vs30 = checkInRange(Site.VS30_RANGE, Site.Key.VS30, vs30); + this.truncate = truncate; + this.maxdir = maxdir; + this.imts = imts.isEmpty() + ? ServletUtil.model().config().hazard.imts + : imts; + } + } + + private static final class Response { + + final Metadata metadata; + final List<ImtCurves> hazardCurves; + + Response(Metadata metadata, List<ImtCurves> hazardCurves) { + this.metadata = metadata; + this.hazardCurves = hazardCurves; + } + + private static final class Metadata { + final Object server; + final String xlabel = "Ground Motion (g)"; + final String ylabel = "Annual Frequency of Exceedence"; + + Metadata(Object server) { + this.server = server; + } + } + + private static final class Builder { + + Stopwatch timer; + Request request; + Map<Imt, Map<SourceType, MutableXySequence>> componentMaps; + Map<Imt, MutableXySequence> totalMap; + + Builder timer(Stopwatch timer) { + this.timer = timer; + return this; + } + + Builder request(Request request) { + this.request = request; + return this; + } + + Builder hazard(Hazard hazard) { + // TODO necessary?? + checkState(totalMap == null, "Hazard has already been added to this builder"); + + componentMaps = new EnumMap<>(Imt.class); + totalMap = new EnumMap<>(Imt.class); + + var typeTotalMaps = curvesBySource(hazard); + + for (var imt : hazard.curves().keySet()) { + + /* Total curve for IMT. */ + XySequence.addToMap(imt, totalMap, hazard.curves().get(imt)); + + /* Source component curves for IMT. */ + var typeTotalMap = typeTotalMaps.get(imt); + var componentMap = componentMaps.get(imt); + + if (componentMap == null) { + componentMap = new EnumMap<>(SourceType.class); + componentMaps.put(imt, componentMap); + } + + for (var type : typeTotalMap.keySet()) { + XySequence.addToMap(type, componentMap, typeTotalMap.get(type)); + } + } + + return this; + } + + Response build() { + var hazards = new ArrayList<ImtCurves>(); + + for (Imt imt : totalMap.keySet()) { + var curves = new ArrayList<Curve>(); + + // total curve + 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(), + updateCurve(request, typeMap.get(type), imt))); + } + + hazards.add(new ImtCurves(imt, curves)); + } + + Object server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer); + var response = new Response( + new Response.Metadata(server), + hazards); + + return response; + } + } + + } + + private static final class ImtCurves { + final Parameter imt; + final List<Curve> data; + + ImtCurves(Imt imt, List<Curve> data) { + this.imt = new Parameter(ServletUtil.imtShortLabel(imt), imt.name()); + this.data = data; + } + } + + private static final class Curve { + final String component; + final XySequence values; + + Curve(String component, XySequence values) { + this.component = component; + this.values = values; + } + } + + private static final double TRUNCATION_LIMIT = 1e-4; + + /* Convert to linear and possibly truncate and scale to max-direction. */ + private static XySequence updateCurve( + Request 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; + } + + /* Read the 'imt' query values; can be comma-delimited. */ + static Set<Imt> readImts(HttpRequest<?> http) { + return http.getParameters() + .getAll("imt")// TODO where are key strings? + .stream() + .map(s -> s.split(",")) + .flatMap(Arrays::stream) + .map(Imt::valueOf) + .collect(toCollection(() -> EnumSet.noneOf(Imt.class))); + } +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/MaxDirection.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/MaxDirection.java similarity index 97% rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/MaxDirection.java rename to src/main/java/gov/usgs/earthquake/nshmp/www/hazard/MaxDirection.java index e35c59355037416482937ce865aeee59400af2cf..99c40c42d3127fcca3bb176c6fb837d20d0d6c3d 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/MaxDirection.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/MaxDirection.java @@ -1,4 +1,4 @@ -package gov.usgs.earthquake.nshmp.www.services; +package gov.usgs.earthquake.nshmp.www.hazard; import static gov.usgs.earthquake.nshmp.gmm.Imt.PGA; import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P01; diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/MetaUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/MetaUtil.java index 74c7c7ab48943d014553be9d53d8046790d38701..a1a2c068400b60b52532e01acb7d912b1dbde6e0 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/MetaUtil.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/MetaUtil.java @@ -39,7 +39,7 @@ public final class MetaUtil { JsonObject json = new JsonObject(); json.add("location", loc); json.addProperty("vs30", site.vs30()); - json.addProperty("vsInfered", site.vsInferred()); + json.addProperty("vsInferred", site.vsInferred()); json.addProperty("z1p0", Double.isNaN(site.z1p0()) ? null : site.z1p0()); json.addProperty("z2p5", Double.isNaN(site.z2p5()) ? null : site.z2p5()); diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java index 1120be8504341bf5e2b843fb4b3615f2f9216869..84227bdce0090294ed736dc7c52b3ecb55bd7e90 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java @@ -4,7 +4,7 @@ import com.google.common.base.Stopwatch; import com.google.common.base.Throwables; import gov.usgs.earthquake.nshmp.geo.Coordinates; -import gov.usgs.earthquake.nshmp.www.services.ServletUtil; +import gov.usgs.earthquake.nshmp.www.ServletUtil; /** * Service metadata, parameterization, and constraint strings, in JSON format. @@ -28,43 +28,11 @@ public final class Metadata { this.status = Status.USAGE.toString(); this.description = description; this.syntax = syntax; - this.server = serverData(1, Stopwatch.createStarted()); + this.server = ServletUtil.serverData(1, Stopwatch.createStarted()); this.parameters = parameters; } } - public static Object serverData(int threads, Stopwatch timer) { - return new Server(threads, timer); - } - - private static class Server { - - final int threads; - final String timer; - final String version; - - Server(int threads, Stopwatch timer) { - this.threads = threads; - this.timer = timer.toString(); - this.version = "TODO where to get version?"; - } - - // static Component NSHMP_HAZ_COMPONENT = new Component( - // NSHMP_HAZ_URL, - // Versions.NSHMP_HAZ_VERSION); - // - // static final class Component { - // - // final String url; - // final String version; - // - // Component(String url, String version) { - // this.url = url; - // this.version = version; - // } - // } - } - public static class DefaultParameters { // final EnumParameter<Edition> edition; 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 deleted file mode 100644 index 89f03a87b3a4d52e36b209be6f79799729b46a51..0000000000000000000000000000000000000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java +++ /dev/null @@ -1,422 +0,0 @@ -package gov.usgs.earthquake.nshmp.www.services; - -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.concurrent.ExecutionException; -import java.util.function.Function; - -import com.google.common.base.Stopwatch; - -import gov.usgs.earthquake.nshmp.calc.CalcConfig; -import gov.usgs.earthquake.nshmp.calc.Hazard; -import gov.usgs.earthquake.nshmp.calc.Site; -import gov.usgs.earthquake.nshmp.data.MutableXySequence; -import gov.usgs.earthquake.nshmp.data.XySequence; -import gov.usgs.earthquake.nshmp.geo.Coordinates; -import gov.usgs.earthquake.nshmp.geo.Location; -import gov.usgs.earthquake.nshmp.gmm.Imt; -import gov.usgs.earthquake.nshmp.model.HazardModel; -import gov.usgs.earthquake.nshmp.model.SourceType; -import gov.usgs.earthquake.nshmp.www.HazardController; -import gov.usgs.earthquake.nshmp.www.ResponseBody; -import gov.usgs.earthquake.nshmp.www.WsUtils; -import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; -import gov.usgs.earthquake.nshmp.www.meta.Metadata; -import gov.usgs.earthquake.nshmp.www.meta.Parameter; -import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceQueryData; -import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceRequestData; -import gov.usgs.earthquake.nshmp.www.services.SourceServices.SourceModel; - -import io.micronaut.http.HttpRequest; -import io.micronaut.http.HttpResponse; -import jakarta.inject.Singleton; - -/** - * Probabilistic seismic hazard calculation handler for - * {@link HazardController}. - * - * @author U.S. Geological Survey - */ -@Singleton -public final class HazardService { - - private static final String NAME = "Hazard Service"; - - /** HazardController.doGetUsage() handler. */ - public static HttpResponse<String> handleDoGetMetadata(HttpRequest<?> request) { - var url = request.getUri().getPath(); - try { - var usage = new RequestMetadata(ServletUtil.model()); - var response = ResponseBody.usage() - .name(NAME) - .url(url) - .request(url) - .response(usage) - .build(); - var svcResponse = ServletUtil.GSON.toJson(response); - return HttpResponse.ok(svcResponse); - } catch (Exception e) { - return ServicesUtil.handleError(e, NAME, url); - } - } - - /** HazardController.doGetHazard() handler. */ - public static HttpResponse<String> handleDoGetHazard( - HttpRequest<?> request, - QueryParameters query) { - - try { - // TODO still need to validate - // if (query.isEmpty()) { - // return handleDoGetUsage(urlHelper); - // } - // query.checkParameters(); - var data = new RequestData(query); - var response = process(request, data); - var svcResponse = ServletUtil.GSON.toJson(response); - return HttpResponse.ok(svcResponse); - } catch (Exception e) { - return ServicesUtil.handleError(e, NAME, request.getUri().getPath()); - } - } - - static ResponseBody<RequestData, ResponseData> process( - HttpRequest<?> request, - RequestData data) - throws InterruptedException, ExecutionException { - - var configFunction = new ConfigFunction(); - var siteFunction = new SiteFunction(data); - var stopwatch = Stopwatch.createStarted(); - var hazard = ServicesUtil.calcHazard(configFunction, siteFunction); - - return new ResultBuilder() - .hazard(hazard) - .requestData(data) - .timer(stopwatch) - .url(request) - .build(); - } - - static class ConfigFunction implements Function<HazardModel, CalcConfig> { - @Override - public CalcConfig apply(HazardModel model) { - var configBuilder = CalcConfig.copyOf(model.config()); - return configBuilder.build(); - } - } - - static class SiteFunction implements Function<CalcConfig, Site> { - final RequestData data; - - private SiteFunction(RequestData data) { - this.data = data; - } - - @Override // TODO this needs to pick up SiteData - public Site apply(CalcConfig config) { - return Site.builder() - .location(Location.create(data.longitude, data.latitude)) - .vs30(data.vs30) - .build(); - } - } - - public static class QueryParameters { - - final double longitude; - final double latitude; - final int vs30; - final boolean truncate; - final boolean maxdir; - - public QueryParameters( - 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; - } - - // 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 - // } - - /* Service request and model metadata */ - static class RequestMetadata { - - final SourceModel model; - final DoubleParameter longitude; - final DoubleParameter latitude; - final DoubleParameter vs30; - - RequestMetadata(HazardModel model) { - this.model = new SourceModel(model); - // TODO need min max from model - longitude = new DoubleParameter( - "Longitude", - "°", - Coordinates.LON_RANGE.lowerEndpoint(), - Coordinates.LON_RANGE.upperEndpoint()); - - latitude = new DoubleParameter( - "Latitude", - "°", - Coordinates.LAT_RANGE.lowerEndpoint(), - Coordinates.LAT_RANGE.upperEndpoint()); - - vs30 = new DoubleParameter( - "Latitude", - "m/s", - 150, - 1500); - } - } - - static class RequestData { - - final double longitude; - final double latitude; - final double vs30; - final boolean truncate; - final boolean maxdir; - - RequestData(QueryParameters query) { - this.longitude = query.longitude; - this.latitude = query.latitude; - this.vs30 = query.vs30; - this.truncate = query.truncate; - this.maxdir = query.maxdir; - } - } - - private static final class ResponseMetadata { - final String xlabel = "Ground Motion (g)"; - final String ylabel = "Annual Frequency of Exceedence"; - final Object server; - - ResponseMetadata(Object server) { - this.server = server; - } - } - - private static String imtShortLabel(Imt imt) { - if (imt.equals(Imt.PGA) || imt.equals(Imt.PGV)) { - return imt.name(); - } else if (imt.isSA()) { - return imt.period() + " s"; - } - return imt.toString(); - } - - @Deprecated - static class RequestDataOld extends ServiceRequestData { - final double vs30; - - RequestDataOld(Query query, double vs30) { - super(query); - this.vs30 = vs30; - } - } - - private static final class ResponseData { - final ResponseMetadata metadata; - final List<HazardResponse> hazardCurves; - - ResponseData(ResponseMetadata metadata, List<HazardResponse> hazardCurves) { - this.metadata = metadata; - this.hazardCurves = hazardCurves; - } - } - - private static final class HazardResponse { - final Parameter imt; - final List<Curve> data; - - HazardResponse(Imt imt, List<Curve> data) { - this.imt = new Parameter(imtShortLabel(imt), imt.name()); - this.data = data; - } - } - - private static final class Curve { - final String component; - final XySequence values; - - Curve(String component, XySequence values) { - this.component = component; - this.values = values; - } - } - - private static final String TOTAL_KEY = "Total"; - - private static final class ResultBuilder { - - String url; - Stopwatch timer; - RequestData request; - - Map<Imt, Map<SourceType, MutableXySequence>> componentMaps; - Map<Imt, MutableXySequence> totalMap; - - ResultBuilder hazard(Hazard hazardResult) { - // TODO necessary?? - checkState(totalMap == null, "Hazard has already been added to this builder"); - - componentMaps = new EnumMap<>(Imt.class); - totalMap = new EnumMap<>(Imt.class); - - var typeTotalMaps = curvesBySource(hazardResult); - - for (var imt : hazardResult.curves().keySet()) { - - /* Total curve for IMT. */ - XySequence.addToMap(imt, totalMap, hazardResult.curves().get(imt)); - - /* Source component curves for IMT. */ - var typeTotalMap = typeTotalMaps.get(imt); - var componentMap = componentMaps.get(imt); - - if (componentMap == null) { - componentMap = new EnumMap<>(SourceType.class); - componentMaps.put(imt, componentMap); - } - - for (var type : typeTotalMap.keySet()) { - XySequence.addToMap(type, componentMap, typeTotalMap.get(type)); - } - } - - return this; - } - - ResultBuilder url(HttpRequest<?> request) { - url = request.getUri().getPath(); - return this; - } - - ResultBuilder timer(Stopwatch timer) { - this.timer = timer; - return this; - } - - ResultBuilder requestData(RequestData request) { - this.request = request; - return this; - } - - ResponseBody<RequestData, ResponseData> build() { - var hazards = new ArrayList<HazardResponse>(); - - for (Imt imt : totalMap.keySet()) { - var curves = new ArrayList<Curve>(); - - // total curve - 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(), - updateCurve(request, typeMap.get(type), imt))); - } - - hazards.add(new HazardResponse(imt, List.copyOf(curves))); - } - - Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer); - var response = new ResponseData(new ResponseMetadata(server), List.copyOf(hazards)); - - return ResponseBody.<RequestData, ResponseData> success() - .name(NAME) - .url(url) - .request(request) - .response(response) - .build(); - } - } - - 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; - - public Query(Double longitude, Double latitude, Integer vs30) { - super(longitude, latitude); - this.vs30 = vs30; - } - - @Override - public boolean isNull() { - return super.isNull() && vs30 == null; - } - - @Override - public void checkValues() { - super.checkValues(); - WsUtils.checkValue(ServicesUtil.Key.VS30, vs30); - } - } - -} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService2.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService2.java deleted file mode 100644 index 63fb4261bd5c38b37aea9926f00c787e3cdbd1d1..0000000000000000000000000000000000000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService2.java +++ /dev/null @@ -1,452 +0,0 @@ -package gov.usgs.earthquake.nshmp.www.services; - -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.concurrent.ExecutionException; -import java.util.function.Function; - -import com.google.common.base.Stopwatch; - -import gov.usgs.earthquake.nshmp.calc.CalcConfig; -import gov.usgs.earthquake.nshmp.calc.Hazard; -import gov.usgs.earthquake.nshmp.calc.Site; -import gov.usgs.earthquake.nshmp.data.MutableXySequence; -import gov.usgs.earthquake.nshmp.data.XySequence; -import gov.usgs.earthquake.nshmp.geo.Coordinates; -import gov.usgs.earthquake.nshmp.geo.Location; -import gov.usgs.earthquake.nshmp.gmm.Imt; -import gov.usgs.earthquake.nshmp.model.HazardModel; -import gov.usgs.earthquake.nshmp.model.SourceType; -import gov.usgs.earthquake.nshmp.www.HazardController; -import gov.usgs.earthquake.nshmp.www.ResponseBody; -import gov.usgs.earthquake.nshmp.www.WsUtils; -import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; -import gov.usgs.earthquake.nshmp.www.meta.Metadata; -import gov.usgs.earthquake.nshmp.www.meta.Parameter; -import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceQueryData; -import gov.usgs.earthquake.nshmp.www.services.SourceServices.SourceModel; - -import io.micronaut.http.HttpRequest; -import io.micronaut.http.HttpResponse; -import jakarta.inject.Singleton; - -/** - * Probabilistic seismic hazard calculation handler for - * {@link HazardController}. - * - * @author U.S. Geological Survey - */ -@Singleton -public final class HazardService2 { - - private static final String NAME = "Hazard Service"; - - /** HazardController.doGetUsage() handler. */ - public static HttpResponse<String> handleDoGetMetadata(HttpRequest<?> request) { - var url = request.getUri().getPath(); - try { - var usage = new RequestMetadata(ServletUtil.model());// SourceServices.ResponseData(); - var response = ResponseBody.usage() - .name(NAME) - .url(url) - .request(url) - .response(usage) - .build(); - var svcResponse = ServletUtil.GSON.toJson(response); - return HttpResponse.ok(svcResponse); - } catch (Exception e) { - return ServicesUtil.handleError(e, NAME, url); - } - } - - /** HazardController.doGetHazard() handler. */ - public static HttpResponse<String> handleDoGetHazard( - HttpRequest<?> request, - RequestData args) { - - try { - // TODO still need to validate - // if (query.isEmpty()) { - // return handleDoGetUsage(urlHelper); - // } - // query.checkParameters(); - - // var data = new RequestData(query); - - ResponseBody<RequestData, ResponseData> response = process(request, args); - String svcResponse = ServletUtil.GSON.toJson(response); - return HttpResponse.ok(svcResponse); - - } catch (Exception e) { - return ServicesUtil.handleError(e, NAME, request.getUri().getPath()); - } - } - - static ResponseBody<RequestData, ResponseData> process( - HttpRequest<?> request, - RequestData data) throws InterruptedException, ExecutionException { - - var configFunction = new ConfigFunction(); - var siteFunction = new SiteFunction(data); - var stopwatch = Stopwatch.createStarted(); - var hazard = ServicesUtil.calcHazard(configFunction, siteFunction); - - return new ResultBuilder() - .hazard(hazard) - .requestData(data) - .timer(stopwatch) - .url(request) - .build(); - } - - static class ConfigFunction implements Function<HazardModel, CalcConfig> { - @Override - public CalcConfig apply(HazardModel model) { - var configBuilder = CalcConfig.copyOf(model.config()); - return configBuilder.build(); - } - } - - static class SiteFunction implements Function<CalcConfig, Site> { - final RequestData data; - - private SiteFunction(RequestData data) { - this.data = data; - } - - @Override // TODO this needs to pick up SiteData - public Site apply(CalcConfig config) { - return Site.builder() - .location(Location.create(data.longitude, data.latitude)) - .vs30(data.vs30) - .build(); - } - } - - // public static class QueryParameters { - // - // final double longitude; - // final double latitude; - // final int vs30; - // final boolean truncate; - // final boolean maxdir; - // - // public QueryParameters( - // 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; - // } - // - // // 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 - // } - - /* Service request and model metadata */ - static class RequestMetadata { - - final SourceModel model; - final DoubleParameter longitude; - final DoubleParameter latitude; - final DoubleParameter vs30; - - RequestMetadata(HazardModel model) { - this.model = new SourceModel(model); - // TODO need min max from model - longitude = new DoubleParameter( - "Longitude", - "°", - Coordinates.LON_RANGE.lowerEndpoint(), - Coordinates.LON_RANGE.upperEndpoint()); - - latitude = new DoubleParameter( - "Latitude", - "°", - Coordinates.LAT_RANGE.lowerEndpoint(), - Coordinates.LAT_RANGE.upperEndpoint()); - - vs30 = new DoubleParameter( - "Latitude", - "m/s", - 150, - 1500); - } - } - - // static class RequestData { - // - // final double longitude; - // final double latitude; - // final double vs30; - // final boolean truncate; - // final boolean maxdir; - // - // RequestData(QueryParameters query) { - // this.longitude = query.longitude; - // this.latitude = query.latitude; - // this.vs30 = query.vs30; - // this.truncate = query.truncate; - // this.maxdir = query.maxdir; - // } - // } - - private static final class ResponseMetadata { - final String xlabel = "Ground Motion (g)"; - final String ylabel = "Annual Frequency of Exceedence"; - final Object server; - - ResponseMetadata(Object server) { - this.server = server; - } - } - - private static String imtShortLabel(Imt imt) { - if (imt.equals(Imt.PGA) || imt.equals(Imt.PGV)) { - return imt.name(); - } else if (imt.isSA()) { - return imt.period() + " s"; - } - return imt.toString(); - } - - // @Deprecated - // static class RequestDataOld extends ServiceRequestData { - // final double vs30; - // - // RequestDataOld(Query query, double vs30) { - // super(query); - // this.vs30 = vs30; - // } - // } - - private static final class ResponseData { - final ResponseMetadata metadata; - final List<HazardResponse> hazardCurves; - - ResponseData(ResponseMetadata metadata, List<HazardResponse> hazardCurves) { - this.metadata = metadata; - this.hazardCurves = hazardCurves; - } - } - - private static final class HazardResponse { - final Parameter imt; - final List<Curve> data; - - HazardResponse(Imt imt, List<Curve> data) { - this.imt = new Parameter(imtShortLabel(imt), imt.name()); - this.data = data; - } - } - - private static final class Curve { - final String component; - final XySequence values; - - Curve(String component, XySequence values) { - this.component = component; - this.values = values; - } - } - - private static final String TOTAL_KEY = "Total"; - - private static final class ResultBuilder { - - String url; - Stopwatch timer; - RequestData request; - - Map<Imt, Map<SourceType, MutableXySequence>> componentMaps; - Map<Imt, MutableXySequence> totalMap; - - ResultBuilder hazard(Hazard hazardResult) { - // TODO necessary?? - checkState(totalMap == null, "Hazard has already been added to this builder"); - - componentMaps = new EnumMap<>(Imt.class); - totalMap = new EnumMap<>(Imt.class); - - var typeTotalMaps = curvesBySource(hazardResult); - - for (var imt : hazardResult.curves().keySet()) { - - /* Total curve for IMT. */ - XySequence.addToMap(imt, totalMap, hazardResult.curves().get(imt)); - - /* Source component curves for IMT. */ - var typeTotalMap = typeTotalMaps.get(imt); - var componentMap = componentMaps.get(imt); - - if (componentMap == null) { - componentMap = new EnumMap<>(SourceType.class); - componentMaps.put(imt, componentMap); - } - - for (var type : typeTotalMap.keySet()) { - XySequence.addToMap(type, componentMap, typeTotalMap.get(type)); - } - } - - return this; - } - - ResultBuilder url(HttpRequest<?> request) { - url = request.getUri().getPath(); - return this; - } - - ResultBuilder timer(Stopwatch timer) { - this.timer = timer; - return this; - } - - ResultBuilder requestData(RequestData request) { - this.request = request; - return this; - } - - ResponseBody<RequestData, ResponseData> build() { - var hazards = new ArrayList<HazardResponse>(); - - for (Imt imt : totalMap.keySet()) { - var curves = new ArrayList<Curve>(); - - // total curve - 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(), - updateCurve(request, typeMap.get(type), imt))); - } - - hazards.add(new HazardResponse(imt, List.copyOf(curves))); - } - - Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer); - var response = new ResponseData(new ResponseMetadata(server), List.copyOf(hazards)); - - return ResponseBody.<RequestData, ResponseData> success() - .name(NAME) - .url(url) - .request(request) - .response(response) - .build(); - } - } - - 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; - - public Query(Double longitude, Double latitude, Integer vs30) { - super(longitude, latitude); - this.vs30 = vs30; - } - - @Override - public boolean isNull() { - return super.isNull() && vs30 == null; - } - - @Override - public void checkValues() { - super.checkValues(); - WsUtils.checkValue(ServicesUtil.Key.VS30, vs30); - } - } - - public static final class RequestData { - - final double longitude; - final double latitude; - final int vs30; - final boolean truncate; - final boolean maxdir; - - public RequestData( - 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; - } - - // void checkParameters() { - // checkParameter(longitude, "longitude"); - // checkParameter(latitude, "latitude"); - // checkParameter(vs30, "vs30"); - // } - } - -} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java index 0312e7359a0af379d43fdd55c8a5903edce67a2a..d4b81df814966cef2ba072dfbd42062d40f68b5b 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java @@ -6,6 +6,9 @@ import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.google.common.base.Stopwatch; import com.google.common.util.concurrent.ListenableFuture; @@ -17,13 +20,13 @@ import gov.usgs.earthquake.nshmp.geo.Location; import gov.usgs.earthquake.nshmp.model.HazardModel; import gov.usgs.earthquake.nshmp.www.RateController; import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ServicesUtil.Key; +import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceQueryData; +import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceRequestData; +import gov.usgs.earthquake.nshmp.www.ServletUtil; import gov.usgs.earthquake.nshmp.www.WsUtils; import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; -import gov.usgs.earthquake.nshmp.www.meta.Metadata; import gov.usgs.earthquake.nshmp.www.meta.Metadata.DefaultParameters; -import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.Key; -import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceQueryData; -import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceRequestData; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; @@ -38,6 +41,8 @@ import jakarta.inject.Singleton; @Singleton public final class RateService { + static final Logger LOG = LoggerFactory.getLogger(RateService.class); + /* * Developer notes: * @@ -62,7 +67,7 @@ public final class RateService { var json = ServletUtil.GSON.toJson(response); return HttpResponse.ok(json); } catch (Exception e) { - return ServicesUtil.handleError(e, service.name, request.getUri().getPath()); + return ServletUtil.error(LOG, e, service.name, request.getUri().getPath()); } } @@ -91,7 +96,7 @@ public final class RateService { var svcResponse = ServletUtil.GSON.toJson(response); return HttpResponse.ok(svcResponse); } catch (Exception e) { - return ServicesUtil.handleError(e, service.name, request.getUri().getPath()); + return ServletUtil.error(LOG, e, service.name, request.getUri().getPath()); } } @@ -271,7 +276,7 @@ public final class RateService { final List<Sequence> data; ResponseData(ResponseMetadata metadata, EqRate rates, Stopwatch timer) { - server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer); + server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer); this.metadata = metadata; this.data = buildSequence(rates); } @@ -330,7 +335,7 @@ public final class RateService { private Usage(Service service, DefaultParameters parameters) { description = service.description; this.syntax = service.syntax; - server = Metadata.serverData(1, Stopwatch.createStarted()); + server = ServletUtil.serverData(1, Stopwatch.createStarted()); this.parameters = parameters; } } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java index 3d0dfe0a7055376d3b5e764e252f23ca80f4f265..01e2d4c2186ce6f09a74094576a6b9f489a34e11 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java @@ -1,7 +1,11 @@ package gov.usgs.earthquake.nshmp.www.services; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import gov.usgs.earthquake.nshmp.model.Models; import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ServletUtil; import gov.usgs.earthquake.nshmp.www.SourceLogicTreesController; import io.micronaut.http.HttpRequest; @@ -16,6 +20,8 @@ import jakarta.inject.Singleton; @Singleton public class SourceLogicTreesService { + static final Logger LOG = LoggerFactory.getLogger(SourceLogicTreesService.class); + private static final String NAME = "Source Logic Trees"; /** SourceLogicTreesController.doGetMetadata() handler */ @@ -32,7 +38,7 @@ public class SourceLogicTreesService { .build(); return HttpResponse.ok(ServletUtil.GSON.toJson(response)); } catch (Exception e) { - return ServicesUtil.handleError(e, NAME, url); + return ServletUtil.error(LOG, e, NAME, url); } } @@ -51,7 +57,7 @@ public class SourceLogicTreesService { .build(); return HttpResponse.ok(ServletUtil.GSON.toJson(response)); } catch (Exception e) { - return ServicesUtil.handleError(e, NAME, url); + return ServletUtil.error(LOG, e, NAME, url); } } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java index 83910b2fc623cd7b27e81855da5263435cb87d65..b473fd92f80d3cc808aa1419511dfa247b729d2e 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java @@ -1,8 +1,14 @@ package gov.usgs.earthquake.nshmp.www.services; +import static java.util.stream.Collectors.toList; + +import java.util.List; import java.util.Map; import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.google.common.base.Stopwatch; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -12,8 +18,9 @@ import gov.usgs.earthquake.nshmp.gmm.Imt; import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass; import gov.usgs.earthquake.nshmp.model.HazardModel; import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ServletUtil; import gov.usgs.earthquake.nshmp.www.WsUtils; -import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Parameter; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; @@ -33,6 +40,8 @@ public class SourceServices { private static final String SERVICE_DESCRIPTION = "Utilities for querying earthquake source models"; + static final Logger LOG = LoggerFactory.getLogger(RateService.class); + public static final Gson GSON; static { @@ -56,7 +65,7 @@ public class SourceServices { var json = GSON.toJson(response); return HttpResponse.ok(json); } catch (Exception e) { - return ServicesUtil.handleError(e, NAME, url); + return ServletUtil.error(LOG, e, NAME, url); } } @@ -71,7 +80,9 @@ public class SourceServices { public ResponseData() { this.description = "Installed source model listing"; - this.server = Metadata.serverData(ServletUtil.THREAD_COUNT, Stopwatch.createStarted()); + this.server = ServletUtil.serverData( + ServletUtil.THREAD_COUNT, + Stopwatch.createStarted()); // this.parameters = new Parameters(); } } @@ -104,11 +115,19 @@ public class SourceServices { String name; Set<Gmm> gmms; Map<NehrpSiteClass, Double> siteClasses; + List<Parameter> imts; - SourceModel(HazardModel model) { + public SourceModel(HazardModel model) { name = model.name(); gmms = model.gmms(); siteClasses = model.siteClasses(); + imts = model.gmms().stream() + .map(Gmm::supportedImts) + .flatMap(Set::stream) + .distinct() + .sorted() + .map(imt -> new Parameter(ServletUtil.imtShortLabel(imt), imt.name())) + .collect(toList()); } // public static List<SourceModel> getList() { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d4d11ef0c76a2efa5215bacfdf56dc4f1d56d3e9..5ca3350641b37f5b8d35d94bb59f1bea3dc5b00b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,4 +20,6 @@ nshmp-haz: # The path to the models. # To specify the model to use: # java -jar build/libs/nshmp-haz.jar --models=<path/to/models> + # model-path: ${models:libs/nshmp-haz-dep--nshm-hi-2021} + # model-path: ${models:libs/nshmp-haz-dep--nshm-conus-2018}