From 3e340cfdc47696f97610636c7358f91792b84e38 Mon Sep 17 00:00:00 2001 From: Peter Powers <pmpowers@usgs.gov> Date: Wed, 10 Nov 2021 11:59:17 -0700 Subject: [PATCH] placeholder class for updates to hazard service --- .../nshmp/www/services/HazardService2.java | 443 ++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService2.java 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 new file mode 100644 index 000000000..f63e1f330 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService2.java @@ -0,0 +1,443 @@ +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 javax.inject.Singleton; + +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.Response; +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.meta.Status; +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; + +/** + * 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 = new Response(Status.USAGE, NAME, url, usage, url); + 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); + + Response<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 Response<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; + } + + Response<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 new Response<>(Status.SUCCESS, NAME, request, response, url); + } + } + + 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"); + // } + } + +} -- GitLab