diff --git a/gradle.properties b/gradle.properties index 16974b50fae880645864579e4917a5f250a21ca0..2812326520132211d8037e24b13cb7a04058feff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,7 +9,7 @@ micronautAppVersion = 3.7.10 micronautVersion = 3.10.1 nodePluginVersion = 3.0.1 nodeVersion = 16.3.0 -nshmpLibVersion = 1.5.6 +nshmpLibVersion = 1.5.8 nshmpUtilsJavaVersion = 0.4.0 openApiVersion = 4.0.0 shadowVersion = 7.1.2 diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateController.java index 1ad8fa730622f2cf06cfbbceb64b6a522d5d2fcf..0a40d124a64588f58ee4488ffae6bf549b54d318 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateController.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateController.java @@ -6,7 +6,6 @@ import gov.usgs.earthquake.nshmp.www.ServletUtil; import gov.usgs.earthquake.nshmp.www.source.RateService.Metadata; import gov.usgs.earthquake.nshmp.www.source.RateService.Request; import gov.usgs.earthquake.nshmp.www.source.RateService.Response; - import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import io.micronaut.http.MediaType; @@ -97,6 +96,42 @@ public class RateController { } } + /** + * @param longitude Longitude in decimal degrees in the range + * @param latitude Latitude in decimal degrees in the range + * @param distance Cutoff distance in the range [0.01, 1000] km. + */ + @Operation( + summary = "Compute annual earthquake rates", + description = "Compute incremental annual earthquake rates at a location", + operationId = "rate-calc") + @ApiResponse( + description = "Earthquake annual rate calculation response", + responseCode = "200", + content = @Content( + schema = @Schema(implementation = RateResponse.class))) + @Get( + uri = "/dev/{longitude}/{latitude}/{distance}", + produces = MediaType.APPLICATION_JSON) + public HttpResponse<String> doGetRateDev( + HttpRequest<?> http, + @PathVariable double longitude, + @PathVariable double latitude, + @Schema( + minimum = "0.01", + maximum = "1000") @PathVariable double distance) { + try { + RateService.Request request = new RateService.Request( + http, longitude, latitude, distance); + return RateService.getRateDev(request); + } catch (Exception e) { + return ServletUtil.error( + RateService.LOG, e, + RateService.NAME_RATE, + http.getUri().getPath()); + } + } + // Swagger schema private static class RateResponse extends ResponseBody<Request, Response> {} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateService.java index 59246530d7358c50a78d24ac760eb1e7bc0a6424..6f8f5cacb7afc3c1174b422c4461135fd74bd727 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateService.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateService.java @@ -7,7 +7,6 @@ import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLongitude; import java.util.ArrayList; import java.util.List; import java.util.OptionalDouble; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import org.slf4j.Logger; @@ -15,13 +14,16 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Stopwatch; import com.google.common.collect.Range; -import com.google.common.util.concurrent.ListenableFuture; import gov.usgs.earthquake.nshmp.calc.CalcConfig; import gov.usgs.earthquake.nshmp.calc.EqRate; 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.Location; import gov.usgs.earthquake.nshmp.model.HazardModel; +import gov.usgs.earthquake.nshmp.model.SourceType; +import gov.usgs.earthquake.nshmp.model.TectonicSetting; import gov.usgs.earthquake.nshmp.www.HazVersion; import gov.usgs.earthquake.nshmp.www.ResponseBody; import gov.usgs.earthquake.nshmp.www.ResponseMetadata; @@ -29,7 +31,6 @@ import gov.usgs.earthquake.nshmp.www.ServletUtil; import gov.usgs.earthquake.nshmp.www.ServletUtil.Server; import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; import gov.usgs.earthquake.nshmp.www.source.SourceService.SourceModel; - import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; import jakarta.inject.Singleton; @@ -58,6 +59,25 @@ public final class RateService { private static final String TOTAL_KEY = "Total"; + public static HttpResponse<String> getRateDev(Request request) { + var stopwatch = Stopwatch.createStarted(); + var rates = calcRate(request); + var response = new Response.Builder() + .timer(stopwatch) + .request(request) + .ratesDev(rates) + .build(); + var body = ResponseBody.success() + .name(NAME_RATE) + .url(request.http.getUri().getPath()) + .metadata(new ResponseMetadata(HazVersion.appVersions(ServletUtil.model().root()))) + .request(request) + .response(response) + .build(); + String json = ServletUtil.GSON2.toJson(body); + return HttpResponse.ok(json); + } + public static HttpResponse<String> getRate(Request request) { var stopwatch = Stopwatch.createStarted(); var rates = calcRate(request); @@ -107,59 +127,38 @@ public final class RateService { private static EqRate calc(Request request, OptionalDouble timespan) { var location = Location.create(request.longitude, request.latitude); var site = Site.builder().location(location).build(); - var futureRates = new ArrayList<ListenableFuture<EqRate>>(); - - /* - * Because we need to combine model results, intially calculate incremental - * annual rates and only convert to cumulative probabilities at the end if - * probability service has been called. - */ - - var model = ServletUtil.model(); - var rate = process(model, site, request.distance, timespan); - futureRates.add(rate); - - var rates = futureRates.stream() - .map((future) -> { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - }).collect(Collectors.toList()) - .toArray(new EqRate[] {}); - - var ratesCombined = EqRate.combine(rates); + HazardModel model = ServletUtil.model(); + EqRate rate = process(model, site, request.distance, timespan); if (timespan.isPresent()) { - ratesCombined = EqRate.toCumulative(ratesCombined); - ratesCombined = EqRate.toPoissonProbability(ratesCombined, timespan.orElseThrow()); + rate = EqRate.toCumulative(rate); + rate = EqRate.toPoissonProbability(rate, timespan.orElseThrow()); } - return ratesCombined; + return rate; } - private static ListenableFuture<EqRate> process( + private static EqRate process( HazardModel model, Site site, double distance, OptionalDouble timespan) { - var configBuilder = CalcConfig.copyOf(model.config()).distance(distance); + + CalcConfig.Builder configBuilder = CalcConfig.copyOf(model.config()).distance(distance); if (timespan.isPresent()) { /* Also sets value format to Poisson probability. */ configBuilder.timespan(timespan.getAsDouble()); } - var config = configBuilder.build(); - var task = EqRate.callable(model, config, site); - return ServletUtil.CALC_EXECUTOR.submit(task); + CalcConfig config = configBuilder.build(); + return EqRate.create(model, config, site); } static class Response { final Metadata metadata; - final List<Sequence> data; + final List<?> data; - Response(Metadata metadata, List<Sequence> data) { + Response(Metadata metadata, List<?> data) { this.metadata = metadata; this.data = data; } @@ -168,7 +167,7 @@ public final class RateService { return metadata; } - public List<Sequence> getData() { + public List<?> getData() { return data; } @@ -198,7 +197,7 @@ public final class RateService { Stopwatch timer; Request request; - List<Sequence> data; + List<?> data; Builder timer(Stopwatch timer) { this.timer = timer; @@ -215,6 +214,11 @@ public final class RateService { return this; } + Builder ratesDev(EqRate rates) { + this.data = buildSequencesDev(rates); + return this; + } + private List<Sequence> buildSequences(EqRate rates) { var sequences = new ArrayList<Sequence>(); @@ -243,6 +247,30 @@ public final class RateService { return List.copyOf(sequences); } + private List<SequenceDev> buildSequencesDev(EqRate rates) { + var sequences = new ArrayList<SequenceDev>(); + + /* Total mfd. */ + var total = (!rates.totalMfd.isClear()) ? rates.totalMfd.trim() : rates.totalMfd; + var totalOut = new SequenceDev(TOTAL_KEY, null, null, null, total); + sequences.add(totalOut); + + MutableXySequence totalMfdCheck = MutableXySequence.emptyCopyOf(rates.totalMfd); + + /* Source type mfds. */ + for (EqRate.Contributor contrib : rates.mfds) { + var contribOut = new SequenceDev( + contrib.component, + contrib.id, + contrib.setting, + contrib.type, + contrib.mfd.trim()); + sequences.add(contribOut); + totalMfdCheck.add(contrib.mfd); + } + return sequences; + } + Response build() { Server server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer); return new Response(new Response.Metadata(server), data); @@ -255,6 +283,7 @@ public final class RateService { * HazardService.Curve */ private static class Sequence { + final String component; final List<Double> xvalues; final List<Double> yvalues; @@ -278,6 +307,49 @@ public final class RateService { } } + private static class SequenceDev { + + final String component; + final Integer id; + final TectonicSetting setting; + final SourceType type; + final XySequence values; + + SequenceDev( + String component, + Integer id, + TectonicSetting setting, + SourceType type, + XySequence values) { + + this.component = component; + this.id = id; + this.setting = setting; + this.type = type; + this.values = values; + } + + public String getComponent() { + return component; + } + + public Integer getId() { + return id; + } + + public TectonicSetting getSetting() { + return setting; + } + + public SourceType getSourceType() { + return type; + } + + public XySequence getValues() { + return values; + } + } + private static final Range<Double> DISTANCE_RANGE = Range.closed(0.01, 1000.0); private static final Range<Double> TIMESPAN_RANGE = Range.closed(1.0, 10000.0);