From 1c0e9cb90ceea169b5649e87f3def275b194e072 Mon Sep 17 00:00:00 2001
From: Brandon Clayton <bclayton@usgs.gov>
Date: Fri, 29 Jul 2022 09:51:56 -0600
Subject: [PATCH] Handle single netcdf file

---
 .../netcdf/NetcdfDataFilesHazardCurves.java   |  52 ++++---------
 .../netcdf/www/NetcdfServiceHazardCurves.java |  72 +++++++++---------
 .../nshmp/netcdf/www/SwaggerHazardCurves.java |  65 ++++++++--------
 src/hazard/src/main/resources/application.yml |   4 +-
 .../src/main/resources/hazard-example.nc      | Bin 10332874 -> 10332981 bytes
 5 files changed, 81 insertions(+), 112 deletions(-)

diff --git a/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataFilesHazardCurves.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataFilesHazardCurves.java
index 6078488..c429b83 100644
--- a/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataFilesHazardCurves.java
+++ b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataFilesHazardCurves.java
@@ -1,16 +1,13 @@
 package gov.usgs.earthquake.nshmp.netcdf;
 
-import java.io.FileNotFoundException;
-import java.io.IOException;
 import java.nio.file.Path;
 import java.util.List;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 
 /**
- * Read in and parse all hazard NetCDF data files.
+ * Read in and parse a single hazard NetCDF data file.
  *
  * @author U.S. Geological Survey
  */
@@ -22,54 +19,31 @@ public class NetcdfDataFilesHazardCurves extends NetcdfDataFiles<NetcdfHazardCur
   }
 
   /**
-   * Returns the set of NEHRP site classes for all data files.
+   * Returns the set of IMTs.
    */
   public Set<Imt> imts() {
-    return stream()
-        .flatMap(netcdf -> netcdf.netcdfData().imts().stream())
-        .sorted()
-        .collect(Collectors.toSet());
+    return Set.copyOf(netcdf().netcdfData().imts());
   }
 
   /**
-   * Returns the Netcdf object associated with a location.
-   *
-   * @param location The location to find the NetCDF object
-   * @throws IllegalArgumentException If no location is contained in a Netcdf
-   *         object.
+   * Returns the Netcdf object.
    */
-  public NetcdfHazardCurves netcdf(Nshm nshm) {
+  public NetcdfHazardCurves netcdf() {
     return stream()
-        .filter(netcdf -> netcdf.nshm() == nshm)
         .findFirst()
-        .orElseThrow(() -> new IllegalArgumentException(
-            String.format("NSHM %s not found in data sets.", nshm)));
-  }
-
-  /**
-   * Returns the {@link Nshm NSHMs} associated with the data sets read in.
-   */
-  public Set<Nshm> nshms() {
-    return stream()
-        .map(netcdf -> netcdf.nshm())
-        .sorted()
-        .collect(Collectors.toSet());
+        .orElseThrow(() -> new IllegalArgumentException("Data set not found"));
   }
 
   @Override
   protected List<NetcdfHazardCurves> readFiles(Path netcdfPath, NetcdfDataType dataType) {
-    try {
-      List<NetcdfHazardCurves> data = walkFiles(netcdfPath, dataType)
-          .map(NetcdfHazardCurves::new)
-          .collect(Collectors.toList());
+    NetcdfDataType fileDataType = NetcdfDataType.getDataType((netcdfPath));
 
-      if (data.isEmpty()) {
-        throw new FileNotFoundException("Failed to find hazard NetCDF files");
-      }
+    if (!fileDataType.equals(dataType)) {
+      throw new RuntimeException(
+          String.format("Data type %s of file must be of type %s", fileDataType, dataType));
+    } ;
 
-      return List.copyOf(data);
-    } catch (IOException e) {
-      throw new RuntimeException("Failed to read NetCDF directory " + netcdfPath, e);
-    }
+    NetcdfHazardCurves data = new NetcdfHazardCurves(netcdfPath);
+    return List.of(data);
   }
 }
diff --git a/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java
index 74d1f52..6984e67 100644
--- a/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java
+++ b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfServiceHazardCurves.java
@@ -9,15 +9,15 @@ import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfDataFilesHazardCurves;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfHazardCurves;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfVersion;
-import gov.usgs.earthquake.nshmp.netcdf.Nshm;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticDataHazardCurves;
 import gov.usgs.earthquake.nshmp.netcdf.www.HazardMetadata.HazardResponseMetadata;
