diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java deleted file mode 100644 index 048b8bcfd52e099363a5962f43a45bc6e3f31ab4..0000000000000000000000000000000000000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java +++ /dev/null @@ -1,60 +0,0 @@ -package gov.usgs.earthquake.nshmp.netcdf; - -import java.io.IOException; -import java.nio.file.Path; -import java.util.List; -import java.util.Map; - -import gov.usgs.earthquake.nshmp.data.XySequence; -import gov.usgs.earthquake.nshmp.geo.Location; -import gov.usgs.earthquake.nshmp.gmm.Imt; - -public interface Netcdf { - - /** - * Returns a {@code NetCdf} reader for 2018 hazard curves. - * - * @param path The path to the 2018 Netcdf hazard file - * @throws IOException - */ - public static Netcdf create2018Reader(Path path) { - return new Netcdf2018Reader(path); - } - - /** - * Returns the Netcdf dimensions and coordinate variables. - */ - NetcdfCoordinates coordinates(); - - /** - * Returns the path to the Netcdf file. - */ - Path path(); - - /** - * Return the full set of hazard curves at the bounding grid points and the - * target Location. Intended for validation uses. - * - * @param site The location to get bouning hazards - */ - List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazards(Location site); - - /** - * Return a Map<SiteClass, Map<Imt, XySequence>> with hazard curves at the - * specified Location. - * - * @param site The site to get the hazard curves - */ - Map<SiteClass, Map<Imt, XySequence>> hazard(Location site); - - /** - * Return a single hazard curve for the specified Location, SiteClass, and - * Imt. - * - * @param site The site to get the hazard curves - * @param siteClass The site class - * @param imt The IMT - */ - XySequence hazard(Location site, SiteClass siteClass, Imt imt); - -} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NshmGroup.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NshmGroup.java new file mode 100644 index 0000000000000000000000000000000000000000..b6470018837709aa169efb38df4104774e019b2b --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NshmGroup.java @@ -0,0 +1,21 @@ +package gov.usgs.earthquake.nshmp.netcdf; + +public enum NshmGroup { + + NSHM_CONUS_2018("/CONUS/2018/v1.1"); + + private final String baseGroup; + + private NshmGroup(String baseGroup) { + this.baseGroup = baseGroup; + } + + /** + * Returns the Netcdf target base group to read. + */ + @Override + public String toString() { + return baseGroup; + } + +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf2018Reader.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NshmNetcdfReader.java similarity index 57% rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf2018Reader.java rename to src/main/java/gov/usgs/earthquake/nshmp/netcdf/NshmNetcdfReader.java index 0e482fdb1bc193223d6c638896110e85b3fae7e2..6cbdd8d62f23031955771b7a00367ad953b62bc9 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf2018Reader.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NshmNetcdfReader.java @@ -5,10 +5,14 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import gov.usgs.earthquake.nshmp.data.XySequence; import gov.usgs.earthquake.nshmp.geo.Location; import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.netcdf.reader.BoundingHazards; +import gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfCoordinates; import ucar.nc2.dataset.NetcdfDataset; @@ -33,14 +37,21 @@ import ucar.nc2.dataset.NetcdfDataset; * * @author U.S. Geological Survey */ -class Netcdf2018Reader implements Netcdf { +public class NshmNetcdfReader { - private static final String BASE_GROUP = "/CONUS/2018/v1.1"; + private static final Logger LOGGER = Logger.getLogger("ucar"); private final Path path; + private final NshmGroup nshmGroup; private final NetcdfCoordinates coords; - Netcdf2018Reader(Path path) { + static { + /* Update ucar logger */ + LOGGER.setLevel(Level.SEVERE); + } + + public NshmNetcdfReader(NshmGroup nshmGroup, Path path) { + this.nshmGroup = nshmGroup; this.path = path; if (Files.notExists(path)) { @@ -52,34 +63,62 @@ class Netcdf2018Reader implements Netcdf { // - Handle metadata from netCDF file get root group and // attributes, and attributes of subgroups // - Get netCDF dimensions - var targetGroup = ncd.findGroup(BASE_GROUP); + var targetGroup = ncd.findGroup(nshmGroup.toString()); coords = new NetcdfCoordinates(targetGroup); } catch (IOException e) { throw new RuntimeException("Could not read Netcdf file [" + path + " ]"); } } - @Override + /** + * Returns the NSHM NetCDF group + */ + public NshmGroup nshmGroup() { + return nshmGroup; + } + + /** + * Returns the NetCDF dimensions and coordinate variables. + */ public NetcdfCoordinates coordinates() { return coords; } - @Override + /** + * Returns the path to the NetCDF file. + */ public Path path() { return path; } - @Override + /** + * Return the full set of hazard curves at the bounding grid points and the + * target Location. Intended for validation uses. + * + * @param site The location to get bounding hazards + */ public List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazards(Location site) { - return BoundingHazards.boundingHazards(this, site, BASE_GROUP); + return BoundingHazards.boundingHazards(this, site); } - @Override + /** + * Return a Map<SiteClass, Map<Imt, XySequence>> with hazard curves at the + * specified Location. + * + * @param site The site to get the hazard curves + */ public Map<SiteClass, Map<Imt, XySequence>> hazard(Location site) { return boundingHazards(site).get(4); } - @Override + /** + * Return a single hazard curve for the specified Location, SiteClass, and + * Imt. + * + * @param site The site to get the hazard curves + * @param siteClass The site class + * @param imt The IMT + */ public XySequence hazard(Location site, SiteClass siteClass, Imt imt) { return hazard(site).get(siteClass).get(imt); } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/BoundingHazards.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingHazards.java similarity index 87% rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/BoundingHazards.java rename to src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingHazards.java index f89e2cf7898fb92411ac7209c331636b7d78ff50..769db9dcd5c805f1e409c33ad65c7bba58025d6b 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/BoundingHazards.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingHazards.java @@ -1,4 +1,4 @@ -package gov.usgs.earthquake.nshmp.netcdf; +package gov.usgs.earthquake.nshmp.netcdf.reader; import java.io.IOException; import java.util.ArrayList; @@ -11,7 +11,9 @@ import com.google.common.collect.Maps; import gov.usgs.earthquake.nshmp.data.XySequence; import gov.usgs.earthquake.nshmp.geo.Location; import gov.usgs.earthquake.nshmp.gmm.Imt; -import gov.usgs.earthquake.nshmp.netcdf.NetcdfUtils.Key; +import gov.usgs.earthquake.nshmp.netcdf.NshmNetcdfReader; +import gov.usgs.earthquake.nshmp.netcdf.SiteClass; +import gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils.Key; import ucar.ma2.Array; import ucar.ma2.DataType; @@ -23,29 +25,38 @@ import ucar.nc2.dataset.NetcdfDataset; /* * Container for gridded hazard curves at four closest grid points to target */ -class BoundingHazards { +public class BoundingHazards { - final Netcdf netcdf; - final String baseGroup; - final NetcdfCoordinates coords; + private final NshmNetcdfReader netcdf; + private final NetcdfCoordinates coords; + private List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazards; - private int idxLonLL; - private int idxLatLL; - - final List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazards; - - private BoundingHazards(Netcdf netcdf, Location site, String baseGroup) { + private BoundingHazards(NshmNetcdfReader netcdf, Location site) { this.netcdf = netcdf; - this.baseGroup = baseGroup; this.coords = netcdf.coordinates(); + coords.contains(site); + setBoundingHazards(site); + } - coords.checkCoords(site); + /** + * Returns the bounding hazards at four closet grid points to target. + * + * @param netcdf The {@code Netcdf} + * @param site The site to get bounding hazards + * @param baseGroup The Netcdf base group + */ + public static List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazards( + NshmNetcdfReader netcdf, + Location site) { + return new BoundingHazards(netcdf, site).boundingHazards; + } + private void setBoundingHazards(Location site) { var longitudes = coords.locations().stream().mapToDouble(loc -> loc.longitude).toArray(); var latitudes = coords.locations().stream().mapToDouble(loc -> loc.latitude).toArray(); - idxLonLL = NetcdfUtils.getIdxLTEQ(longitudes, site.longitude); - idxLatLL = NetcdfUtils.getIdxLTEQ(latitudes, site.latitude); + var idxLonLL = NetcdfUtils.getIdxLTEQ(longitudes, site.longitude); + var idxLatLL = NetcdfUtils.getIdxLTEQ(latitudes, site.latitude); boundingHazards = extractHazardsAt(idxLonLL, idxLatLL); @@ -54,20 +65,6 @@ class BoundingHazards { boundingHazards.add(calcTargetHazards(fracLon, fracLat)); } - /** - * Returns the bounding hazards at four closet grid points to target. - * - * @param netcdf The {@code Netcdf} - * @param site The site to get bounding hazards - * @param baseGroup The Netcdf base group - */ - static List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazards( - Netcdf netcdf, - Location site, - String baseGroup) { - return new BoundingHazards(netcdf, site, baseGroup).boundingHazards; - } - private Map<SiteClass, Map<Imt, XySequence>> calcTargetHazards(double fracLon, double fracLat) { var westTarget = getTargetData(boundingHazards.get(0), boundingHazards.get(1), fracLat); var eastTarget = getTargetData(boundingHazards.get(3), boundingHazards.get(2), fracLat); @@ -82,11 +79,10 @@ class BoundingHazards { * empty slot for the interpolated target hazards */ private List<Map<SiteClass, Map<Imt, XySequence>>> extractHazardsAt(int idxLonLL, int idxLatLL) { - var boundingHazardMaps = new ArrayList<Map<SiteClass, Map<Imt, XySequence>>>(5); try (NetcdfDataset ncd = NetcdfDataset.openDataset(netcdf.path().toString())) { - Group targetGroup = ncd.findGroup(baseGroup); + Group targetGroup = ncd.findGroup(netcdf.nshmGroup().toString()); // TODO: rename variable in netCDF Variable vHazards = targetGroup.findVariable(Key.AEPS); diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfCoordinates.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfCoordinates.java similarity index 67% rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfCoordinates.java rename to src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfCoordinates.java index 43c6d3910479b17840b13dd4fe7ba4eee2fba764..9627d73d4229c5574605704d764c89cd2a32a3ad 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfCoordinates.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfCoordinates.java @@ -1,4 +1,4 @@ -package gov.usgs.earthquake.nshmp.netcdf; +package gov.usgs.earthquake.nshmp.netcdf.reader; import static com.google.common.base.Preconditions.checkArgument; @@ -13,8 +13,11 @@ import com.google.common.collect.Maps; import gov.usgs.earthquake.nshmp.geo.Location; import gov.usgs.earthquake.nshmp.geo.LocationList; +import gov.usgs.earthquake.nshmp.geo.Region; +import gov.usgs.earthquake.nshmp.geo.Regions; import gov.usgs.earthquake.nshmp.gmm.Imt; -import gov.usgs.earthquake.nshmp.netcdf.NetcdfUtils.Key; +import gov.usgs.earthquake.nshmp.netcdf.SiteClass; +import gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils.Key; import ucar.ma2.DataType; import ucar.ma2.InvalidRangeException; @@ -22,7 +25,7 @@ import ucar.nc2.Group; import ucar.nc2.Variable; /* - * Container for dimensions and coordinate variables of nshmp netCDF file to + * Container for dimensions and coordinate variables of NSHMP NetCDF file to * facilitate indexing operations prior to data retrieval. */ public class NetcdfCoordinates { @@ -32,23 +35,17 @@ public class NetcdfCoordinates { private final LocationList locations; private final Map<SiteClass, Map<Imt, double[]>> imls; private final int nIml; + private final Region region; - NetcdfCoordinates(Group targetGroup) throws IOException { + public NetcdfCoordinates(Group targetGroup) throws IOException { // This bypasses the netCDF dimensions, but since we know what the // variables and their dimensions should be, this is OK(???) - // get coordinate variables - var vVs30s = targetGroup.findVariable(Key.VS30); - var vImts = targetGroup.findVariable(Key.IMT); - var vlats = targetGroup.findVariable(Key.LAT); - var vlons = targetGroup.findVariable(Key.LON); var vImls = targetGroup.findVariable(Key.IMLS); - - // read coordinate variables into java arrays - var vs30s = (int[]) vVs30s.read().get1DJavaArray(DataType.INT); - var imtVals = (double[]) vImts.read().get1DJavaArray(DataType.DOUBLE); - var lats = (double[]) vlats.read().get1DJavaArray(DataType.DOUBLE); - var lons = (double[]) vlons.read().get1DJavaArray(DataType.DOUBLE); + var vs30s = NetcdfUtils.getIntArray(targetGroup, Key.VS30); + var imtVals = NetcdfUtils.getDoubleArray(targetGroup, Key.IMT); + var lats = NetcdfUtils.getDoubleArray(targetGroup, Key.LAT); + var lons = NetcdfUtils.getDoubleArray(targetGroup, Key.LON); var builder = LocationList.builder(); for (int i = 0; i < lons.length; i++) { @@ -56,6 +53,7 @@ public class NetcdfCoordinates { } locations = builder.build(); + region = Regions.createRectangular("Bounds", locations.bounds().min, locations.bounds().max); // vImls has dimensions (vs30, Imt, Iml) // alternatively get nIml from Dimension Iml @@ -76,42 +74,51 @@ public class NetcdfCoordinates { imls = mapImls(vImls); } - LocationList locations() { + /** + * Returns the locations associated with a {@code NshmGroup} + */ + public LocationList locations() { return LocationList.copyOf(locations); } - int nIml() { + /** + * Returns the number of Imls associated with a {@code NshmGroup}. + */ + public int nIml() { return nIml; } - List<SiteClass> siteClasses() { + /** + * Return the site classes associated with a {@code NshmGroup}. + */ + public List<SiteClass> siteClasses() { return List.copyOf(siteClasses); } - List<Imt> imt() { + /** + * Return the Imts associated with a {@code NshmGroup}. + */ + public List<Imt> imt() { return List.copyOf(imts); } - Map<SiteClass, Map<Imt, double[]>> iml() { + /** + * Returns the Imls associated with a {@code NshmGroup}. + */ + public Map<SiteClass, Map<Imt, double[]>> iml() { return imls; } - /* - * validate target coordinates + /** + * Validate a target site is contained with in the bounds. + * + * @param site The site to test */ - void checkCoords(Location site) { + public void contains(Location site) { var bounds = locations.bounds(); - var minLon = bounds.min.longitude; - var maxLon = bounds.max.longitude; - var minLat = bounds.min.latitude; - var maxLat = bounds.max.latitude; - var lon = site.longitude; - var lat = site.latitude; - checkArgument( - (lon >= minLon && lon <= maxLon && lat >= minLat && lat <= maxLat), - String.format("Target coordinates out of range, %.3f <= lon < %.3f, %.3f <= lat <= %.3f", - minLon, maxLat, minLat, maxLat)); + region.contains(site), + String.format("Target site [%s] out of range %s", site.toString(), bounds.toString())); } /* @@ -123,7 +130,6 @@ public class NetcdfCoordinates { * hazard curves... */ private Map<SiteClass, Map<Imt, double[]>> mapImls(Variable vImls) { - EnumMap<SiteClass, Map<Imt, double[]>> vsImtImlMap = Maps.newEnumMap(SiteClass.class); for (int i = 0; i < siteClasses.size(); i++) { @@ -138,7 +144,8 @@ public class NetcdfCoordinates { var shape = new int[] { 1, 1, nIml }; try { - imtImlMap.put(imt, + imtImlMap.put( + imt, (double[]) vImls.read(origin, shape).reduce().get1DJavaArray(DataType.DOUBLE)); } catch (IOException | InvalidRangeException e) { var msg = "Failed read attempt for vImls with origin: " + diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfUtils.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java similarity index 72% rename from src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfUtils.java rename to src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java index 28a712c6a78df6240272657a9b8687661eb6fcd6..3e7d1b15ee044c75029db56bd2748de7b7f47004 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfUtils.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java @@ -1,5 +1,8 @@ -package gov.usgs.earthquake.nshmp.netcdf; +package gov.usgs.earthquake.nshmp.netcdf.reader; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.IOException; import java.util.Arrays; import java.util.Map; @@ -8,9 +11,52 @@ import com.google.common.math.DoubleMath; import gov.usgs.earthquake.nshmp.data.XySequence; import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.netcdf.SiteClass; + +import ucar.ma2.DataType; +import ucar.nc2.Group; class NetcdfUtils { + /** + * Returns a {@code double[]} from a NetCDF group + * + * @param group The NetCDF group + * @param key The key to read from the group + * @throws IOException + */ + static double[] getDoubleArray(Group group, String key) throws IOException { + return (double[]) get1DArray(group, key, DataType.DOUBLE); + } + + /** + * Returns a {@code int[]} from a NetCDF group + * + * @param group The NetCDF group + * @param key The key to read from the group + * @throws IOException + */ + static int[] getIntArray(Group group, String key) throws IOException { + return (int[]) get1DArray(group, key, DataType.INT); + } + + /** + * Get a 1D array from a NetCDF group. + * + * @param group The NetCDF group + * @param key The key to read from the group + * @param dataType The data type to read + * @throws IOException + */ + static Object get1DArray(Group group, String key, DataType dataType) throws IOException { + var var = group.findVariable(key); + checkNotNull( + var, + String.format("Could not find variable [%s] in group [%s]", key, group.getFullName())); + + return var.read().get1DJavaArray(dataType); + } + /* * find index of first element in a (sorted ascending) that is less than or * equal to target value t. If target value is equal to the maximum value in a