diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/BoundingHazards.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/BoundingHazards.java new file mode 100644 index 0000000000000000000000000000000000000000..f89e2cf7898fb92411ac7209c331636b7d78ff50 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/BoundingHazards.java @@ -0,0 +1,217 @@ +package gov.usgs.earthquake.nshmp.netcdf; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +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 ucar.ma2.Array; +import ucar.ma2.DataType; +import ucar.ma2.InvalidRangeException; +import ucar.nc2.Group; +import ucar.nc2.Variable; +import ucar.nc2.dataset.NetcdfDataset; + +/* + * Container for gridded hazard curves at four closest grid points to target + */ +class BoundingHazards { + + final Netcdf netcdf; + final String baseGroup; + final NetcdfCoordinates coords; + + private int idxLonLL; + private int idxLatLL; + + final List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazards; + + private BoundingHazards(Netcdf netcdf, Location site, String baseGroup) { + this.netcdf = netcdf; + this.baseGroup = baseGroup; + this.coords = netcdf.coordinates(); + + coords.checkCoords(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); + + boundingHazards = extractHazardsAt(idxLonLL, idxLatLL); + + var fracLon = NetcdfUtils.calcGridFrac(longitudes, idxLonLL, site.longitude); + var fracLat = NetcdfUtils.calcGridFrac(latitudes, idxLatLL, site.latitude); + 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); + return getTargetData(westTarget, eastTarget, fracLon); + } + + /* + * Return hazard curves for four closest grid points as a List of: + * Map<SiteClass, Map<Imt, XySequence(iml,hazard)>> + * + * List order is clockwise from lower left corner: LL, UL, UR, LR, [T] with an + * 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); + + // TODO: rename variable in netCDF + Variable vHazards = targetGroup.findVariable(Key.AEPS); + + // set up origin and shape arrays for reading hazard curves at four + // bounding + // (lon,lat) grid points + int[] origin = new int[] { 0, 0, idxLatLL, idxLonLL, 0 }; + int[] shape = new int[] { + coords.siteClasses().size(), + coords.imt().size(), + 2, + 2, + coords.nIml() + }; + + // read data into Array for bounding grid points + var aHazards = vHazards.read(origin, shape); + + // Array aHazards now has shape [nVs,nImt,2,2,nIml] + // ...so origin will now be [0,0,0,0,0] for LL grid point + // ...and shape of requested array is [nVs,nImt,1,1,nIml] + + // Array aHazards is size 2 on the lon and lat dimensions + shape[2] = 1; + shape[3] = 1; + + // TODO: XySequence instead of double[] + + // Put data into hazard map for LL point + origin[2] = 0; // lat index position + origin[3] = 0; // lon index position + boundingHazardMaps.add(mapHazardsFromArray(aHazards.section(origin, shape))); + // Put data into hazard map for UL point + origin[2] = 1; // lat index position + origin[3] = 0; // lon index position + boundingHazardMaps.add(mapHazardsFromArray(aHazards.section(origin, shape))); + // Put data into hazard map for UR point + origin[2] = 1; // lat index position + origin[3] = 1; // lon index position + boundingHazardMaps.add(mapHazardsFromArray(aHazards.section(origin, shape))); + // Put data into hazard map for LR point + origin[2] = 0; // lat index position + origin[3] = 1; // lon index position + + boundingHazardMaps.add(mapHazardsFromArray(aHazards.section(origin, shape))); + } catch (IOException | InvalidRangeException e) { + throw new RuntimeException("Could not read Netcdf file [" + netcdf.path() + "]"); + } + + // interpolate hazards to target point + return boundingHazardMaps; + + } + + /* + * Read hazard curves from netCDF variable into map of hazards by SiteClass + * and Imt + * + * TODO: if target is on a grid point (or on a grid lat or lon), no need to + * read 4 bounding points ? + */ + private Map<SiteClass, Map<Imt, XySequence>> mapHazardsFromArray(Array hazards) { + // hazards has had its lat and lon dimensions reduced (so it now is rank 3) + // hazards[SiteClass, Imt, Iml] + + EnumMap<SiteClass, Map<Imt, XySequence>> vsImtHazardMap = Maps.newEnumMap(SiteClass.class); + + for (int iSc = 0; iSc < coords.siteClasses().size(); iSc++) { + var sc = coords.siteClasses().get(iSc); + + Map<Imt, XySequence> imtHazardMap = Maps.newEnumMap(Imt.class); + for (int iImt = 0; iImt < coords.imt().size(); iImt++) { + Imt imt = coords.imt().get(iImt); + + // set origin and shape of double[] hazards to read + var origin = new int[] { iSc, iImt, 0 }; + var shape = new int[] { 1, 1, coords.nIml() }; + + // try block needed for sectioning Array + try { + imtHazardMap.put(imt, + XySequence.create( + coords.iml().get(sc).get(imt), + (double[]) hazards.section(origin, shape).reduce() + .get1DJavaArray(DataType.DOUBLE))); + } catch (InvalidRangeException e) { + throw new RuntimeException(e.getMessage()); + } + } + + vsImtHazardMap.put(sc, imtHazardMap); + } + + return Maps.immutableEnumMap(vsImtHazardMap); + } + + /* + * Get data for target point + * + * @param d1 data at first point (p1) + * + * @param d2 data at second point (p2) + * + * @param frac fractional distance between p1 and p2 to target point + */ + private static Map<SiteClass, Map<Imt, XySequence>> getTargetData( + Map<SiteClass, Map<Imt, XySequence>> d1, + Map<SiteClass, Map<Imt, XySequence>> d2, + double frac) { + // do we need better checking here? or is it safe to assume that every + // Map<SiteClass, Map<Imt,double[]>> passed in is consistent? + if (d1.size() != d2.size()) { + throw new IllegalArgumentException("Array size disagreement, cannot interpolate"); + } + + if (frac == 0.0) { + // target is the same as d1 + return d1; + } else if (frac == 1.0) { + // target is the same as d2 + return d2; + } else { + return NetcdfUtils.linearInterpolate(d1, d2, frac); + } + } + +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java new file mode 100644 index 0000000000000000000000000000000000000000..048b8bcfd52e099363a5962f43a45bc6e3f31ab4 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java @@ -0,0 +1,60 @@ +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/Netcdf2018Reader.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf2018Reader.java new file mode 100644 index 0000000000000000000000000000000000000000..0e482fdb1bc193223d6c638896110e85b3fae7e2 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf2018Reader.java @@ -0,0 +1,87 @@ +package gov.usgs.earthquake.nshmp.netcdf; + +import java.io.IOException; +import java.nio.file.Files; +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; + +import ucar.nc2.dataset.NetcdfDataset; + +/** + * + * Hazard curves stored in netCDF + * + * This class is currently specific to NSHMP 2018a data release. + * + * TODO: generalize for any NSHMP model data release with subclasses per data + * release. + * + * TODO: Replace loops with streams + * + * TODO: Utilize XySequence.copyOf() + * + * TODO: Update netCDF file format, rename variables, implement enums, + * standardize attributes, remove Vs30 dimension from Imls + * + * TODO: attempt to calculate index, possibly as + * NshmpNetcdfCoordinates.calcIdxLTEQ() + * + * @author U.S. Geological Survey + */ +class Netcdf2018Reader implements Netcdf { + + private static final String BASE_GROUP = "/CONUS/2018/v1.1"; + + private final Path path; + private final NetcdfCoordinates coords; + + Netcdf2018Reader(Path path) { + this.path = path; + + if (Files.notExists(path)) { + throw new IllegalArgumentException("Path to Netcdf file [" + path + "] does not exisit"); + } + + try (var ncd = NetcdfDataset.openDataset(path.toString())) { + // TODO: + // - Handle metadata from netCDF file get root group and + // attributes, and attributes of subgroups + // - Get netCDF dimensions + var targetGroup = ncd.findGroup(BASE_GROUP); + coords = new NetcdfCoordinates(targetGroup); + } catch (IOException e) { + throw new RuntimeException("Could not read Netcdf file [" + path + " ]"); + } + } + + @Override + public NetcdfCoordinates coordinates() { + return coords; + } + + @Override + public Path path() { + return path; + } + + @Override + public List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazards(Location site) { + return BoundingHazards.boundingHazards(this, site, BASE_GROUP); + } + + @Override + public Map<SiteClass, Map<Imt, XySequence>> hazard(Location site) { + return boundingHazards(site).get(4); + } + + @Override + 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/NetcdfCoordinates.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfCoordinates.java new file mode 100644 index 0000000000000000000000000000000000000000..43c6d3910479b17840b13dd4fe7ba4eee2fba764 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfCoordinates.java @@ -0,0 +1,156 @@ +package gov.usgs.earthquake.nshmp.netcdf; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.io.IOException; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +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.gmm.Imt; +import gov.usgs.earthquake.nshmp.netcdf.NetcdfUtils.Key; + +import ucar.ma2.DataType; +import ucar.ma2.InvalidRangeException; +import ucar.nc2.Group; +import ucar.nc2.Variable; + +/* + * Container for dimensions and coordinate variables of nshmp netCDF file to + * facilitate indexing operations prior to data retrieval. + */ +public class NetcdfCoordinates { + + private final List<SiteClass> siteClasses; + private final List<Imt> imts; + private final LocationList locations; + private final Map<SiteClass, Map<Imt, double[]>> imls; + private final int nIml; + + 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 builder = LocationList.builder(); + for (int i = 0; i < lons.length; i++) { + builder.add(lats[i], lons[i]); + } + + locations = builder.build(); + + // vImls has dimensions (vs30, Imt, Iml) + // alternatively get nIml from Dimension Iml + nIml = vImls.getDimension(2).getLength(); // Imls.length; + + // convert Vs30<int> to SiteClass Enum + // TODO: site class should be stored as enum in netCDF file rather than + // vs30 value, so this will break: + siteClasses = Arrays.stream(vs30s) + .mapToObj(vs30 -> SiteClass.ofValue(vs30)) + .collect(Collectors.toUnmodifiableList()); + + // TODO: Imt should be stored in netCDF as enum, so this will break: + imts = Arrays.stream(imtVals) + .mapToObj(x -> (x < 0.009) ? Imt.PGA : Imt.fromPeriod(x)) + .collect(Collectors.toUnmodifiableList()); + + imls = mapImls(vImls); + } + + LocationList locations() { + return LocationList.copyOf(locations); + } + + int nIml() { + return nIml; + } + + List<SiteClass> siteClasses() { + return List.copyOf(siteClasses); + } + + List<Imt> imt() { + return List.copyOf(imts); + } + + Map<SiteClass, Map<Imt, double[]>> iml() { + return imls; + } + + /* + * validate target coordinates + */ + void checkCoords(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)); + } + + /* + * convert 3D Iml variable (dimensions vs30, Imt, Iml) to map of Imls by + * SiteClass and Imt + * + * TODO: use MultiMap or SetMultiMap (etc.) to store unique IML sets? Could + * then also initialize the underlying XySequence objects for reading in the + * 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++) { + var sc = siteClasses.get(i); + + Map<Imt, double[]> imtImlMap = Maps.newEnumMap(Imt.class); + for (int j = 0; j < imts.size(); j++) { + var imt = imts.get(j); + + // set origin and shape of double[] Imls to read + var origin = new int[] { i, j, 0 }; + var shape = new int[] { 1, 1, nIml }; + + try { + 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: " + + Arrays.toString(origin) + ", shape: " + Arrays.toString(shape); + throw new RuntimeException(msg); + } + } + + vsImtImlMap.put(sc, imtImlMap); + } + + return Maps.immutableEnumMap(vsImtImlMap); + } + +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfUtils.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..28a712c6a78df6240272657a9b8687661eb6fcd6 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfUtils.java @@ -0,0 +1,123 @@ +package gov.usgs.earthquake.nshmp.netcdf; + +import java.util.Arrays; +import java.util.Map; + +import com.google.common.collect.Maps; +import com.google.common.math.DoubleMath; + +import gov.usgs.earthquake.nshmp.data.XySequence; +import gov.usgs.earthquake.nshmp.gmm.Imt; + +class NetcdfUtils { + + /* + * 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 + * (the last element), an index of a.length - 1 is returned. + */ + static int getIdxLTEQ(double[] a, double t) { + // assumes array is in sorted order (a[i] < a[i+1]) + // make sure target is within the range of the array + var tol = 0.000001; + var n = a.length; + + if (t < a[0] || t > a[n - 1]) { + // should never get here thanks to NshmpNetcdfCoordinates.checkCoords() + throw new IllegalArgumentException( + String.format("Target outside of valid range: %.4f <= t <= %4f", a[0], a[n - 1])); + } + + // if (t == a[0]) { + if (DoubleMath.fuzzyEquals(a[0], t, tol)) { + return 0; + } + // if (t == a[n - 1]) { + if (DoubleMath.fuzzyEquals(a[n - 1], t, tol)) { + return n - 2; // return second to last index number + } + + var idx = Arrays.binarySearch(a, t); + if (idx < 0) { + // "exact" match not found, so use index of first value less than target + // this is insertion_point - 1 + // returned idx = -insertion_point - 1 + idx = -1 * (idx + 1) - 1; + // array[idx] <= target + } + + return idx; + } + + /* + * Calculate fractional distance from a1 to t, between a1 and a2 + */ + static double calcFrac(double a1, double a2, double t) { + var tol = 0.00001; + + if (Math.abs(t - a1) < tol) { + // target value == a1 + return 0.0; + } else if (Math.abs(t - a2) < tol) { + // target value == a2 + return 1.0; + } else { + // calculate fractional distance to t between a[i] and a[i+1] + return (t - a1) / (a2 - a1); + } + } + + /* + * Calculate fractional distance from a[i] to t, between a[i] and a[i+1] + */ + static double calcGridFrac(double[] a, int i, double t) { + return calcFrac(a[i], a[i + 1], t); + } + + /* + * Linear interpolation of data values to a target point + * + * TODO: use XySequence.copyOf(), streams + */ + static Map<SiteClass, Map<Imt, XySequence>> linearInterpolate( + Map<SiteClass, Map<Imt, XySequence>> v1, + Map<SiteClass, Map<Imt, XySequence>> v2, + double frac) { + // return v1 * (1 - frac) + v2 * frac + // DoubleData routines add and multiply in place, we don't want to modify + // the original data here + if (v1.size() != v2.size()) { + throw new IllegalArgumentException("Array size disagreement, cannot interpolate"); + } + + Map<SiteClass, Map<Imt, XySequence>> tMap = Maps.newEnumMap(SiteClass.class); + + for (SiteClass sc : v1.keySet()) { + Map<Imt, XySequence> imtHazards = Maps.newEnumMap(Imt.class); + + for (Imt imt : v1.get(sc).keySet()) { + var v1Haz = v1.get(sc).get(imt).yValues().toArray(); + var v2Haz = v2.get(sc).get(imt).yValues().toArray(); + var t = new double[v1Haz.length]; + + for (int i = 0; i < v1Haz.length; i++) { + t[i] = v1Haz[i] * (1 - frac) + v2Haz[i] * frac; + } + imtHazards.put(imt, XySequence.create(v1.get(sc).get(imt).xValues().toArray(), t)); + } + tMap.put(sc, imtHazards); + } + return tMap; + } + + static class Key { + static final String AEPS = "AEPs"; + static final String IMLS = "Imls"; + static final String IMT = "Imt"; + static final String LAT = "lat"; + static final String LON = "lon"; + static final String POE = "Poe"; + static final String SPACING = "spacing"; + static final String VS30 = "vs30"; + } +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NshmpNetcdfReader.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NshmpNetcdfReader.java deleted file mode 100644 index 12e099c60f1075286a7407bec0f75680dcc1fe2e..0000000000000000000000000000000000000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NshmpNetcdfReader.java +++ /dev/null @@ -1,926 +0,0 @@ -package gov.usgs.earthquake.nshmp.netcdf; - -import static com.google.common.base.Preconditions.checkArgument; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - -import ucar.ma2.Array; -import ucar.ma2.DataType; -import ucar.ma2.InvalidRangeException; -import ucar.nc2.Group; -import ucar.nc2.Variable; -import ucar.nc2.dataset.NetcdfDataset; - -import com.google.common.collect.Maps; -import com.google.common.math.DoubleMath; - -import gov.usgs.earthquake.nshmp.data.XySequence; -import gov.usgs.earthquake.nshmp.geo.Location; -import gov.usgs.earthquake.nshmp.gmm.Imt; - -/** - * - * Interface to static hazard curves stored in netCDF - * - * This class is currently specific to NSHMP 2018a data release. - * - * TODO: generalize for any NSHMP model data release with subclasses per data - * release. - * - * TODO: Replace loops with streams - * - * TODO: Utilize XySequence.copyOf() - * - * TODO: Update netCDF file format, rename variables, implement enums, - * standardize attributes, remove Vs30 dimension from Imls - * - * @author U.S. Geological Survey - */ -public class NshmpNetcdfReader { - - final String nshmpNetcdfFilename; // path to netCDF file - final String nshmpBaseGroup; // netCDF group path to model data - - final NshmpNetcdfCoordinates nshmpCoords; - - private Logger logger; - - /** - * @param nshmpNetcdfFilename - * @param nshmpBaseGroup - * @throws IOException - */ - public NshmpNetcdfReader(String nshmpNetcdfFilename) throws IOException { - - this.nshmpNetcdfFilename = nshmpNetcdfFilename; - - // NSHMP 2018a data netCDF group path to data - nshmpBaseGroup = "/CONUS/2018/v1.1"; - - // Set netcdf-java logging level - // WARNING is default - // SEVERE disables warnings - logger = Logger.getLogger("ucar"); - logger.setLevel(Level.SEVERE); - - // Open netCDF file - // TODO: switch to try-with-resource block (solve errors when initializing - // final fields) - NetcdfDataset ncd = NetcdfDataset.openDataset(this.nshmpNetcdfFilename); - - // TODO: handle metadata from netCDF file - // get root group and attributes, and attributes of subgroups - - // TODO: get netCDF dimensions - - // get target data group - Group targetGroup = ncd.findGroup(nshmpBaseGroup); - - // get coordinate variables - Variable vVs30 = targetGroup.findVariable("vs30"); - Variable vImts = targetGroup.findVariable("Imt"); - Variable vlats = targetGroup.findVariable("lat"); - Variable vlons = targetGroup.findVariable("lon"); - Variable vImls = targetGroup.findVariable("Imls"); - // TODO: hazard map variable will be renamed in netCDF file - Variable vhazardLevels = targetGroup.findVariable("Poe"); - // or, for shared variables or dimensions: - // Variable vLat = targetGroup.findVariableOrInParent("lat"); - - // store coordinate variables in nshmpCoords - nshmpCoords = new NshmpNetcdfCoordinates(vVs30, vImts, vlats, vlons, vImls, vhazardLevels); - - // Close NetcdfDataset - // TODO: avoid this with try-with-resource block - if (null != ncd) try { - ncd.close(); - } catch (IOException ioe) { - logger.log(Level.SEVERE, "Failed trying to close " + this.nshmpNetcdfFilename, ioe); - } - - } - - /* - * Return the full set of hazard curves at the bounding grid points and the - * target Location. Intended for validation uses. - */ - public static List<Map<SiteClass, Map<Imt, XySequence>>> getBoundingHazards( - NshmpNetcdfReader nshmpNCRD, Location site) { - BoundingHazards boundingHazards = new BoundingHazards(nshmpNCRD, site); - - return boundingHazards.boundingHazardsList; - } - - /* - * Return a Map<SiteClass, Map<Imt, XySequence>> with hazard curves at the - * specified Location. - */ - public static Map<SiteClass, Map<Imt, XySequence>> getHazardAtSite(NshmpNetcdfReader nshmpNCRD, - Location site) { - List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazardsList = - getBoundingHazards(nshmpNCRD, site); - return boundingHazardsList.get(4); - } - - /* - * Return a single hazard curve for the specified Location, SiteClass, and - * Imt. - */ - public static XySequence getHazardCurve(NshmpNetcdfReader nshmpNCRD, Location site, SiteClass sc, - Imt imt) { - Map<SiteClass, Map<Imt, XySequence>> hazardCurves = getHazardAtSite(nshmpNCRD, site); - return hazardCurves.get(sc).get(imt); - } - - /* - * Container for dimensions and coordinate variables of nshmp netCDF file to - * facilitate indexing operations prior to data retrieval. - */ - private class NshmpNetcdfCoordinates { - - final int[] vs30s; - final List<SiteClass> siteClassEnums; - - final double[] imtVals; - final List<Imt> imts; - - final double[] lats; - final double[] lons; - - final Map<SiteClass, Map<Imt, double[]>> imlMap; - final double[] hazardLevels; - - final double maxLon; - final double minLon; - final double maxLat; - final double minLat; - final double deltaLon; - final double deltaLat; - - final int nVs; - final int nImt; - final int nLat; - final int nLon; - final int nIml; - final int nHazardLevel; - - /** - * @param vVs30s - * @param vImts - * @param vlats - * @param vlons - * @param vImls - * @param vHazardLevels - * @throws IOException - */ - public NshmpNetcdfCoordinates( - Variable vVs30s, - Variable vImts, - Variable vlats, - Variable vlons, - Variable vImls, - Variable vHazardLevels) throws IOException { - // This bypasses the netCDF dimensions, but since we know what the - // variables and their dimensions should be, this is OK(???) - - // read coordinate variables into java arrays - vs30s = (int[]) vVs30s.read().get1DJavaArray(DataType.INT); - imtVals = (double[]) vImts.read().get1DJavaArray(DataType.DOUBLE);; - lats = (double[]) vlats.read().get1DJavaArray(DataType.DOUBLE);; - lons = (double[]) vlons.read().get1DJavaArray(DataType.DOUBLE);; - hazardLevels = (double[]) vHazardLevels.read().get1DJavaArray(DataType.DOUBLE); - - // assumes lons and lats are sorted ascending - maxLon = lons[lons.length - 1]; - minLon = lons[0]; - maxLat = lats[lats.length - 1]; - minLat = lats[0]; - - // get lat/lon spacing --> this is currently a required attribute but is - // not used - deltaLon = (double) vlons.findAttribute("spacing").getNumericValue(); - deltaLat = (double) vlats.findAttribute("spacing").getNumericValue(); - - nVs = vs30s.length; - nImt = imtVals.length; - nLat = lats.length; - nLon = lons.length; - nHazardLevel = hazardLevels.length; - - // vImls has dimensions (vs30, Imt, Iml) - // alternatively get nIml from Dimension Iml - nIml = vImls.getDimension(2).getLength(); // Imls.length; - - // convert Vs30<int> to SiteClass Enum - // TODO: site class should be stored as enum in netCDF file rather than - // vs30 value, so this will break: - siteClassEnums = Arrays.stream(vs30s) - .mapToObj(x -> mapVsToEnum2018(x)) - .collect(Collectors.toUnmodifiableList()); - - // convert Imt<double> to Imt Enum - // TODO: Imt should be stored in netCDF as enum, so this will break: - imts = Arrays.stream(imtVals) - .mapToObj(x -> (x < 0.009) ? Imt.PGA : Imt.fromPeriod(x)) - .collect(Collectors.toUnmodifiableList()); - - // Extract Imls to Map - // vImls is a 3-dimensional array with dims (vs30, Imt, Iml) - // Array ImlsArray = vImls.read(); - // int[] imlDims = ImlsArray.getShape(); - - imlMap = mapImls(vImls); - } - - /* - * validate target coordinates - */ - void checkCoords(double lon, double lat) { - 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)); - } - - void checkCoords(Location targetSite) { - checkCoords(targetSite.longitude, targetSite.latitude); - } - - /* - * convert 3D Iml variable (dimensions vs30, Imt, Iml) to map of Imls by - * SiteClass and Imt - * - * TODO: use MultiMap or SetMultiMap (etc.) to store unique IML sets? Could - * then also initialize the underlying XySequence objects for reading in the - * hazard curves... - */ - private Map<SiteClass, Map<Imt, double[]>> mapImls(Variable vImls) { - - // initialize - EnumMap<SiteClass, Map<Imt, double[]>> vsImtImlMap = Maps.newEnumMap(SiteClass.class); - - for (int i = 0; i < siteClassEnums.size(); i++) { - SiteClass sc = siteClassEnums.get(i); - - Map<Imt, double[]> imtImlMap = Maps.newEnumMap(Imt.class); - for (int j = 0; j < imts.size(); j++) { - Imt imt = imts.get(j); - - // set origin and shape of double[] Imls to read - int[] origin = new int[] { i, j, 0 }; - int[] shape = new int[] { 1, 1, nIml }; - - try { - imtImlMap.put(imt, - (double[]) vImls.read(origin, shape).reduce().get1DJavaArray(DataType.DOUBLE)); - } catch (IOException | InvalidRangeException e) { - System.err.println("Failed read attempt for vImls with origin: " + - Arrays.toString(origin) + ", shape: " + Arrays.toString(shape)); - e.printStackTrace(); - } - } - - vsImtImlMap.put(sc, imtImlMap); - } - - return Maps.immutableEnumMap(vsImtImlMap); - } - - private Location getLoc(int idxLon, int idxLat) { - return Location.create(lats[idxLat], lons[idxLon]); - } - } - - /* - * Container for gridded hazard curves at four closest grid points to target - */ - static class BoundingHazards { - - final NshmpNetcdfReader ncrd; - final Location site; - - private int idxLonLL; - private int idxLatLL; - private List<Location> boundingSites; - - final double fracLon; - final double fracLat; - - final List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazardsList; - // final Map<SiteClass, Map<Imt, double[]>> targetHazards; - - BoundingHazards(NshmpNetcdfReader ncrd, Location site) { - this.ncrd = ncrd; - this.site = site; - - // validate target coordinates - ncrd.nshmpCoords.checkCoords(this.site); - - // Compute indices for lower left bounding grid point - idxLonLL = getIdxLTEQ(ncrd.nshmpCoords.lons, this.site.longitude); - idxLatLL = getIdxLTEQ(ncrd.nshmpCoords.lats, this.site.latitude); - - // Get bounding grid points: LL, UL, UR, LR - boundingSites = new ArrayList<Location>(4); - boundingSites.add(ncrd.nshmpCoords.getLoc(idxLonLL(), idxLatLL())); - boundingSites.add(ncrd.nshmpCoords.getLoc(idxLonUL(), idxLatUL())); - boundingSites.add(ncrd.nshmpCoords.getLoc(idxLonUR(), idxLatUR())); - boundingSites.add(ncrd.nshmpCoords.getLoc(idxLonLR(), idxLatLR())); - - // Compute grid fractions to target point - fracLon = calcGridFrac(ncrd.nshmpCoords.lons, idxLonLL(), site.longitude); - fracLat = calcGridFrac(ncrd.nshmpCoords.lats, idxLatLL(), site.latitude); - - boundingHazardsList = this.ncrd.extractHazardsAt(idxLonLL, idxLatLL); - - // targetHazards = calcTargetHazards(); - boundingHazardsList.add(calcTargetHazards()); - - } - - private Map<SiteClass, Map<Imt, XySequence>> calcTargetHazards() { - - // interpolate from LL to UL point - Map<SiteClass, Map<Imt, XySequence>> westTarget = - getTargetData(boundingHazardsList.get(0), boundingHazardsList.get(1), fracLat); - // interpolate from LR to UR point - Map<SiteClass, Map<Imt, XySequence>> eastTarget = - getTargetData(boundingHazardsList.get(3), boundingHazardsList.get(2), fracLat); - - // do E-W interpolation - return getTargetData(westTarget, eastTarget, fracLon); - - } - - void dumpCoords() { - System.err.printf("%14s %14s %14s %14s %14s %14s %s\n", "Point", "LL", "UL", "UR", "LR", - "Target", "Frac"); - System.err.printf("%14s %14.5f %14.5f %14.5f %14.5f %14.5f %.8f\n", "Lat:", - boundingSites.get(0).latitude, - boundingSites.get(1).latitude, - boundingSites.get(2).latitude, - boundingSites.get(3).latitude, - site.latitude, - fracLat); - System.err.printf("%14s %14.5f %14.5f %14.5f %14.5f %14.5f %.8f\n", "Lon:", - boundingSites.get(0).longitude, - boundingSites.get(1).longitude, - boundingSites.get(2).longitude, - boundingSites.get(3).longitude, - site.longitude, - fracLon); - - } - - void dumpBoundingPlusTarget(SiteClass sc, Imt imt) { - System.err.printf("Results for:\n"); - System.err.printf(" SiteClass: %2s, %d m/s\n", sc.toString(), mapSiteClassToVs30(sc)); - System.err.printf(" IMT: %s, %s\n", imt.name(), imt.period()); - - dumpCoords(); - System.err.printf("%14s\n", "Iml"); - for (int i = 0; i < ncrd.nshmpCoords.nIml; i++) { - // System.err.printf("%14d %14d %14d %14d %14d %14d\n", - // (long) ncrd.nshmpCoords.imlMap.get(sc).get(imt)[i], - // (long) boundingHazardsList.get(0).get(sc).get(imt).y(i), - // (long) boundingHazardsList.get(1).get(sc).get(imt).y(i), - // (long) boundingHazardsList.get(2).get(sc).get(imt).y(i), - // (long) boundingHazardsList.get(3).get(sc).get(imt).y(i), - // (long) boundingHazardsList.get(4).get(sc).get(imt).y(i)); - System.err.printf("%14.5f %14.6e %14.6e %14.6e %14.6e %14.6e\n", - ncrd.nshmpCoords.imlMap.get(sc).get(imt)[i], - boundingHazardsList.get(0).get(sc).get(imt).y(i), - boundingHazardsList.get(1).get(sc).get(imt).y(i), - boundingHazardsList.get(2).get(sc).get(imt).y(i), - boundingHazardsList.get(3).get(sc).get(imt).y(i), - boundingHazardsList.get(4).get(sc).get(imt).y(i)); - } - - } - - void dumpBoundingPlusTarget(int idxSc, int idxImt) { - dumpBoundingPlusTarget( - ncrd.nshmpCoords.siteClassEnums.get(idxSc), - ncrd.nshmpCoords.imts.get(idxImt)); - } - - int idxLonLL() { - return idxLonLL; - } - - int idxLatLL() { - return idxLatLL; - } - - int idxLonUL() { - return idxLonLL; - } - - int idxLatUL() { - return idxLatLL + 1; - } - - int idxLonUR() { - return idxLonLL + 1; - } - - int idxLatUR() { - return idxLatLL + 1; - } - - int idxLonLR() { - return idxLonLL + 1; - } - - int idxLatLR() { - return idxLatLL; - } - - } - - /* - * Return hazard curves for four closest grid points as a List of: - * Map<SiteClass, Map<Imt, XySequence(iml,hazard)>> - * - * List order is clockwise from lower left corner: LL, UL, UR, LR, [T] with an - * empty slot for the interpolated target hazards - */ - private List<Map<SiteClass, Map<Imt, XySequence>>> extractHazardsAt(int idxLonLL, int idxLatLL) { - - // initialize list - List<Map<SiteClass, Map<Imt, XySequence>>> boundingHazardMaps = - new ArrayList<Map<SiteClass, Map<Imt, XySequence>>>(5); - - try (NetcdfDataset ncd = NetcdfDataset.openDataset(nshmpNetcdfFilename)) { - - Group targetGroup = ncd.findGroup(nshmpBaseGroup); - - // TODO: rename variable in netCDF - Variable vHazards = targetGroup.findVariable("AEPs"); - - // set up origin and shape arrays for reading hazard curves at four - // bounding - // (lon,lat) grid points - int[] origin = new int[] { 0, 0, idxLatLL, idxLonLL, 0 }; - int[] shape = new int[] { nshmpCoords.nVs, nshmpCoords.nImt, 2, 2, nshmpCoords.nIml }; - - // read data into Array for bounding grid points - // System.err.println("reading hazards for four points"); - Array aHazards = vHazards.read(origin, shape); - - // Array aHazards now has shape [nVs,nImt,2,2,nIml] - // ...so origin will now be [0,0,0,0,0] for LL grid point - // ...and shape of requested array is [nVs,nImt,1,1,nIml] - - // System.err.println(aHazards.shapeToString()); - // Array aHazards is size 2 on the lon and lat dimensions - shape[2] = 1; - shape[3] = 1; - - // TODO: XySequence instead of double[] - - // Put data into hazard map for LL point - origin[2] = 0; // lat index position - origin[3] = 0; // lon index position - // System.err.println("getting LL hazards"); - boundingHazardMaps.add(mapHazardsFromArray(aHazards.section(origin, shape))); - // Put data into hazard map for UL point - origin[2] = 1; // lat index position - origin[3] = 0; // lon index position - // System.err.println("getting UL hazards"); - boundingHazardMaps.add(mapHazardsFromArray(aHazards.section(origin, shape))); - // Put data into hazard map for UR point - origin[2] = 1; // lat index position - origin[3] = 1; // lon index position - // System.err.println("getting UR hazards"); - boundingHazardMaps.add(mapHazardsFromArray(aHazards.section(origin, shape))); - // Put data into hazard map for LR point - origin[2] = 0; // lat index position - origin[3] = 1; // lon index position - // System.err.println("getting LR hazards"); - boundingHazardMaps.add(mapHazardsFromArray(aHazards.section(origin, shape))); - - // // read data into Array (using Variable.read() for each hazard curve) - // THIS IS SLOW - // // TODO: XySequence instead of double[] - // // TODO: read data from variable ONCE!!! convert Array to maps - // // initialize list - // List<Map<SiteClass, Map<Imt, double[]>>> boundingHazardMaps = - // new ArrayList<Map<SiteClass, Map<Imt, double[]>>>(4); - // // Read data into hazard map for LL point - // System.err.println("getting LL hazards"); - // boundingHazardMaps.add(mapHazards(vHazards, idxLonLL, idxLatLL)); - // // Read data into hazard map for UL point - // System.err.println("getting UL hazards"); - // boundingHazardMaps.add(mapHazards(vHazards, idxLonLL, idxLatLL + 1)); - // // Read data into hazard map for UR point - // System.err.println("getting UR hazards"); - // boundingHazardMaps.add(mapHazards(vHazards, idxLonLL + 1, idxLatLL + - // 1)); - // // Read data into hazard map for LR point - // System.err.println("getting LR hazards"); - // boundingHazardMaps.add(mapHazards(vHazards, idxLonLL + 1, idxLatLL)); - - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InvalidRangeException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - // interpolate hazards to target point - return boundingHazardMaps; - - } - - /* - * TODO: attempt to calculate index - * - * possibly as NshmpNetcdfCoordinates.calcIdxLTEQ() - */ - - /* - * Read hazard curves from netCDF variable into map of hazards by SiteClass - * and Imt - * - * TODO: if target is on a grid point (or on a grid lat or lon), no need to - * read 4 bounding points ? - */ - private Map<SiteClass, Map<Imt, XySequence>> mapHazardsFromArray(Array aHazards) { - // aHazards has had its lat and lon dimensions reduced (so it now is rank 3) - // aHazards[SiteClass, Imt, Iml] - - // initialize - EnumMap<SiteClass, Map<Imt, XySequence>> vsImtHazardMap = Maps.newEnumMap(SiteClass.class); - - for (int iSc = 0; iSc < nshmpCoords.siteClassEnums.size(); iSc++) { - SiteClass sc = nshmpCoords.siteClassEnums.get(iSc); - - Map<Imt, XySequence> imtHazardMap = Maps.newEnumMap(Imt.class); - for (int iImt = 0; iImt < nshmpCoords.imts.size(); iImt++) { - Imt imt = nshmpCoords.imts.get(iImt); - - // set origin and shape of double[] hazards to read - int[] origin = new int[] { iSc, iImt, 0 }; - int[] shape = new int[] { 1, 1, nshmpCoords.nIml }; - - // try block needed for sectioning Array - try { - imtHazardMap.put(imt, - XySequence.create( - nshmpCoords.imlMap.get(sc).get(imt), - (double[]) aHazards.section(origin, shape).reduce() - .get1DJavaArray(DataType.DOUBLE))); - } catch (InvalidRangeException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - } - - vsImtHazardMap.put(sc, imtHazardMap); - } - - return Maps.immutableEnumMap(vsImtHazardMap); - } - - /* - * Read hazard curves from netCDF variable into map of hazards by SiteClass - * and Imt - * - * TODO: if target is on a grid point (or on a grid lat or lon), no need to - * read 4 bounding points - */ - private Map<SiteClass, Map<Imt, double[]>> mapHazards(Variable vHazards, int idxLon, int idxLat) { - - // initialize - EnumMap<SiteClass, Map<Imt, double[]>> vsImtHazardMap = Maps.newEnumMap(SiteClass.class); - - for (int iSc = 0; iSc < nshmpCoords.siteClassEnums.size(); iSc++) { - SiteClass sc = nshmpCoords.siteClassEnums.get(iSc); - - Map<Imt, double[]> imtHazardMap = Maps.newEnumMap(Imt.class); - for (int iImt = 0; iImt < nshmpCoords.imts.size(); iImt++) { - Imt imt = nshmpCoords.imts.get(iImt); - - // set origin and shape of double[] hazards to read - int[] origin = new int[] { iSc, iImt, idxLat, idxLon, 0 }; - int[] shape = new int[] { 1, 1, 1, 1, nshmpCoords.nIml }; - - try { - imtHazardMap.put(imt, - (double[]) vHazards.read(origin, shape).reduce().get1DJavaArray(DataType.DOUBLE)); - } catch (IOException | InvalidRangeException e) { - System.err.println("Failed read attempt for vHazards with origin: " + - Arrays.toString(origin) + ", shape: " + Arrays.toString(shape)); - e.printStackTrace(); - } - - } - - vsImtHazardMap.put(sc, imtHazardMap); - } - - return Maps.immutableEnumMap(vsImtHazardMap); - } - - /* - * 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 - * (the last element), an index of a.length - 1 is returned. - */ - private static int getIdxLTEQ(double[] a, double t) { - // assumes array is in sorted order (a[i] < a[i+1]) - // make sure target is within the range of the array - double tol = 0.000001; - int n = a.length; - if (t < a[0] || t > a[n - 1]) { - // should never get here thanks to NshmpNetcdfCoordinates.checkCoords() - throw new IllegalArgumentException( - String.format("Target outside of valid range: %.4f <= t <= %4f", a[0], a[n - 1])); - } - // if (t == a[0]) { - if (DoubleMath.fuzzyEquals(a[0], t, tol)) { - return 0; - } - // if (t == a[n - 1]) { - if (DoubleMath.fuzzyEquals(a[n - 1], t, tol)) { - return n - 2; // return second to last index number - } - - int idx = Arrays.binarySearch(a, t); - if (idx < 0) { - // "exact" match not found, so use index of first value less than target - // this is insertion_point - 1 - // returned idx = -insertion_point - 1 - idx = -1 * (idx + 1) - 1; - // array[idx] <= target - } - return idx; - } - - /* - * Calculate fractional distance from a1 to t, between a1 and a2 - */ - private static double calcFrac(double a1, double a2, double t) { - double tol = 0.00001; - double frac = 0.0; - if (Math.abs(t - a1) < tol) { - // target value == a1 - frac = 0.0; - } else if (Math.abs(t - a2) < tol) { - // target value == a2 - frac = 1.0; - } else { - // calculate fractional distance to t between a[i] and a[i+1] - frac = (t - a1) / (a2 - a1); - } - return frac; - } - - /* - * Calculate fractional distance from a[i] to t, between a[i] and a[i+1] - */ - private static double calcGridFrac(double[] a, int i, double t) { - return calcFrac(a[i], a[i + 1], t); - } - - /* - * Get data for target point - * - * @param d1 data at first point (p1) - * - * @param d2 data at second point (p2) - * - * @param frac fractional distance between p1 and p2 to target point - */ - private static Map<SiteClass, Map<Imt, XySequence>> getTargetData( - Map<SiteClass, Map<Imt, XySequence>> d1, - Map<SiteClass, Map<Imt, XySequence>> d2, - double frac) { - // do we need better checking here? or is it safe to assume that every - // Map<SiteClass, Map<Imt,double[]>> passed in is consistent? - if (d1.size() != d2.size()) { - throw new IllegalArgumentException("Array size disagreement, cannot interpolate"); - } - Map<SiteClass, Map<Imt, XySequence>> tMap; - if (frac == 0.0) { - // target is the same as d1 - tMap = d1; - } else if (frac == 1.0) { - // target is the same as d2 - tMap = d2; - } else { - tMap = linearInterpolate(d1, d2, frac); - } - return tMap; - } - - /* - * Linear interpolation of data values to a target point - * - * TODO: use XySequence.copyOf(), streams - */ - private static Map<SiteClass, Map<Imt, XySequence>> linearInterpolate( - Map<SiteClass, Map<Imt, XySequence>> v1, - Map<SiteClass, Map<Imt, XySequence>> v2, - double frac) { - // return v1 * (1 - frac) + v2 * frac - // DoubleData routines add and multiply in place, we don't want to modify - // the original data here - if (v1.size() != v2.size()) { - throw new IllegalArgumentException("Array size disagreement, cannot interpolate"); - } - - Map<SiteClass, Map<Imt, XySequence>> tMap = Maps.newEnumMap(SiteClass.class); - - for (SiteClass sc : v1.keySet()) { - Map<Imt, XySequence> imtHazards = Maps.newEnumMap(Imt.class); - - for (Imt imt : v1.get(sc).keySet()) { - double[] v1Haz = v1.get(sc).get(imt).yValues().toArray(); - double[] v2Haz = v2.get(sc).get(imt).yValues().toArray(); - double[] t = new double[v1Haz.length]; - for (int i = 0; i < v1Haz.length; i++) { - t[i] = v1Haz[i] * (1 - frac) + v2Haz[i] * frac; - } - imtHazards.put(imt, XySequence.create(v1.get(sc).get(imt).xValues().toArray(), t)); - } - tMap.put(sc, imtHazards); - } - return tMap; - } - - /* - * check that sets of Iml are consistent for a given Imt across all SiteClass - */ - private static boolean checkConsistentImls(Map<SiteClass, Map<Imt, double[]>> imlMap, - List<SiteClass> siteClasses, List<Imt> imts) { - - boolean allEqual = true; - - // System.err.println("\n\n"); - for (Imt imt : imts) { - // System.err.println("Checking Imt " + imt.toString()); - - double[] firstImls = new double[1]; - SiteClass firstSc = null; - for (SiteClass sc : siteClasses) { - if (firstImls.length == 1) { - firstImls = imlMap.get(sc).get(imt); - firstSc = sc; - continue; - } - boolean imlsDiffer = false; - double[] theseImls = imlMap.get(sc).get(imt); - for (int i = 0; i < firstImls.length; i++) { - if (DoubleMath.fuzzyEquals(firstImls[i], theseImls[i], 1e-9)) { - continue; - } - imlsDiffer = true; - allEqual = false; - break; - } - // if (imlsDiffer) { - // // Imls differ for imt between lastSc and sc - // System.err.println("Iml set differs, " + firstSc.toString() + " vs. " - // + sc.toString()); - // System.err.println(" " + Arrays.toString(firstImls)); - // System.err.println(" " + Arrays.toString(theseImls)); - // - // } - } - } - return allEqual; - } - - /** - * This should be imported. For now, it is copied from - * nshmp-haz-v2/src/gov.usgs.earthquake.nshmp.site.NehrpSiteClass.java - * - * Placeholder enum for likely move to Nehrp site class identifier instead of - * Vs30. - * - * <p>These site class identifiers map to NEHRP site clases, but the intent is - * that they can be used more generally for models in other parts of the world - * where the GMMs are not necessarily parameterized in terms of vs30 to define - * site response. For instance, NZ/JP site classes might use A, B, C, and D as - * a proxy for the local I, II, III, and IV identifiers. In the U.S., models - * will need to specify the Vs30 value that each site class corresponds to. - * Although the values were consistent over prior models, now that multiple - * site classes are supported (in 2018) across the entire U.S., there have - * been changes proposed for balloting by the BSSC to make the Vs30 - * definitions of site classes consistent in how they are calculated. - * - * @author Peter Powers - */ - enum SiteClass { - - /* - * Notes on calculation of Vs30 for site class: - * - * Question: Why is it that the soil shear wave velocity shown in the - * Unified Hazard Tool is not equal to the average of the values shown in - * ASCE 7-10 table 20.3-1? - * - * For instance: 259 m/s (Site Class D), from the Unified Hazard Tool, is - * not equal to (600 ft/s + 1200 ft/s)/2 * .3048 = 274 m/s - * - * Answer (Sanaz): we take the geometric mean: sqrt(1200*600)*0.3048 = - * 258.6314 , which rounds to 259m/s. - * - * - */ - - /* OLD Vs30, NEW Vs30 */ - - /* 2000 2000 */ - A, - - /* 1500 1500 (new) */ - AB, - - /* 1150 1080 */ - B, - - /* 760 760 */ - BC, - - /* 537 530 */ - C, - - /* 360 365 */ - CD, - - /* 259 260 */ - D, - - /* 180 185 */ - DE, - - /* 150 150 (new) */ - E, - - ; - } - - static SiteClass mapVsToEnum2018(int vs) { - // vs30 = 150, 185, 260, 365, 530, 760, 1080, 1500 ; - switch (vs) { - case 150: - return SiteClass.E; - case 185: - return SiteClass.DE; - case 260: - return SiteClass.D; - case 365: - return SiteClass.CD; - case 530: - return SiteClass.C; - case 760: - return SiteClass.BC; - case 1080: - return SiteClass.B; - case 1500: - return SiteClass.AB; - default: - throw new IllegalArgumentException( - String.format("Invalid Vs30, no matching 2018 SiteClass: %d", vs)); - } - } - - static int mapSiteClassToVs30(SiteClass sc) { - switch (sc) { - case A: - return 2000; - case AB: - return 1500; - case B: - return 1080; - case BC: - return 760; - case C: - return 530; - case CD: - return 365; - case D: - return 260; - case DE: - return 185; - case E: - return 150; - default: - throw new IllegalArgumentException( - String.format("Invalid SiteClass %s", sc.toString())); - } - } -} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java new file mode 100644 index 0000000000000000000000000000000000000000..89d22fef5415fa43f4004cb259ef8dbb0d5faf6e --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java @@ -0,0 +1,98 @@ +package gov.usgs.earthquake.nshmp.netcdf; + +import java.util.Arrays; + +/** + * This should be imported. For now, it is copied from + * nshmp-haz-v2/src/gov.usgs.earthquake.nshmp.site.NehrpSiteClass.java + * + * Placeholder enum for likely move to Nehrp site class identifier instead of + * Vs30. + * + * <p>These site class identifiers map to NEHRP site clases, but the intent is + * that they can be used more generally for models in other parts of the world + * where the GMMs are not necessarily parameterized in terms of vs30 to define + * site response. For instance, NZ/JP site classes might use A, B, C, and D as a + * proxy for the local I, II, III, and IV identifiers. In the U.S., models will + * need to specify the Vs30 value that each site class corresponds to. Although + * the values were consistent over prior models, now that multiple site classes + * are supported (in 2018) across the entire U.S., there have been changes + * proposed for balloting by the BSSC to make the Vs30 definitions of site + * classes consistent in how they are calculated. + * + * @author Peter Powers + */ +public enum SiteClass { + + /* + * Notes on calculation of Vs30 for site class: + * + * Question: Why is it that the soil shear wave velocity shown in the Unified + * Hazard Tool is not equal to the average of the values shown in ASCE 7-10 + * table 20.3-1? + * + * For instance: 259 m/s (Site Class D), from the Unified Hazard Tool, is not + * equal to (600 ft/s + 1200 ft/s)/2 * .3048 = 274 m/s + * + * Answer (Sanaz): we take the geometric mean: sqrt(1200*600)*0.3048 = + * 258.6314 , which rounds to 259m/s. + * + * + */ + + /* OLD Vs30, NEW Vs30 */ + + /* 2000 2000 */ + A(2000), + + /* 1500 1500 (new) */ + AB(1500), + + /* 1150 1080 */ + B(1080), + + /* 760 760 */ + BC(760), + + /* 537 530 */ + C(530), + + /* 360 365 */ + CD(365), + + /* 259 260 */ + D(260), + + /* 180 185 */ + DE(185), + + /* 150 150 (new) */ + E(150); + + private final int vs30; + + private SiteClass(int vs30) { + this.vs30 = vs30; + } + + /** + * Returns the Vs30 associated with the {@code SiteClass}. + */ + public int vs30() { + return vs30; + } + + /** + * Returns a {@code SiteClass} associated with the vs30. + * + * @param vs30 The vs30 of the site class + */ + public static SiteClass ofValue(int vs30) { + return Arrays.stream(values()) + .filter(siteClass -> siteClass.vs30 == vs30) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException( + "No matching site class to Vs30 [" + vs30 + "]")); + } + +}