+import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfService.SourceModel;
 import gov.usgs.earthquake.nshmp.netcdf.www.NetcdfWsUtils.Key;
 import gov.usgs.earthquake.nshmp.netcdf.www.Query.Service;
-import gov.usgs.earthquake.nshmp.netcdf.www.RequestHazardCurves.HazardRequestData;
+import gov.usgs.earthquake.nshmp.netcdf.www.Request.RequestData;
+import gov.usgs.earthquake.nshmp.netcdf.www.Request.RequestDataSiteClass;
 import gov.usgs.earthquake.nshmp.netcdf.www.RequestHazardCurves.HazardRequestDataImt;
-import gov.usgs.earthquake.nshmp.netcdf.www.RequestHazardCurves.HazardRequestDataSiteClass;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
 import gov.usgs.earthquake.nshmp.www.ResponseMetadata;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
@@ -31,7 +31,8 @@ import io.micronaut.http.HttpRequest;
  *
  * @author U.S. Geological Survey
  */
-public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
+public class NetcdfServiceHazardCurves extends
+    NetcdfService<NetcdfServiceHazardCurves.HazardSourceModel, HazardQuery> {
 
   static final String SERVICE_DESCRIPTION = "Get static hazard curves from a NetCDF file";
   static final String X_LABEL = "Ground Motion (g)";
@@ -42,9 +43,10 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
   }
 
   @Override
-  ResponseBody<String, Metadata<HazardQuery>> getMetadataResponse(HttpRequest<?> request) {
-    var metadata = new Metadata<HazardQuery>(request, this, SERVICE_DESCRIPTION);
-    return ResponseBody.<String, Metadata<HazardQuery>> usage()
+  ResponseBody<String, Metadata<HazardSourceModel, HazardQuery>> getMetadataResponse(
+      HttpRequest<?> request) {
+    var metadata = new Metadata<HazardSourceModel, HazardQuery>(request, this, SERVICE_DESCRIPTION);
+    return ResponseBody.<String, Metadata<HazardSourceModel, HazardQuery>> usage()
         .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(getServiceName())
         .request(NetcdfWsUtils.getRequestUrl(request))
@@ -66,14 +68,12 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
 
   @Override
   String getServiceName() {
-    return "Static Hazard Curves for NSHMs";
+    return netcdfDataFiles().netcdf().netcdfData().scienceBaseMetadata().label;
   }
 
   @Override
-  List<SourceModel> getSourceModels() {
-    return netcdfDataFiles().stream()
-        .map(HazardSourceModel::new)
-        .collect(Collectors.toList());
+  HazardSourceModel getSourceModels() {
+    return new HazardSourceModel(netcdfDataFiles().netcdf());
   }
 
   /**
@@ -82,8 +82,8 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
    *
    * @param nshm The NSHM to get the NetCDF data
    */
-  NetcdfHazardCurves netcdf(Nshm nshm) {
-    return netcdfDataFiles().netcdf(nshm);
+  NetcdfHazardCurves netcdf() {
+    return netcdfDataFiles().netcdf();
   }
 
   @Override
@@ -91,16 +91,16 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
     return (NetcdfDataFilesHazardCurves) netcdfDataFiles;
   }
 
-  ResponseBody<HazardRequestData, List<List<ResponseData<HazardResponseMetadata>>>> processCurves(
-      HazardRequestData request,
+  ResponseBody<RequestData, List<List<ResponseData<HazardResponseMetadata>>>> processCurves(
+      RequestData request,
       String url) {
     WsUtils.checkValue(Key.LATITUDE, request.latitude);
     WsUtils.checkValue(Key.LONGITUDE, request.longitude);
-    var curves = netcdf(request.nshm).staticData(request.site);
+    var curves = netcdf().staticData(request.site);
     var curvesAsList = toList(request, curves);
 
     return ResponseBody
-        .<HazardRequestData, List<List<ResponseData<HazardResponseMetadata>>>> success()
+        .<RequestData, List<List<ResponseData<HazardResponseMetadata>>>> success()
         .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(getServiceName())
         .request(request)
@@ -109,17 +109,17 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
         .build();
   }
 
-  ResponseBody<HazardRequestDataSiteClass, List<ResponseData<HazardResponseMetadata>>> processCurvesSiteClass(
-      HazardRequestDataSiteClass request,
+  ResponseBody<RequestDataSiteClass, List<ResponseData<HazardResponseMetadata>>> processCurvesSiteClass(
+      RequestDataSiteClass request,
       String url) {
     WsUtils.checkValue(Key.LATITUDE, request.latitude);
     WsUtils.checkValue(Key.LONGITUDE, request.longitude);
     WsUtils.checkValue(Key.SITE_CLASS, request.siteClass);
-    var curves = netcdf(request.nshm).staticData(request.site, request.siteClass);
+    var curves = netcdf().staticData(request.site, request.siteClass);
     var curvesAsList = toList(request, curves);
 
     return ResponseBody
-        .<HazardRequestDataSiteClass, List<ResponseData<HazardResponseMetadata>>> success()
+        .<RequestDataSiteClass, List<ResponseData<HazardResponseMetadata>>> success()
         .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(getServiceName())
         .request(request)
@@ -128,16 +128,16 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
         .build();
   }
 
-  ResponseBody<HazardRequestDataSiteClass, ResponseData<HazardResponseMetadata>> processCurvesImt(
+  ResponseBody<RequestDataSiteClass, ResponseData<HazardResponseMetadata>> processCurvesImt(
       HazardRequestDataImt request,
       String url) {
     WsUtils.checkValue(Key.LATITUDE, request.latitude);
     WsUtils.checkValue(Key.LONGITUDE, request.longitude);
     WsUtils.checkValue(Key.SITE_CLASS, request.siteClass);
     WsUtils.checkValue(Key.IMT, request.imt);
-    var curves = netcdf(request.nshm).staticData(request.site, request.siteClass);
+    var curves = netcdf().staticData(request.site, request.siteClass);
 
-    return ResponseBody.<HazardRequestDataSiteClass, ResponseData<HazardResponseMetadata>> success()
+    return ResponseBody.<RequestDataSiteClass, ResponseData<HazardResponseMetadata>> success()
         .metadata(new ResponseMetadata(NetcdfVersion.appVersions()))
         .name(getServiceName())
         .request(request)
@@ -154,15 +154,15 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
 
     switch (service) {
       case CURVES:
-        var requestData = new HazardRequestData(query.nshm, site, query.format);
+        var requestData = new RequestData(site, query.format);
         return toResponseFromListOfList(processCurves(requestData, url));
       case CURVES_BY_SITE_CLASS:
         var requestDataSiteClass =
-            new HazardRequestDataSiteClass(query.nshm, site, query.siteClass, query.format);
+            new RequestDataSiteClass(site, query.siteClass, query.format);
         return toResponseFromList(processCurvesSiteClass(requestDataSiteClass, url));
       case CURVES_BY_IMT:
         var requestDataImt =
-            new HazardRequestDataImt(query.nshm, site, query.siteClass, query.imt, query.format);
+            new HazardRequestDataImt(site, query.siteClass, query.imt, query.format);
         return toResponse(processCurvesImt(requestDataImt, url));
       default:
         throw new RuntimeException("Netcdf service [" + service + "] not found");
@@ -170,7 +170,7 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
   }
 
   private String toResponseFromListOfList(
-      ResponseBody<HazardRequestData, List<List<ResponseData<HazardResponseMetadata>>>> serviceResponse) {
+      ResponseBody<RequestData, List<List<ResponseData<HazardResponseMetadata>>>> serviceResponse) {
     if (serviceResponse.getRequest().format == ResponseFormat.CSV) {
       var csvResponse =
           toCsvFromListOfList(serviceResponse.getRequest(), serviceResponse.getResponse());
@@ -180,14 +180,14 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
     }
   }
 
-  private String toCsvFromListOfList(HazardRequestData requestData,
+  private String toCsvFromListOfList(RequestData requestData,
       List<List<ResponseData<HazardResponseMetadata>>> responseData) {
     return responseData.stream().map(responses -> toCsvResponseFromList(requestData, responses))
         .collect(Collectors.joining("\n\n"));
   }
 
   List<ResponseData<HazardResponseMetadata>> toList(
-      HazardRequestDataSiteClass request,
+      RequestDataSiteClass request,
       StaticDataHazardCurves curves) {
     return curves.entrySet().stream()
         .map((entry) -> toResponseData(request, entry.getKey(), entry.getValue()))
@@ -195,12 +195,12 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
   }
 
   List<List<ResponseData<HazardResponseMetadata>>> toList(
-      HazardRequestData requestData,
+      RequestData requestData,
       StaticData<StaticDataHazardCurves> curves) {
     return curves.entrySet().stream()
         .map(entry -> {
           var request =
-              new HazardRequestDataSiteClass(requestData.nshm, requestData.site, entry.getKey(),
+              new RequestDataSiteClass(requestData.site, entry.getKey(),
                   requestData.format);
           return toList(request, entry.getValue());
         })
@@ -208,21 +208,19 @@ public class NetcdfServiceHazardCurves extends NetcdfService<HazardQuery> {
   }
 
   ResponseData<HazardResponseMetadata> toResponseData(
-      HazardRequestDataSiteClass request,
+      RequestDataSiteClass request,
       Imt imt,
       XySequence curves) {
     var metadata =
-        new HazardResponseMetadata(request.nshm, request.siteClass, imt, X_LABEL, Y_LABEL);
+        new HazardResponseMetadata(request.siteClass, imt, X_LABEL, Y_LABEL);
     return new ResponseData<>(metadata, curves);
   }
 
   static class HazardSourceModel extends SourceModel {
-    public final Nshm nshm;
     public final List<Imt> imts;
 
     HazardSourceModel(NetcdfHazardCurves netcdf) {
       super(netcdf);
-      nshm = netcdf.nshm();
       imts = netcdf.netcdfData().imts().stream().sorted().collect(Collectors.toList());
     }
   }
diff --git a/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerHazardCurves.java b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerHazardCurves.java
index 2e2d592..64e880c 100644
--- a/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerHazardCurves.java
+++ b/src/hazard/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerHazardCurves.java
@@ -2,16 +2,12 @@ package gov.usgs.earthquake.nshmp.netcdf.www;
 
 import java.io.IOException;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
-import gov.usgs.earthquake.nshmp.netcdf.Nshm;
+import gov.usgs.earthquake.nshmp.netcdf.data.ScienceBaseMetadata;
 import gov.usgs.earthquake.nshmp.www.SwaggerUtils;
 
 import io.micronaut.http.HttpRequest;
 import io.swagger.v3.oas.models.OpenAPI;
-import io.swagger.v3.oas.models.media.Schema;
-import io.swagger.v3.parser.util.SchemaTypeUtil;
 
 /**
  * Swagger page updates for hazard services.
@@ -24,16 +20,30 @@ public class SwaggerHazardCurves extends Swagger<NetcdfServiceHazardCurves> {
     super(request, service);
   }
 
+  @Override
+  String description() {
+    StringBuilder builder = new StringBuilder();
+
+    service.netcdfDataFiles()
+        .forEach(netcdf -> {
+          ScienceBaseMetadata metadata = netcdf.netcdfData().scienceBaseMetadata();
+          builder
+              .append(metadata.description + "\n")
+              .append(parameterSection(netcdf.netcdfData()) + "\n")
+              .append(scienceBaseSection(metadata) + "\n");
+        });
+
+    return builder.toString();
+  }
+
   @Override
   String descriptionHeader() {
-    return "NSHM Data Sets";
+    return "";
   }
 
   @Override
   String serviceInfo() {
-    return String.join("",
-        "Get hazard curves for user-specified NSHM, latitudes, ",
-        "longitudes, and/or site classes of the National Seismic Hazard Models data releases.");
+    return "";
   }
 
   @Override
@@ -51,18 +61,17 @@ public class SwaggerHazardCurves extends Swagger<NetcdfServiceHazardCurves> {
                 "The query based service call is in the form of:\n" +
 
                 "```text\n" +
-                url + "/hazard?nshm={nshm}&longitude={number}&latitude={number}\n" +
-                url + "/hazard" +
-                "?nshm={nshm}&longitude={number}&latitude={number}&siteClass={string}\n" +
+                url + "/hazard?longitude={number}&latitude={number}\n" +
+                url + "/hazard?longitude={number}&latitude={number}&siteClass={string}\n" +
                 url + "/hazard" +
-                "?nshm={nshm}&longitude={number}&latitude={number}&siteClass={string}&imt={string}\n" +
+                "?longitude={number}&latitude={number}&siteClass={string}&imt={string}\n" +
                 "````\n" +
 
                 "Example:\n" +
                 "```text\n" +
-                url + "/hazard?nshm=CONUS_2018&longitude=-118&latitude=34\n" +
-                url + "/hazard?nshm=CONUS_2018&longitude=-118&latitude=34&siteClass=BC\n" +
-                url + "/hazard?nshm=CONUS_2018&longitude=-118&latitude=34&siteClass=BC&imt=PGA\n" +
+                url + "/hazard?longitude=-118&latitude=34\n" +
+                url + "/hazard?longitude=-118&latitude=34&siteClass=BC\n" +
+                url + "/hazard?longitude=-118&latitude=34&siteClass=BC&imt=PGA\n" +
                 "```\n")
         .append(
             "### Slash Pattern\n" +
@@ -70,17 +79,17 @@ public class SwaggerHazardCurves extends Swagger<NetcdfServiceHazardCurves> {
                 "The slash based service call is in the form of:\n" +
 
                 "```text\n" +
-                url + "/hazard/{nshm}/{longitude}/{latitude}\n" +
-                url + "/hazard/{nshm}/{longitude}/{latitude}/{siteClass}\n" +
-                url + "/hazard/{nshm}/{longitude}/{latitude}/{siteClass}/{imt}\n" +
+                url + "/hazard/{longitude}/{latitude}\n" +
+                url + "/hazard/{longitude}/{latitude}/{siteClass}\n" +
+                url + "/hazard/{longitude}/{latitude}/{siteClass}/{imt}\n" +
                 "```\n" +
 
                 "Example:\n" +
 
                 "```text\n" +
-                url + "/hazard/CONUS_2018/-118/34\n" +
-                url + "/hazard/CONUS_2018/-118/34/BC\n" +
-                url + "/hazard/CONUS_2018/-118/34/BC/PGA\n" +
+                url + "/hazard/-118/34\n" +
+                url + "/hazard/-118/34/BC\n" +
+                url + "/hazard/-118/34/BC/PGA\n" +
                 "```\n")
         .append("</details>")
         .toString();
@@ -94,18 +103,6 @@ public class SwaggerHazardCurves extends Swagger<NetcdfServiceHazardCurves> {
         openApi.getComponents().getSchemas(),
         List.copyOf(service.netcdfDataFiles().imts()));
 
-    nshmSchema(openApi.getComponents().getSchemas(), service.netcdfDataFiles().nshms());
-
     return openApi;
   }
-
-  private void nshmSchema(Map<String, Schema> schemas, Set<Nshm> nshms) {
-    Schema<Nshm> schema = new Schema<>();
-    schema.setType(SchemaTypeUtil.STRING_TYPE);
-    nshms.stream()
-        .sorted()
-        .forEach(nshm -> schema.addEnumItemObject(nshm));
-
-    schemas.put(Nshm.class.getSimpleName(), schema);
-  }
 }
diff --git a/src/hazard/src/main/resources/application.yml b/src/hazard/src/main/resources/application.yml
index 1455ba2..eb7e65e 100644
--- a/src/hazard/src/main/resources/application.yml
+++ b/src/hazard/src/main/resources/application.yml
@@ -16,5 +16,5 @@ micronaut:
         logger-name: http
 
 nshmp-ws-static:
-  # Path to directory holding NetCDF files
-  netcdf-path: ${netcdf:src/main/resources}
+  # Path to hazard NetCDF file
+  netcdf-path: ${netcdf:src/main/resources/hazard-example.nc}
diff --git a/src/hazard/src/main/resources/hazard-example.nc b/src/hazard/src/main/resources/hazard-example.nc
index 884b1262c63574f58ac7399fdeb3d2c43606fb28..1969150bcb1f553bd4e701ed35ea61c4fd8a96f9 100644
GIT binary patch
delta 3445
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
WfS-bs;Ah}v@CtbK+;sn`%Krgqn*c)q

delta 3129
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
KJXtY(toDBojfb58

-- 
GitLab