diff --git a/src/lib/build.gradle b/src/lib/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc
--- /dev/null
+++ b/src/lib/build.gradle
@@ -0,0 +1 @@
+
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c13df52084f3a2bcac317f429588f895edd4007
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java
@@ -0,0 +1,97 @@
+package gov.usgs.earthquake.nshmp.netcdf;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import gov.usgs.earthquake.nshmp.geo.Location;
+import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
+import gov.usgs.earthquake.nshmp.netcdf.data.BoundingData;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfData;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfShape;
+import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
+import gov.usgs.earthquake.nshmp.netcdf.reader.Reader;
+
+/**
+ * Abstarct class for NetCDF types: hazard curves and ground motions.
+ *
+ * @author U.S. Geological Survey
+ */
+public abstract class Netcdf<T> {
+
+  protected final Path netcdfPath;
+  protected final NetcdfDataType dataType;
+  protected final NetcdfData netcdfData;
+  protected NetcdfShape netcdfShape;
+
+  private static final Logger LOGGER = Logger.getLogger("ucar");
+
+  static {
+    /* Update ucar logger */
+    LOGGER.setLevel(Level.SEVERE);
+  }
+
+  public Netcdf(Path netcdfPath) {
+    this.netcdfPath = netcdfPath;
+
+    if (Files.notExists(netcdfPath)) {
+      throw new IllegalArgumentException("Path to Netcdf file [" + netcdfPath + "] does not exist");
+    }
+
+    dataType = NetcdfDataType.getDataType(netcdfPath);
+    var reader = getNetcdfData(netcdfPath);
+    netcdfData = reader.netcdfData();
+    netcdfShape = reader.netcdfShape();
+  }
+
+  /**
+   * Returns the bounding data from a specific site.
+   *
+   * @param site The site to get the bounding data
+   */
+  public abstract BoundingData<T> boundingData(Location site);
+
+  /**
+   * Returns the data type.
+   */
+  public NetcdfDataType dataType() {
+    return dataType;
+  }
+
+  /**
+   * Returns the NetCDF data.
+   */
+  public abstract NetcdfData netcdfData();
+
+  /**
+   * Returns the NetCDF path.
+   */
+  public Path netcdfPath() {
+    return netcdfPath;
+  }
+
+  /**
+   * Returns the NetCDF shape`
+   */
+  public NetcdfShape netcdfShape() {
+    return netcdfShape;
+  }
+
+  /**
+   * Returns the static data from a specific site.
+   *
+   * @param site The site to get the static data
+   */
+  public abstract StaticData<T> staticData(Location site);
+
+  /**
+   * Returns the static data from a site and site class.
+   *
+   * @param site The site to get the static data
+   * @param siteClass The site class to get the static data
+   */
+  public abstract T staticData(Location site, NehrpSiteClass siteClass);
+
+  abstract Reader getNetcdfData(Path netcdfPath);
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataType.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataType.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7bb13c55cb393d6fad60b864e903487580fee39
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfDataType.java
@@ -0,0 +1,32 @@
+package gov.usgs.earthquake.nshmp.netcdf;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils.Key;
+
+import ucar.nc2.dataset.NetcdfDatasets;
+
+/**
+ * Supported NetCDF data types.
+ */
+public enum NetcdfDataType {
+
+  GROUND_MOTIONS,
+  HAZARD_CURVES;
+
+  /**
+   * Returns the data type read from a NetCDF file with attribute "dataType".
+   *
+   * @param netcdfPath Path to NetCDF file
+   */
+  public static NetcdfDataType getDataType(Path netcdfPath) {
+    try (var ncd = NetcdfDatasets.openDataset(netcdfPath.toString())) {
+      var group = ncd.getRootGroup();
+      var vDataType = group.attributes().findAttribute(Key.DATA_TYPE);
+      return NetcdfDataType.valueOf(vDataType.getStringValue());
+    } catch (IOException e) {
+      throw new RuntimeException("Could not read Netcdf file [" + netcdfPath + " ]");
+    }
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d38dc3a675277445b9a50ab902750290320b34b
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/SiteClass.java
@@ -0,0 +1,104 @@
+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("Site class A (Vs30 2000)", 2000),
+
+  /* 1500 1500 (new) */
+  AB("Site class AB (Vs30 1500)", 1500),
+
+  /* 1150 1080 */
+  B("Site class B (Vs30 1080)", 1080),
+
+  /* 760 760 */
+  BC("Site class BC (Vs30 760)", 760),
+
+  /* 537 530 */
+  C("Site class C (Vs30 530)", 530),
+
+  /* 360 365 */
+  CD("Site class CD (Vs30 365)", 365),
+
+  /* 259 260 */
+  D("Site class D (Vs30 260)", 260),
+
+  /* 180 185 */
+  DE("Site class DE (Vs30 185)", 185),
+
+  /* 150 150 (new) */
+  E("Site class E (Vs30 150)", 150);
+
+  private final String display;
+  private final int vs30;
+
+  private SiteClass(String display, int vs30) {
+    this.display = display;
+    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 + "]"));
+  }
+
+  @Override
+  public String toString() {
+    return display;
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/BoundingData.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/BoundingData.java
new file mode 100644
index 0000000000000000000000000000000000000000..6fcda9d66b1a82164c270f380531c26bc3a8c709
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/BoundingData.java
@@ -0,0 +1,45 @@
+package gov.usgs.earthquake.nshmp.netcdf.data;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import gov.usgs.earthquake.nshmp.geo.Location;
+
+/**
+ * Location to static data mapper.
+ *
+ * @author U.S. Geological Survey
+ */
+public class BoundingData<T> extends LinkedHashMap<Location, StaticData<T>> {
+
+  public BoundingData() {}
+
+  private BoundingData(Map<Location, StaticData<T>> boundingHazards) {
+    putAll(boundingHazards);
+  }
+
+  public static <T> Builder<T> builder() {
+    return new Builder<T>();
+  }
+
+  public static class Builder<T> {
+    Map<Location, StaticData<T>> boundingData;
+
+    private Builder() {
+      boundingData = new HashMap<>();
+    }
+
+    public Builder<T> put(Location location, StaticData<T> staticData) {
+      boundingData.put(location, staticData);
+      return this;
+    }
+
+    public BoundingData<T> build() {
+      checkState(!boundingData.isEmpty(), "Must add static data");
+      return new BoundingData<>(boundingData);
+    }
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfData.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfData.java
new file mode 100644
index 0000000000000000000000000000000000000000..41b07528eb87a8c6c70f774b008c765736627b6f
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfData.java
@@ -0,0 +1,156 @@
+package gov.usgs.earthquake.nshmp.netcdf.data;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import gov.usgs.earthquake.nshmp.geo.Bounds;
+import gov.usgs.earthquake.nshmp.geo.Location;
+import gov.usgs.earthquake.nshmp.geo.Regions;
+import gov.usgs.earthquake.nshmp.gmm.Imt;
+import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
+
+/**
+ * Data info from the NetCDF file.
+ *
+ * @author U.S. Geological Survey
+ */
+public class NetcdfData {
+  private final Bounds bounds;
+  private final List<Imt> imts;
+  private final double[] latitudes;
+  private final double[] longitudes;
+  private final ScienceBaseMetadata scienceBaseMetadata;
+  private final List<NehrpSiteClass> siteClasses;
+  private final Map<NehrpSiteClass, Double> vs30Map;
+
+  protected NetcdfData(Builder builder) {
+    var minLatitude = Arrays.stream(builder.latitudes).min().getAsDouble();
+    var maxLatitude = Arrays.stream(builder.latitudes).max().getAsDouble();
+    var minLongitude = Arrays.stream(builder.longitudes).min().getAsDouble();
+    var maxLongitude = Arrays.stream(builder.longitudes).max().getAsDouble();
+
+    bounds = Regions.createRectangular(
+        "",
+        Location.create(minLongitude, minLatitude),
+        Location.create(maxLongitude, maxLatitude)).bounds();
+    imts = builder.imts;
+    latitudes = builder.latitudes;
+    longitudes = builder.longitudes;
+    scienceBaseMetadata = builder.scienceBaseMetadata;
+    siteClasses = builder.siteClasses;
+    vs30Map = builder.vs30Map;
+  }
+
+  /**
+   * Returns the bounds
+   */
+  public Bounds bounds() {
+    return bounds;
+  }
+
+  /**
+   * Return the Imts
+   */
+  public List<Imt> imts() {
+    return List.copyOf(imts);
+  }
+
+  /**
+   * Returns the latitudes.
+   */
+  public double[] latitudes() {
+    return latitudes.clone();
+  }
+
+  /**
+   * Returns the longitudes.
+   */
+  public double[] longitudes() {
+    return longitudes.clone();
+  }
+
+  /**
+   * Returns the science base info
+   */
+  public ScienceBaseMetadata scienceBaseMetadata() {
+    return scienceBaseMetadata;
+  }
+
+  /**
+   * Return the site classes
+   */
+  public List<NehrpSiteClass> siteClasses() {
+    return List.copyOf(siteClasses);
+  }
+
+  /**
+   * Returns the VS30 map
+   */
+  public Map<NehrpSiteClass, Double> vs30Map() {
+    return new TreeMap<>(vs30Map);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    List<Imt> imts;
+    double[] latitudes;
+    double[] longitudes;
+    List<NehrpSiteClass> siteClasses;
+    ScienceBaseMetadata scienceBaseMetadata;
+    Map<NehrpSiteClass, Double> vs30Map;
+
+    Builder() {}
+
+    public Builder imts(List<Imt> imts) {
+      this.imts = imts;
+      return this;
+    }
+
+    public Builder latitudes(double[] latitudes) {
+      this.latitudes = latitudes;
+      return this;
+    }
+
+    public Builder longitudes(double[] longitudes) {
+      this.longitudes = longitudes;
+      return this;
+    }
+
+    public Builder scienceBaseMetadata(ScienceBaseMetadata scienceBaseMetadata) {
+      this.scienceBaseMetadata = scienceBaseMetadata;
+      return this;
+    }
+
+    public Builder siteClasses(List<NehrpSiteClass> siteClasses) {
+      this.siteClasses = siteClasses;
+      return this;
+    }
+
+    public Builder vs30Map(Map<NehrpSiteClass, Double> vs30Map) {
+      this.vs30Map = vs30Map;
+      return this;
+    }
+
+    public NetcdfData build() {
+      checkBuildState();
+      return new NetcdfData(this);
+    }
+
+    void checkBuildState() {
+      checkState(!imts.isEmpty(), "Must add imts");
+      checkState(!(latitudes.length == 0), "Must add latitude");
+      checkState(!(longitudes.length == 0), "Must add longitudes");
+      checkState(scienceBaseMetadata != null, "Must set science base metadata");
+      checkState(!siteClasses.isEmpty(), "Must add site classes");
+      checkState(!vs30Map.isEmpty(), "Must add vs30s");
+    }
+  }
+
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java
new file mode 100644
index 0000000000000000000000000000000000000000..698207bf4ae2a9d5c1c4cd5b24ad11ac491a8633
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java
@@ -0,0 +1,139 @@
+package gov.usgs.earthquake.nshmp.netcdf.data;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Create NetCDF shapes and keep track of indices.
+ *
+ * @author U.S. Geological Survey
+ */
+public class NetcdfShape {
+
+  private final Map<IndexKey, Integer> indexMap;
+
+  private NetcdfShape(Builder builder) {
+    indexMap = builder.indexMap;
+  }
+
+  /**
+   * Returns builder to build a NetCDF shape.
+   */
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  /**
+   * Returns a builder to build an array shape for NetCDF origin and shapes.
+   */
+  public BuildShape buildShape() {
+    return new BuildShape();
+  }
+
+  public class BuildShape {
+    List<KeySize> keySizes;
+    Optional<Integer> pad = Optional.empty();
+    boolean reduce = false;
+
+    private BuildShape() {
+      keySizes = new ArrayList<>();
+    }
+
+    /**
+     * Add key and associated size for shape.
+     *
+     * @param key The key
+     * @param size The size
+     */
+    public BuildShape add(IndexKey key, int size) {
+      checkState(indexMap.containsKey(key), String.format("Key [%s] not found in shape", key));
+      keySizes.add(new KeySize(key, size));
+      return this;
+    }
+
+    /**
+     * Change to padding value of 0.
+     *
+     * Not used if reducing shape.
+     *
+     * @param value The value to pad the array shape
+     */
+    public BuildShape pad(int value) {
+      pad = Optional.of(value);
+      return this;
+    }
+
+    /**
+     * Reduce the array to only added keys in builder.
+     */
+    public BuildShape reduce() {
+      reduce = true;
+      return this;
+    }
+
+    /**
+     * Returns the shape array.
+     */
+    public int[] build() {
+      if (reduce) {
+        var reducePad = -1;
+        return build(reducePad).stream()
+            .mapToInt(Integer::intValue)
+            .filter(val -> val != reducePad)
+            .toArray();
+      } else {
+        return build(pad.orElse(0)).stream().mapToInt(Integer::intValue).toArray();
+      }
+    }
+
+    private List<Integer> build(int pad) {
+      var shape = new ArrayList<>(Collections.nCopies(indexMap.size(), pad));
+      keySizes.forEach(keySize -> {
+        shape.set(indexMap.get(keySize.indexKey), keySize.size);
+      });
+
+      return shape;
+    }
+  }
+
+  public static class KeySize {
+    public final IndexKey indexKey;
+    public final int size;
+
+    private KeySize(IndexKey indexKey, int size) {
+      this.indexKey = indexKey;
+      this.size = size;
+    }
+  }
+
+  public static enum IndexKey {
+    IML,
+    IMT,
+    LATITUDE,
+    LONGITUDE,
+    SITE_CLASS;
+  }
+
+  public static class Builder {
+    Map<IndexKey, Integer> indexMap;
+
+    private Builder() {
+      indexMap = new HashMap<>();
+    }
+
+    public Builder add(IndexKey key, int index) {
+      indexMap.put(key, index);
+      return this;
+    }
+
+    public NetcdfShape build() {
+      return new NetcdfShape(this);
+    }
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseInfo.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseInfo.java
new file mode 100644
index 0000000000000000000000000000000000000000..a57b2422537863b90b4404d86dfadfd4eb2b6e2a
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseInfo.java
@@ -0,0 +1,38 @@
+package gov.usgs.earthquake.nshmp.netcdf.data;
+
+public class ScienceBaseInfo {
+
+  final String id;
+  final String file;
+  final CatalogFileInfo[] files;
+  final String scienceBaseVersion;
+  final String title;
+  final String url;
+
+  ScienceBaseInfo(
+      String id,
+      String file,
+      CatalogFileInfo[] files,
+      String scienceBaseVersion,
+      String title,
+      String url) {
+    this.id = id;
+    this.file = file;
+    this.files = files;
+    this.scienceBaseVersion = scienceBaseVersion;
+    this.title = title;
+    this.url = url;
+  }
+
+  static class CatalogFileInfo {
+    final String file;
+    final String siteClass;
+    final String vs30;
+
+    CatalogFileInfo(String file, String siteClass, String vs30) {
+      this.file = file;
+      this.siteClass = siteClass;
+      this.vs30 = vs30;
+    }
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseMetadata.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseMetadata.java
new file mode 100644
index 0000000000000000000000000000000000000000..556087b6420b90c137bbc5d126dad2854e149377
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/ScienceBaseMetadata.java
@@ -0,0 +1,38 @@
+package gov.usgs.earthquake.nshmp.netcdf.data;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+
+import gov.usgs.earthquake.nshmp.netcdf.NetcdfDataType;
+import gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils.Key;
+
+import ucar.nc2.Group;
+
+public class ScienceBaseMetadata {
+
+  public final String catalogId;
+  public final NetcdfDataType dataType;
+  public final String description;
+  public final double gridStep;
+  public final String history;
+  public final String label;
+  public final String region;
+  public final ScienceBaseInfo[] scienceBaseInfo;
+  public final int year;
+
+  static final Gson GSON = new Gson();
+
+  public ScienceBaseMetadata(Group targetGroup) {
+    catalogId = targetGroup.findAttribute(Key.ID).getStringValue();
+    dataType = NetcdfDataType.valueOf(targetGroup.findAttribute(Key.DATA_TYPE).getStringValue());
+    description = targetGroup.findAttribute(Key.DESCRIPTION).getStringValue();
+    gridStep = targetGroup.findAttribute(Key.GRID_STEP).getNumericValue().doubleValue();
+    history = targetGroup.findAttribute(Key.HISTORY).getStringValue();
+    label = targetGroup.findAttribute(Key.LABEL).getStringValue();
+    region = targetGroup.findAttribute(Key.REGION).getStringValue();
+    scienceBaseInfo = GSON.fromJson(
+        targetGroup.findAttribute(Key.SCIENCE_BASE_INFO).getStringValue(),
+        new TypeToken<ScienceBaseInfo[]>() {}.getType());
+    year = targetGroup.findAttribute(Key.YEAR).getNumericValue().intValue();
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticData.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticData.java
new file mode 100644
index 0000000000000000000000000000000000000000..21642ae253a2c6c1974a48a8a8779e7aeb2505e6
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/StaticData.java
@@ -0,0 +1,46 @@
+package gov.usgs.earthquake.nshmp.netcdf.data;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.util.EnumMap;
+
+import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
+
+/**
+ * NEHRP site class to data mapper.
+ *
+ * @author U.S. Geological Survey
+ */
+public class StaticData<T> extends EnumMap<NehrpSiteClass, T> {
+
+  public StaticData() {
+    super(NehrpSiteClass.class);
+  }
+
+  StaticData(EnumMap<NehrpSiteClass, T> data) {
+    super(NehrpSiteClass.class);
+    putAll(data);
+  }
+
+  public static <T> Builder<T> builder() {
+    return new Builder<T>();
+  }
+
+  public static class Builder<T> {
+    EnumMap<NehrpSiteClass, T> data;
+
+    private Builder() {
+      data = new EnumMap<>(NehrpSiteClass.class);
+    }
+
+    public Builder<T> put(NehrpSiteClass siteClass, T data) {
+      this.data.put(siteClass, data);
+      return this;
+    }
+
+    public StaticData<T> build() {
+      checkState(!data.isEmpty(), "Must add data");
+      return new StaticData<T>(data);
+    }
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..83f5c2e3cfab7d21a6a56b1678b7b6667cc80cb1
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java
@@ -0,0 +1,135 @@
+package gov.usgs.earthquake.nshmp.netcdf.reader;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import gov.usgs.earthquake.nshmp.geo.Location;
+import gov.usgs.earthquake.nshmp.geo.LocationList;
+import gov.usgs.earthquake.nshmp.netcdf.Netcdf;
+import gov.usgs.earthquake.nshmp.netcdf.data.BoundingData;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfData;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfShape;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfShape.IndexKey;
+import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
+
+import ucar.ma2.Array;
+
+/**
+ * Abstract class to read in NetCDF file and create the bounding locations and
+ * associated data.
+ *
+ * @author U.S. Geological Survey
+ */
+public abstract class BoundingReader<T> {
+
+  private final NetcdfData netcdfData;
+  private BoundingData<T> boundingData;
+  private List<BoundingLocation> boundingLocations = new ArrayList<>();
+
+  BoundingReader(Netcdf<T> netcdf, Location site) {
+    this.netcdfData = netcdf.netcdfData();
+    boundingLocations = setBoundingLocations(netcdf, site);
+    boundingData = setBoundingData(netcdf, site, boundingLocations);
+  }
+
+  /**
+   * Returns the bounding data associated with a location
+   */
+  public BoundingData<T> boundingData() {
+    return boundingData;
+  }
+
+  /**
+   * Returns the bounding locations
+   */
+  LocationList boundingLocations() {
+    var locations = boundingLocations.stream()
+        .map(boundingLocation -> boundingLocation.location)
+        .collect(Collectors.toList());
+
+    return LocationList.copyOf(locations);
+  }
+
+  abstract StaticData<T> calculateTargetData(
+      List<BoundingLocation> boundingLocations,
+      BoundingData<T> boundingData,
+      double fracLon,
+      double fracLat);
+
+  abstract BoundingData<T> extractDataAt(
+      Netcdf<T> netcdf,
+      List<BoundingLocation> boundingLocations,
+      int idxLonLL,
+      int idxLatLL);
+
+  /**
+   * 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
+   */
+  abstract StaticData<T> getTargetData(StaticData<T> d1, StaticData<T> d2, double frac);
+
+  /*
+   * 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 ?
+   */
+  abstract StaticData<T> mapDataFromArray(
+      Netcdf<T> netcdf,
+      Array array);
+
+  /**
+   * Set the bounding data
+   */
+  abstract BoundingData<T> setBoundingData(
+      Netcdf<T> netcdf,
+      Location site,
+      List<BoundingLocation> boundingLocations);
+
+  static class BoundingLocation {
+    final Location location;
+    final int[] origin;
+
+    BoundingLocation(
+        NetcdfShape netcdfShape,
+        Location location,
+        int longitudeIndex,
+        int latitudeIndex) {
+      this.location = location;
+      origin = netcdfShape.buildShape()
+          .add(IndexKey.LATITUDE, latitudeIndex)
+          .add(IndexKey.LONGITUDE, longitudeIndex)
+          .build();
+    }
+  }
+
+  private List<BoundingLocation> setBoundingLocations(Netcdf<T> netcdf, Location site) {
+    var boundingLocations = new ArrayList<BoundingLocation>();
+    var longitudes = netcdfData.longitudes();
+    var latitudes = netcdfData.latitudes();
+
+    var idxLonLL = NetcdfUtils.getIdxLTEQ(longitudes, site.longitude);
+    var idxLatLL = NetcdfUtils.getIdxLTEQ(latitudes, site.latitude);
+
+    var lonLeft = longitudes[idxLonLL];
+    var lonRight = longitudes[idxLonLL + 1];
+    var latLower = latitudes[idxLatLL];
+    var latUpper = latitudes[idxLatLL + 1];
+
+    boundingLocations.add(
+        new BoundingLocation(netcdf.netcdfShape(), Location.create(lonLeft, latLower), 0, 0));
+    boundingLocations.add(
+        new BoundingLocation(netcdf.netcdfShape(), Location.create(lonLeft, latUpper), 0, 1));
+    boundingLocations.add(
+        new BoundingLocation(netcdf.netcdfShape(), Location.create(lonRight, latUpper), 1, 1));
+    boundingLocations.add(
+        new BoundingLocation(netcdf.netcdfShape(), Location.create(lonRight, latLower), 1, 0));
+
+    return boundingLocations;
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..d03775dd2fadd9c9a00baa1d7f79331f7d8c6e3a
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
@@ -0,0 +1,257 @@
+package gov.usgs.earthquake.nshmp.netcdf.reader;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+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.geo.LocationList;
+import gov.usgs.earthquake.nshmp.netcdf.data.BoundingData;
+import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
+
+import ucar.ma2.DataType;
+import ucar.nc2.Group;
+
+public class NetcdfUtils {
+
+  // Tolerance for longitude/latitude comparisons
+  static final double LOCATION_TOLERANCE = 0.000001;
+
+  /**
+   * Creates a border going clockwise of the given longitudes and latitudes.
+   *
+   * @param longitudes The longitudes
+   * @param latitudes The latitudes
+   * @return
+   */
+  public static LocationList buildBorder(double[] longitudes, double[] latitudes) {
+    var builder = LocationList.builder();
+
+    for (var lat : latitudes) {
+      builder.add(longitudes[0], lat);
+    }
+
+    // omit duplicate points at corners
+    for (var i = 1; i < longitudes.length; i++) {
+      builder.add(longitudes[i], latitudes[latitudes.length - 1]);
+    }
+
+    for (var i = latitudes.length - 2; i >= 0; i--) {
+      builder.add(longitudes[longitudes.length - 1], latitudes[i]);
+    }
+
+    for (var i = longitudes.length - 2; i >= 0; i--) {
+      builder.add(longitudes[i], latitudes[0]);
+    }
+
+    return builder.build();
+  }
+
+  /*
+   * Calculate fractional distance from a1 to t, between a1 and a2
+   */
+  static double calcFrac(double a1, double a2, double t) {
+    if (Math.abs(t - a1) < LOCATION_TOLERANCE) {
+      // target value == a1
+      return 0.0;
+    } else if (Math.abs(t - a2) < LOCATION_TOLERANCE) {
+      // 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);
+  }
+
+  /**
+   * Check whether bounding ground motions contain the same: Site classes, IMTs
+   * per each site class, and ground motions.
+   *
+   * @param a static data A
+   * @param b static B
+   */
+  static void checkBoundingGroundMotion(
+      StaticData<XySequence> a,
+      StaticData<XySequence> b) {
+    checkState(a.size() == b.size(), "Maps are not the same size");
+    checkState(a.keySet().containsAll(b.keySet()), "Site classes do not match");
+    a.keySet().forEach(key -> checkXySequence(a.get(key), b.get(key)));
+  }
+
+  /**
+   * Checks bounding hazard maps contain the same: Site classes, IMTs per each
+   * site class, and ground motions per each IMT
+   *
+   * @param boundingData The bounding ground motions
+   */
+  static void checkBoundingGroundMotions(
+      BoundingData<XySequence> boundingData,
+      Location location) {
+    checkArgument(boundingData.containsKey(location), "Location not in bounding hazards");
+    boundingData.keySet().stream()
+        .filter(loc -> loc.equals(location))
+        .forEach(key -> {
+          checkBoundingGroundMotion(boundingData.get(location), boundingData.get(key));
+        });
+  }
+
+  /**
+   * Check that the X values are identical.
+   *
+   * @param a Sequence A
+   * @param b Sequence B
+   */
+  public static void checkXySequence(XySequence a, XySequence b) {
+    checkState(
+        Arrays.equals(a.xValues().toArray(), b.xValues().toArray()),
+        "Hazard curves xValues are not the same");
+  }
+
+  /**
+   * 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.findVariableLocal(key);
+    checkNotNull(
+        var,
+        String.format("Could not find variable [%s] in group [%s]", key, group.getFullName()));
+
+    return var.read().get1DJavaArray(dataType);
+  }
+
+  /**
+   * 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);
+  }
+
+  /*
+   * 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 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 [%.4f] outside of valid range: %.4f <= t <= %.4f", t, a[0],
+              a[n - 1]));
+    }
+
+    // if (t == a[0]) {
+    if (DoubleMath.fuzzyEquals(a[0], t, LOCATION_TOLERANCE)) {
+      return 0;
+    }
+    // if (t == a[n - 1]) {
+    if (DoubleMath.fuzzyEquals(a[n - 1], t, LOCATION_TOLERANCE)) {
+      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;
+  }
+
+  /**
+   * 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);
+  }
+
+  /**
+   * Returns a {@code String[]} from a netCDF group
+   *
+   * @param group The netCDF group
+   * @param key The key to read from the group
+   * @throws IOException
+   */
+  static String[] getStringArray(Group group, String key) throws IOException {
+    return (String[]) get1DArray(group, key, DataType.STRING);
+  }
+
+  /*
+   * Linear interpolation of data values to a target point
+   */
+  static StaticData<XySequence> linearInterpolateGroundMotions(
+      StaticData<XySequence> v1,
+      StaticData<XySequence> v2,
+      double frac) {
+    checkBoundingGroundMotion(v1, v2);
+
+    var targetMap = StaticData.<XySequence> builder();
+
+    v1.keySet().forEach(siteClass -> {
+      var v1Data = v1.get(siteClass).yValues().toArray();
+      var v2Data = v2.get(siteClass).yValues().toArray();
+      var target = new double[v1Data.length];
+
+      for (int i = 0; i < v1Data.length; i++) {
+        target[i] = v1Data[i] * (1 - frac) + v2Data[i] * frac;
+      }
+
+      var xValues = v1.get(siteClass).xValues().toArray();
+      targetMap.put(siteClass, XySequence.create(xValues, target));
+    });
+
+    return targetMap.build();
+  }
+
+  public static class Key {
+    public static final String DESCRIPTION = "description";
+    public static final String GRID_STEP = "gridStep";
+    public static final String ID = "id";
+    public static final String LABEL = "label";
+    public static final String DATA_TYPE = "dataType";
+    public static final String GROUND_MOTION = "groundMotion";
+    public static final String HAZARD = "hazard";
+    public static final String HISTORY = "history";
+    public static final String IMLS = "iml";
+    public static final String IMT = "imt";
+    public static final String LAT = "lat";
+    public static final String LON = "lon";
+    public static final String REGION = "region";
+    public static final String SCIENCE_BASE_INFO = "scienceBaseInfo";
+    public static final String SITE_CLASS = "siteClass";
+    public static final String VS30 = "vs30";
+    public static final String YEAR = "year";
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java
new file mode 100644
index 0000000000000000000000000000000000000000..80f176fe02cb3a9ee6398e51e4b32a64cd4b645a
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java
@@ -0,0 +1,106 @@
+package gov.usgs.earthquake.nshmp.netcdf.reader;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import com.google.common.reflect.TypeToken;
+import com.google.gson.Gson;
+
+import gov.usgs.earthquake.nshmp.Maths;
+import gov.usgs.earthquake.nshmp.gmm.Imt;
+import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfData;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfShape;
+import gov.usgs.earthquake.nshmp.netcdf.data.ScienceBaseMetadata;
+import gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils.Key;
+
+import ucar.ma2.DataType;
+import ucar.nc2.Group;
+
+/**
+ * Read in NetCDF file.
+ *
+ * @author U.S. Geological Survey
+ */
+public abstract class Reader {
+
+  Group targetGroup;
+  NetcdfData netcdfData;
+  private NetcdfShape netcdfShape;
+
+  static final String FLAG_KEY = "flag_key";
+  static final Gson GSON = new Gson();
+
+  public Reader(Group targetGroup) throws IOException {
+    this.targetGroup = targetGroup;
+    netcdfData = readData(targetGroup);
+    netcdfShape = buildNetcdfShape(targetGroup);
+  }
+
+  public NetcdfShape netcdfShape() {
+    return netcdfShape;
+  }
+
+  public NetcdfData netcdfData() {
+    return netcdfData;
+  }
+
+  abstract NetcdfShape buildNetcdfShape(Group group);
+
+  NetcdfData readData(Group targetGroup) throws IOException {
+    var vSiteClass = targetGroup.findVariableLocal(Key.SITE_CLASS);
+    var vImts = targetGroup.findVariableLocal(Key.IMT);
+    var vVs30 = targetGroup.findVariableLocal(Key.VS30);
+    var stringStringMapType = new TypeToken<Map<String, String>>() {}.getType();
+
+    // get list of IMT enums
+    Map<String, String> imtFlagKeys = GSON
+        .fromJson(vImts.findAttribute(FLAG_KEY)
+            .getValues().toString(), stringStringMapType);
+    var imts = Arrays.stream((int[]) vImts.read().get1DJavaArray(DataType.INT))
+        .boxed()
+        .map(imtInt -> imtFlagKeys.get(Integer.toString((imtInt))))
+        .map(imt -> Imt.valueOf(imt))
+        .collect(Collectors.toUnmodifiableList());
+
+    // get list of SiteClass enums
+    Map<String, String> siteClassFlagKeys = GSON
+        .fromJson(vSiteClass.findAttribute(FLAG_KEY)
+            .getValues().toString(), stringStringMapType);
+    List<NehrpSiteClass> siteClasses =
+        Arrays.stream((int[]) vSiteClass.read().get1DJavaArray(DataType.INT)).boxed()
+            .map(siteClassInt -> siteClassFlagKeys.get(Integer.toString(siteClassInt)))
+            .map(siteClass -> NehrpSiteClass.valueOf(siteClass))
+            .collect(Collectors.toUnmodifiableList());
+
+    // Get vs30 map
+    Map<String, Double> vs30FlagKeys = GSON
+        .fromJson(vVs30.findAttribute(FLAG_KEY).getValues().toString(),
+            new TypeToken<Map<String, Double>>() {}.getType());
+    Map<NehrpSiteClass, Double> vs30Map = vs30FlagKeys.entrySet().stream()
+        .collect(Collectors.toUnmodifiableMap(entry -> NehrpSiteClass.valueOf(entry.getKey()),
+            Map.Entry::getValue));
+
+    var latitudes = Arrays.stream(NetcdfUtils.getDoubleArray(targetGroup, Key.LAT))
+        // TODO: Dynamic set location precision from NetCDF
+        .map(lat -> Maths.round(lat, 3))
+        .toArray();
+
+    var longitudes = Arrays.stream(NetcdfUtils.getDoubleArray(targetGroup, Key.LON))
+        // TODO: Dynamic set location precision from NetCDF
+        .map(lon -> Maths.round(lon, 3))
+        .toArray();
+
+    return NetcdfData.builder()
+        .imts(imts)
+        .latitudes(latitudes)
+        .longitudes(longitudes)
+        .scienceBaseMetadata(new ScienceBaseMetadata(targetGroup))
+        .siteClasses(siteClasses)
+        .vs30Map(vs30Map)
+        .build();
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfService.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfService.java
new file mode 100644
index 0000000000000000000000000000000000000000..b3d161ec06019a733abd9551f4f5300011f203f7
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfService.java
@@ -0,0 +1,228 @@
+package gov.usgs.earthquake.nshmp.netcdf.www;
+
+import static gov.usgs.earthquake.nshmp.netcdf.www.NetcdfWsUtils.GSON;
+
+import java.util.Map;
+import java.util.logging.Logger;
+
+import gov.usgs.earthquake.nshmp.Maths;
+import gov.usgs.earthquake.nshmp.data.XySequence;
+import gov.usgs.earthquake.nshmp.geo.Location;
+import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
+import gov.usgs.earthquake.nshmp.netcdf.Netcdf;
+import gov.usgs.earthquake.nshmp.netcdf.data.ScienceBaseMetadata;
+import gov.usgs.earthquake.nshmp.netcdf.www.Query.Service;
+import gov.usgs.earthquake.nshmp.netcdf.www.meta.DoubleParameter;
+import gov.usgs.earthquake.nshmp.www.Response;
+
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+
+/**
+ * Abstract service handler for {@code NetcdfController}.
+ *
+ * @see NetcdfController
+ *
+ * @author U.S. Geological Survey
+ */
+public abstract class NetcdfService {
+
+  protected static final Logger LOGGER = Logger.getLogger(NetcdfService.class.getName());
+
+  Netcdf<?> netcdf;
+
+  protected NetcdfService(Netcdf<?> netcdf) {
+    this.netcdf = netcdf;
+  }
+
+  /**
+   * Returns the metadata response.
+   *
+   * @param httpRequest The HTTP request
+   */
+  abstract Response<String, Metadata> getMetadataResponse(HttpRequest<?> httpRequest);
+
+  /**
+   * Returns the service name
+   */
+  abstract String getServiceName();
+
+  /**
+   * Returns the netcdf object associated with the specific data type.
+   */
+  abstract Netcdf<?> netcdf();
+
+  /**
+   * Returns the static curves at a specific location.
+   *
+   * @param <T> The response type
+   * @param request The request data
+   * @param url The URL for the service call
+   */
+  abstract <T> Response<RequestData, T> processCurves(RequestData request, String url);
+
+  /**
+   * Returns the static curves at a specific location and site class.
+   *
+   * @param <T> The response type
+   * @param request The request data
+   * @param url The URL for the service call
+   */
+  abstract <T> Response<RequestDataSiteClass, T> processCurvesSiteClass(
+      RequestDataSiteClass request,
+      String url);
+
+  /**
+   * Process the service request and returns the reponse.
+   *
+   * @param httpRequest The HTTP request
+   * @param query The HTTP query
+   * @param service The NetCDF service
+   */
+  abstract Response<?, ?> processRequest(HttpRequest<?> httpRequest, Query query, Service service);
+
+  /**
+   * Handler of HTTP request, returns the service response.
+   *
+   * @param httpRequest The HTTP request
+   * @param query The service query
+   */
+  public HttpResponse<String> handleServiceCall(HttpRequest<?> httpRequest, Query query) {
+    try {
+      var url = httpRequest.getUri().toString();
+      LOGGER.info("Request: " + url);
+      LOGGER.fine("Query:\n" + GSON.toJson(query));
+
+      if (query.longitude == null && query.latitude == null) {
+        return metadata(httpRequest);
+      }
+      var service = getService(query);
+      Response<?, ?> svcResponse = processRequest(httpRequest, query, service);
+      var response = GSON.toJson(svcResponse);
+      LOGGER.fine("Result:\n" + response);
+      return HttpResponse.ok(response);
+    } catch (Exception e) {
+      var url = httpRequest.getUri().toString();
+      return NetcdfWsUtils.handleError(e, getServiceName(), url);
+    }
+  }
+
+  private Service getService(Query query) {
+    if (query.siteClass != null) {
+      return Service.CURVES_BY_SITE_CLASS;
+    } else {
+      return Service.CURVES;
+    }
+  }
+
+  private HttpResponse<String> metadata(HttpRequest<?> httpRequest) {
+    var svcResponse = getMetadataResponse(httpRequest);
+    var response = GSON.toJson(svcResponse);
+    LOGGER.fine("Result:\n" + response);
+    return HttpResponse.ok(response);
+  }
+
+  class Metadata {
+    final String description;
+    final String[] syntax;
+    final SourceModel model;
+    final DoubleParameter longitude;
+    final DoubleParameter latitude;
+    final DoubleParameter vs30;
+    final NetcdfMetadata netcdfMetadata;
+
+    Metadata(HttpRequest<?> request, String description) {
+      var url = request.getUri().toString();
+      url = url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
+      this.description = description;
+      syntax = new String[] {
+          url + "/{longitude:number}/{latitude:number}",
+          url + "?longitude={number}&latitude={number}",
+          url + "/{longitude:number}/{latitude:number}/{siteClass:NehrpSiteClass}",
+          url + "?longitude={number}&latitude={number}&siteClass={NehrpSiteClass}",
+      };
+
+      var bounds = netcdf().netcdfData().bounds();
+      longitude = new DoubleParameter(
+          "Longitude",
+          "°",
+          Maths.round(bounds.min.longitude, 3),
+          Maths.round(bounds.max.longitude, 3));
+      latitude = new DoubleParameter(
+          "Latitude",
+          "°",
+          Maths.round(bounds.min.latitude, 3),
+          Maths.round(bounds.max.latitude, 3));
+      model = new SourceModel();
+      vs30 = new DoubleParameter(
+          "Vs30",
+          "m/s",
+          150,
+          1500);
+      netcdfMetadata = new NetcdfMetadata();
+    }
+  }
+
+  class NetcdfMetadata {
+    final String netcdfFile;
+    final ScienceBaseMetadata scienceBaseMetadata;
+
+    NetcdfMetadata() {
+      var fileName = netcdf.netcdfPath().getFileName();
+      netcdfFile = fileName == null ? netcdf().netcdfPath().toString() : fileName.toString();
+      scienceBaseMetadata = netcdf().netcdfData().scienceBaseMetadata();
+    }
+  }
+
+  class SourceModel {
+    final String name;
+    final Map<NehrpSiteClass, Double> siteClasses;
+
+    SourceModel() {
+      name = netcdf().netcdfData().scienceBaseMetadata().label;
+      siteClasses = netcdf().netcdfData().vs30Map();
+    }
+  }
+
+  static class ResponseMetadata extends RequestDataSiteClass {
+    final String xLabel;
+    final String yLabel;
+
+    ResponseMetadata(
+        String xLabel,
+        String yLabel,
+        Location site,
+        NehrpSiteClass siteClass) {
+      super(site, siteClass);
+      this.xLabel = xLabel;
+      this.yLabel = yLabel;
+    }
+  }
+
+  static class ResponseData<T extends ResponseMetadata> {
+    final T metadata;
+    final XySequence data;
+
+    ResponseData(T metadata, XySequence data) {
+      this.metadata = metadata;
+      this.data = data;
+    }
+  }
+
+  static class RequestData {
+    Location site;
+
+    RequestData(Location site) {
+      this.site = site;
+    }
+  }
+
+  static class RequestDataSiteClass extends RequestData {
+    NehrpSiteClass siteClass;
+
+    RequestDataSiteClass(Location site, NehrpSiteClass siteClass) {
+      super(site);
+      this.siteClass = siteClass;
+    }
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfWsUtils.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfWsUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..79dafabeadec0284ad344845b51910568287f8f0
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/NetcdfWsUtils.java
@@ -0,0 +1,79 @@
+package gov.usgs.earthquake.nshmp.netcdf.www;
+
+import static com.google.common.base.CaseFormat.UPPER_CAMEL;
+import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
+
+import java.lang.reflect.Type;
+import java.util.logging.Logger;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import gov.usgs.earthquake.nshmp.Maths;
+import gov.usgs.earthquake.nshmp.geo.Location;
+import gov.usgs.earthquake.nshmp.gmm.Imt;
+import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
+import gov.usgs.earthquake.nshmp.www.Response;
+import gov.usgs.earthquake.nshmp.www.WsUtils.EnumSerializer;
+import gov.usgs.earthquake.nshmp.www.WsUtils.NaNSerializer;
+import gov.usgs.earthquake.nshmp.www.meta.Status;
+
+import io.micronaut.http.HttpResponse;
+
+public class NetcdfWsUtils {
+  static final Gson GSON;
+
+  private static final Logger LOGGER = Logger.getLogger(NetcdfWsUtils.class.getName());
+
+  static {
+    GSON = new GsonBuilder()
+        .registerTypeAdapter(Imt.class, new EnumSerializer<Imt>())
+        .registerTypeAdapter(NehrpSiteClass.class, new EnumSerializer<NehrpSiteClass>())
+        .registerTypeAdapter(Double.class, new NaNSerializer())
+        .registerTypeAdapter(Location.class, new LocationSerializer())
+        .disableHtmlEscaping()
+        .serializeNulls()
+        .setPrettyPrinting()
+        .create();
+  }
+
+  public static HttpResponse<String> handleError(
+      Throwable e,
+      String name,
+      String url) {
+    var msg = e.getMessage() + " (see logs)";
+    var svcResponse = new Response<>(Status.ERROR, name, url, msg, url);
+    var response = GSON.toJson(svcResponse);
+    LOGGER.severe(name + " -\n" + response);
+    e.printStackTrace();
+    return HttpResponse.serverError(response);
+  }
+
+  static enum Key {
+    LATITUDE,
+    LONGITUDE,
+    SITE_CLASS,
+    IMT;
+
+    @Override
+    public String toString() {
+      return UPPER_UNDERSCORE.to(UPPER_CAMEL, name());
+    }
+  }
+
+  public static final class LocationSerializer implements JsonSerializer<Location> {
+
+    @Override
+    public JsonElement serialize(Location location, Type type, JsonSerializationContext context) {
+      JsonObject jObj = new JsonObject();
+      jObj.addProperty("longitude", Maths.round(location.longitude, 5));
+      jObj.addProperty("latitude", Maths.round(location.latitude, 5));
+
+      return jObj;
+    }
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Query.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Query.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1a5bffee4dfabd6cde7fcfa6293257ba248959d
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/Query.java
@@ -0,0 +1,21 @@
+package gov.usgs.earthquake.nshmp.netcdf.www;
+
+import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
+
+public class Query {
+  public final Double longitude;
+  public final Double latitude;
+  public final NehrpSiteClass siteClass;
+
+  public Query(Double longitude, Double latitude, NehrpSiteClass siteClass) {
+    this.longitude = longitude;
+    this.latitude = latitude;
+    this.siteClass = siteClass;
+  }
+
+  public static enum Service {
+    BOUNDING,
+    CURVES,
+    CURVES_BY_SITE_CLASS,
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerController.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerController.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec3e9dfc765f07f26416d4678e715ac009e5720c
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/SwaggerController.java
@@ -0,0 +1,46 @@
+package gov.usgs.earthquake.nshmp.netcdf.www;
+
+import java.nio.charset.StandardCharsets;
+import java.util.stream.Collectors;
+
+import com.google.common.io.Resources;
+
+import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
+
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.MediaType;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Inject;
+
+/**
+ * Expose OpenAPI YAML file.
+ *
+ * @author U.S. Geological Survey
+ */
+@Tag(
+    name = "Swagger",
+    description = "Swagger OpenAPI YAML")
+@Hidden
+@Controller("/swagger")
+public class SwaggerController {
+
+  @Inject
+  private NshmpMicronautServlet servlet;
+
+  @Get(produces = MediaType.TEXT_EVENT_STREAM)
+  public HttpResponse<String> doGet(HttpRequest<?> request) {
+    try {
+      var url = Resources.getResource("META-INF/swagger/nshmp-ws-static.yml");
+      var yml = Resources.readLines(url, StandardCharsets.UTF_8)
+          .stream()
+          .collect(Collectors.joining("\n"));
+      return HttpResponse.ok(yml);
+    } catch (Exception e) {
+      return NetcdfWsUtils.handleError(e, "Swagger", request.getUri().getPath());
+    }
+  }
+}
diff --git a/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/meta/DoubleParameter.java b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/meta/DoubleParameter.java
new file mode 100644
index 0000000000000000000000000000000000000000..6a4bd319d258a4b28f2a585c7231213739de187d
--- /dev/null
+++ b/src/lib/src/main/java/gov/usgs/earthquake/nshmp/netcdf/www/meta/DoubleParameter.java
@@ -0,0 +1,16 @@
+package gov.usgs.earthquake.nshmp.netcdf.www.meta;
+
+public final class DoubleParameter {
+
+  private final String name;
+  private final String units;
+  private final double min;
+  private final double max;
+
+  public DoubleParameter(String name, String units, double min, double max) {
+    this.name = name;
+    this.units = units;
+    this.min = min;
+    this.max = max;
+  }
+}
diff --git a/src/lib/src/main/resources/application.yml b/src/lib/src/main/resources/application.yml
new file mode 100644
index 0000000000000000000000000000000000000000..448a7d535a1b583240dcdbd0dacac534c08ad192
--- /dev/null
+++ b/src/lib/src/main/resources/application.yml
@@ -0,0 +1,17 @@
+micronaut:
+  io:
+    watch:
+      paths: src
+      restart: true
+  router:
+    static-resources:
+      swagger:
+        enabled: true
+        paths: classpath:swagger
+        mapping: /**
+
+nshmp-ws-static:
+  # Hazard example
+  netcdf-file: ${netcdf:src/main/resources/hazard-example.nc}
+  # Ground motions example
+  # netcdf-file: ${netcdf:src/main/resources/rtsa-example.nc}
diff --git a/src/lib/src/main/resources/eclipse.importorder b/src/lib/src/main/resources/eclipse.importorder
new file mode 100644
index 0000000000000000000000000000000000000000..2ef1b480f52563e1089abcb404d3476e8a89931b
--- /dev/null
+++ b/src/lib/src/main/resources/eclipse.importorder
@@ -0,0 +1,7 @@
+#Organize Import Order
+0=java
+1=javax
+2=org
+3=com
+4=gov
+5=ucar
diff --git a/src/lib/src/main/resources/hazard-example.nc b/src/lib/src/main/resources/hazard-example.nc
new file mode 100644
index 0000000000000000000000000000000000000000..521866c0689b6ad029a2bfd7f3461ad9e69363f4
Binary files /dev/null and b/src/lib/src/main/resources/hazard-example.nc differ
diff --git a/src/lib/src/main/resources/logback.xml b/src/lib/src/main/resources/logback.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f74e41693090ae1b24cabe0642f4ec814564c4a1
--- /dev/null
+++ b/src/lib/src/main/resources/logback.xml
@@ -0,0 +1,17 @@
+<configuration>
+
+  <appender name="STDOUT"
+    class="ch.qos.logback.core.ConsoleAppender">
+    <withJansi>true</withJansi>
+    <!-- encoders are assigned the type ch.qos.logback.classic.encoder.PatternLayoutEncoder
+      by default -->
+    <encoder>
+      <pattern>%cyan(%d{HH:mm:ss.SSS}) %gray([%thread])
+        %highlight(%-5level) %magenta(%logger{36}) - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="info">
+    <appender-ref ref="STDOUT" />
+  </root>
+</configuration>
diff --git a/src/lib/src/main/resources/nshmp.eclipse-format.xml b/src/lib/src/main/resources/nshmp.eclipse-format.xml
new file mode 100644
index 0000000000000000000000000000000000000000..181dbd16d380cb5688372df1ca124177b8c583f0
--- /dev/null
+++ b/src/lib/src/main/resources/nshmp.eclipse-format.xml
@@ -0,0 +1,916 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<profiles version="13">
+  <profile kind="CodeFormatterProfile" name="nshmp" version="13">
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_after_imports"
+      value="1" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments"
+      value="true" />
+    <setting id="org.eclipse.jdt.core.formatter.indentation.size"
+      value="2" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for"
+      value="insert" />
+    <setting id="org.eclipse.jdt.core.formatter.disabling_tag"
+      value="@formatter:off" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.continuation_indentation"
+      value="2" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants"
+      value="81" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_before_imports"
+      value="1" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_after_package"
+      value="1" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.indent_root_tags"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch"
+      value="true" />
+    <setting id="org.eclipse.jdt.core.formatter.enabling_tag"
+      value="@formatter:on" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations"
+      value="2" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references"
+      value="0" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.compiler.problem.enumIdentifier"
+      value="error" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.line_length" value="80" />
+    <setting id="org.eclipse.jdt.core.formatter.use_on_off_tags"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body"
+      value="0" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_block"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body"
+      value="end_of_line" />
+    <setting id="org.eclipse.jdt.core.formatter.compact_else_if"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_type_parameters"
+      value="0" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.compiler.problem.assertIdentifier"
+      value="error" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer"
+      value="32" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve"
+      value="1" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.format_line_comments"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.align_type_members_on_columns"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_assignment"
+      value="80" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration"
+      value="80" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration"
+      value="0" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.format_header"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode"
+      value="enabled" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration"
+      value="0" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.wrap_before_conditional_operator"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines"
+      value="2147483647" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try"
+      value="80" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column"
+      value="false" />
+    <setting id="org.eclipse.jdt.core.compiler.source" value="1.8" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws"
+      value="insert" />
+    <setting id="org.eclipse.jdt.core.formatter.tabulation.size"
+      value="2" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.format_source_code"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_before_field"
+      value="0" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer"
+      value="2" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_before_method"
+      value="1" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration"
+      value="80" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration"
+      value="80" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.wrap_before_assignment_operator"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" value="1.8" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_switch"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.format_html" value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_compact_if"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_type_arguments"
+      value="0" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk"
+      value="1" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_label"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type"
+      value="1" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.format_block_comments"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields"
+      value="16" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch"
+      value="do not insert" />
+    <setting id="org.eclipse.jdt.core.compiler.compliance"
+      value="1.8" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration"
+      value="common_lines" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration"
+      value="end_of_line" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_before_package"
+      value="0" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header"
+      value="0" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.join_lines_in_comments"
+      value="true" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional"
+      value="insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description"
+      value="false" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement"
+      value="do not insert" />
+    <setting id="org.eclipse.jdt.core.formatter.tabulation.char"
+      value="space" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups"
+      value="1" />
+    <setting id="org.eclipse.jdt.core.formatter.lineSplit"
+      value="100" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation"
+      value="do not insert" />
+    <setting
+      id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch"
+      value="insert" />
+  </profile>
+</profiles>
diff --git a/src/lib/src/main/resources/rtsa-example.nc b/src/lib/src/main/resources/rtsa-example.nc
new file mode 100644
index 0000000000000000000000000000000000000000..0b4e8d883e15107bd60ed7fc8bde3a9da0066427
Binary files /dev/null and b/src/lib/src/main/resources/rtsa-example.nc differ
diff --git a/src/lib/src/main/resources/swagger/favicon.ico b/src/lib/src/main/resources/swagger/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..0c3e87c87b8ecc9900a3b6368598be574e9d0716
Binary files /dev/null and b/src/lib/src/main/resources/swagger/favicon.ico differ
diff --git a/src/lib/src/main/resources/swagger/index.css b/src/lib/src/main/resources/swagger/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..65f90aca32b0c4145e7f2e7fa437cb2448e84b59
--- /dev/null
+++ b/src/lib/src/main/resources/swagger/index.css
@@ -0,0 +1,73 @@
+html,
+body {
+  height: 100%;
+  overflow: hidden;
+}
+
+body {
+  color: #333;
+  font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+  margin: 0;
+  overflow: scroll;
+}
+
+#swagger-ui {
+  padding-top: 1em;
+  padding-bottom: 50px;
+}
+
+.nshmp-template-header,
+.nshmp-template-footer {
+  background-color: #3d5e80;
+  box-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
+  height: 30px;
+  padding: 10px;
+  position: fixed;
+  width: 100vw;
+  z-index: 4;
+}
+
+/* ---- Header ---- */
+
+.nshmp-template-header {
+  top: 0;
+}
+
+.nshmp-template-header .nshmp-template-header--logo {
+  height: 28px;
+  overflow: hidden;
+
+}
+
+.nshmp-template-header--logo .usgs-logo {
+  display: inline-block;
+  height: 100%;
+  position: relative;
+
+}
+
+.nshmp-template-header--logo .usgs-logo img {
+  border: none;
+  height: 140%;
+  max-width: 100%;
+}
+
+/* ---- Footer ---- */
+
+.nshmp-template-footer {
+  bottom: 0;
+  font-size: large;
+  text-align: center;
+}
+
+.nshmp-template-footer a {
+  color: white;
+  cursor: pointer;
+  line-height: 30px;
+  padding: 0 1em;
+  text-decoration: none;
+}
+
+.nshmp-template-footer a:visited {
+  color: white;
+}
diff --git a/src/lib/src/main/resources/swagger/index.html b/src/lib/src/main/resources/swagger/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..5bd722b5c63c977af1306b9b58e7b4d85308c8b3
--- /dev/null
+++ b/src/lib/src/main/resources/swagger/index.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>NSHMP Static Data Services</title>
+    <meta charset="UTF-8" />
+    <link
+      rel="icon"
+      type="image/x-icon"
+      href="favicon.ico"
+    />
+    <script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
+    <script src="https://unpkg.com/swagger-ui-dist/swagger-ui-standalone-preset.js"></script>
+    <link
+      rel="stylesheet"
+      type="text/css"
+      href="https://unpkg.com/swagger-ui-dist/swagger-ui.css"
+    />
+    <link
+      rel="stylesheet"
+      type="text/css"
+      href="https://unpkg.com/swagger-ui-themes@3.0.0/themes/3.x/theme-material.css"
+    />
+    <link
+      rel="stylesheet"
+      type="text/css"
+      href="index.css"
+    />
+  </head>
+
+  <body>
+    <header class="nshmp-template-header">
+      <div class="nshmp-template-header--logo">
+        <a href="" class="usgs-logo">
+          <img alt="USGS" src="usgs-logo.svg" />
+        </a>
+      </div>
+    </header>
+
+    <div id="swagger-ui"></div>
+
+    <footer class="nshmp-template-footer">
+      <a href="https://code.usgs.gov/ghsc/nshmp/nshmp-ws-static/-/blob/main/LICENSE.md">
+        License
+      </a>
+
+      <a href="https://code.usgs.gov/ghsc/nshmp/nshmp-ws-static/-/blob/main/DISCLAIMER.md">
+        Disclaimer
+      </a>
+
+    </footer>
+
+    <script src="index.js"></script>
+  </body>
+</html>
diff --git a/src/lib/src/main/resources/swagger/index.js b/src/lib/src/main/resources/swagger/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..06e5a1326e3df94485c3d6879f3d3d773b4dcecf
--- /dev/null
+++ b/src/lib/src/main/resources/swagger/index.js
@@ -0,0 +1,38 @@
+window.onload = function() {
+  let contextPath = window.location.pathname;
+  contextPath = contextPath.endsWith('/') ? contextPath.slice(0, -1) : contextPath;
+
+  const ui = SwaggerUIBundle({
+    url: `./swagger`,
+    dom_id: '#swagger-ui',
+    tagsSorter: 'alpha',
+    presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
+    plugins: [SwaggerUIBundle.plugins.DownloadUrl, updateContextPath(contextPath)],
+    validatorUrl: null,
+    deepLinking: true
+  });
+
+  window.ui = ui;
+};
+
+function updateContextPath(contextPath) {
+  return {
+    statePlugins: {
+      spec: {
+        wrapActions: {
+          updateJsonSpec: (oriAction) => (...args) => {
+            const [spec] = args;
+            if (spec && spec.paths) {
+              const newPaths = {};
+              Object.entries(spec.paths).forEach(
+                ([path, value]) => (newPaths[contextPath + path] = value)
+              );
+              spec.paths = newPaths;
+            }
+            oriAction(...args);
+          },
+        },
+      },
+    },
+  };
+}
diff --git a/src/lib/src/main/resources/swagger/usgs-logo.svg b/src/lib/src/main/resources/swagger/usgs-logo.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d33f18c4c10451bc3a3540afabb67f420775b8cf
--- /dev/null
+++ b/src/lib/src/main/resources/swagger/usgs-logo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="2353" height="869" viewBox="40.2 28.7 2353.3 869.2"><style>.a{fill:#FFF;}</style><path d="M1135.8 38.7v425.7c0 89.8-50.7 184.6-203.7 184.6 -138 0-204.6-71.5-204.6-184.6V38.7h125.6v417.4c0 74 31.6 103.1 77.3 103.1 54.9 0 79.8-36.6 79.8-103.1V38.7H1135.8z" class="a"/><path d="M1339.9 649c-155.5 0-184.6-91.5-177.9-195.4h125.6c0 57.4 2.5 105.6 71.5 105.6 42.4 0 63.2-27.4 63.2-68.2 0-108.1-252.8-114.7-252.8-293.5 0-94 44.9-168.8 197.9-168.8 122.2 0 182.9 54.9 174.6 180.4h-122.2c0-44.9-7.5-90.6-58.2-90.6 -40.7 0-64.8 22.5-64.8 64 0 113.9 252.8 103.9 252.8 292.7C1549.5 630.7 1435.6 649 1339.9 649z" class="a"/><path d="M2184 649c-155.5 0-184.6-91.5-177.9-195.4h125.6c0 57.4 2.5 105.6 71.5 105.6 42.4 0 63.2-27.4 63.2-68.2 0-108.1-252.8-114.7-252.8-293.5 0-94 44.9-168.8 197.9-168.8 122.2 0 182.9 54.9 174.6 180.4h-122.2c0-44.9-7.5-90.6-58.2-90.6 -40.7 0-64.9 22.5-64.9 64 0 113.9 252.8 103.9 252.8 292.7C2393.6 630.7 2279.6 649 2184 649z" class="a"/><path d="M1784 415.3h73.9v132.5l-6.1 2.3c-19.1 6.7-41.6 11.6-62.4 11.6 -71.5 0-89.8-33.3-89.8-221.2 0-116.4 0-222 78.2-222 60.8 0 73.6 41 74.8 90.6h125.1c1-116.5-74-180.4-192.3-180.4 -205.4 0-212.9 153.8-212.9 305.2 0 227.8 24.7 316.2 235.9 316.2 49 0 127-11.1 163-20.9 2.2-0.6 6.1-3 6.1-6.1 0-16.4 0-298.4 0-298.4h-193.3V415.3L1784 415.3z" class="a"/><path d="M71.3 823.1c-1.2 4.8-2 9.5-1.2 12.8 1 3.4 3.8 5 9.6 5s11.7-3.8 13.2-10.9c4.8-22.5-48.3-17.4-41.2-51.2 4.9-22.9 29-29.8 48.1-29.8 20.1 0 36.3 9.7 29.8 32.5h-29c1.6-7.3 1.7-11.7 0-13.6 -1.5-1.9-3.9-2.3-7.1-2.3 -6.5 0-11 4.2-12.5 11.3 -3.6 16.8 48.7 15.9 41.4 49.9 -3.9 18.5-22 32.3-46.5 32.3 -25.8 0-41.6-6.5-33.7-36.1H71.3L71.3 823.1z" class="a"/><path d="M197.9 789c3.3-15.7 0.5-21-8.8-21 -12.6 0-16 11.1-20.7 33.2 -6.9 32.3-5.6 39.2 5.3 39.2 9.2 0 15.4-8.4 18.4-22.2h29.6c-6.1 28.7-24.3 41.1-52 41.1 -38.2 0-38.3-23.3-31.2-56.4 6.2-28.9 15.6-53.7 54.6-53.7 27.3 0 40.2 12.2 34.3 39.9H197.9z" class="a"/><path d="M258.7 857.1h-29.6l22.5-106h29.6L258.7 857.1zM289.9 710.4l-5 23.3H255.4l5-23.3H289.9z" class="a"/><path d="M1643.2 857.1h-29.6l22.5-106h29.6L1643.2 857.1zM1674.4 710.4l-5 23.3h-29.6l4.9-23.3H1674.4z" class="a"/><path d="M315.8 808.9c-2.7 12.6-6.3 31.5 6.7 31.5 10.5 0 14.7-10.1 16.6-18.9h30c-2.9 11.5-8.7 21-17.2 27.5 -8.3 6.5-19.6 10.3-33.5 10.3 -38.2 0-38.3-23.3-31.2-56.4 6.2-28.9 15.6-53.7 54.6-53.7 39.9 0 38.8 25.8 30.5 59.8H315.8zM346.4 792.3c2.2-10.3 5.7-25-8.3-25 -13.6 0-16.8 15.9-18.7 25H346.4z" class="a"/><path d="M428.3 761.9h0.4c7.9-9.4 16.6-12.8 27.7-12.8 14.1 0 24.7 8.4 21.5 23.1l-18.1 85h-29.6l15.5-73c2-9.4 1.9-16.8-7.6-16.8 -9.4 0-12.7 7.3-14.7 16.8l-15.5 73H378.4l22.5-106h29.6L428.3 761.9z" class="a"/><path d="M545.7 789c3.3-15.7 0.5-21-8.8-21 -12.6 0-16 11.1-20.7 33.2 -6.9 32.3-5.6 39.2 5.3 39.2 9.2 0 15.4-8.4 18.4-22.2h29.6c-6.1 28.7-24.3 41.1-52 41.1 -38.2 0-38.3-23.3-31.2-56.4 6.2-28.9 15.6-53.7 54.6-53.7 27.3 0 40.2 12.2 34.3 39.9H545.7z" class="a"/><path d="M613.3 808.9c-2.7 12.6-6.3 31.5 6.7 31.5 10.5 0 14.7-10.1 16.6-18.9h30c-2.9 11.5-8.7 21-17.2 27.5 -8.3 6.5-19.6 10.3-33.5 10.3 -38.2 0-38.3-23.3-31.2-56.4 6.2-28.9 15.6-53.7 54.6-53.7 39.9 0 38.8 25.8 30.5 59.8H613.3zM643.9 792.3c2.2-10.3 5.7-25-8.3-25 -13.6 0-16.8 15.9-18.7 25H643.9z" class="a"/><path d="M794.1 729.3c-11.8-0.8-15.3 3.8-17.6 14.5l-2.1 7.4h13.8l-3.9 18.3h-13.8l-18.6 87.7h-29.6l18.6-87.7h-13.2l3.9-18.3h13.4c5.9-26.5 11.3-41.2 42.6-41.2 3.6 0 6.9 0.2 10.4 0.4L794.1 729.3z" class="a"/><path d="M784.8 802.8c6.2-28.9 15.6-53.7 54.6-53.7 39 0 38 24.8 31.8 53.7 -7 33.2-17 56.4-55.2 56.4C777.8 859.2 777.7 836 784.8 802.8zM842 801.1c4.7-22 6-33.2-6.6-33.2 -12.6 0-16 11.1-20.7 33.2 -6.9 32.3-5.6 39.2 5.3 39.2C830.9 840.4 835.1 833.4 842 801.1z" class="a"/><path d="M924.3 751.2l-2.9 13.6h0.4c8-12.2 18.9-15.7 30.8-15.7l-5.6 26.4c-25.7-1.7-29.3 13.4-31.5 23.9l-12.3 57.7h-29.6l22.5-106h28.1V751.2z" class="a"/><path d="M1065.3 837.4c-1.4 6.5-2 13.2-2.7 19.7h-27.5l1.7-14.1h-0.4c-8.6 10.9-18.3 16.2-31.1 16.2 -20.6 0-24.9-15.3-20.9-33.8 7.5-35 34.8-36.5 62.2-36.1l1.7-8.2c1.9-9 2-15.5-8.7-15.5 -10.3 0-12.8 7.8-14.6 16.2h-29c2.7-12.8 8.4-21 16.4-25.8 7.8-5 17.8-6.9 29.2-6.9 37.6 0 39 16.2 34.8 35.5L1065.3 837.4zM1013.5 824.2c-1.6 7.6-2.3 16.8 7.3 16.8 17.4 0 19.7-23.5 22.1-35C1028.2 806.6 1017.5 805.3 1013.5 824.2z" class="a"/><path d="M1182.4 789c3.3-15.7 0.5-21-8.8-21 -12.6 0-16 11.1-20.7 33.2 -6.9 32.3-5.6 39.2 5.3 39.2 9.2 0 15.4-8.4 18.4-22.2h29.6c-6.1 28.7-24.3 41.1-52 41.1 -38.2 0-38.3-23.3-31.2-56.4 6.2-28.9 15.6-53.7 54.6-53.7 27.3 0 40.2 12.2 34.3 39.9H1182.4z" class="a"/><path d="M1396.7 837.4c-1.4 6.5-2 13.2-2.7 19.7h-27.5l1.7-14.1h-0.4c-8.6 10.9-18.3 16.2-31.1 16.2 -20.6 0-24.9-15.3-20.9-33.8 7.5-35 34.8-36.5 62.2-36.1l1.7-8.2c1.9-9 2-15.5-8.7-15.5 -10.3 0-12.8 7.8-14.6 16.2h-29c2.7-12.8 8.5-21 16.4-25.8 7.8-5 17.8-6.9 29.2-6.9 37.6 0 38.9 16.2 34.8 35.5L1396.7 837.4zM1344.9 824.2c-1.6 7.6-2.3 16.8 7.3 16.8 17.4 0 19.7-23.5 22.1-35C1359.6 806.6 1348.9 805.3 1344.9 824.2z" class="a"/><path d="M1461.6 761.9h0.4c7.9-9.4 16.6-12.8 27.7-12.8 14.1 0 24.7 8.4 21.5 23.1l-18.1 85h-29.6l15.5-73c2-9.4 1.9-16.8-7.6-16.8 -9.4 0-12.7 7.3-14.7 16.8l-15.5 73h-29.6l22.5-106h29.6L1461.6 761.9z" class="a"/><path d="M1614.9 751.2l-23.5 110.4c-1.6 7.3-7.3 36.3-48 36.3 -22 0-39.7-5.7-34.9-31.5h29c-0.9 4.4-1.1 8.2 0.2 10.7 1.3 2.7 4.4 4.2 9 4.2 7.3 0 12.4-6.9 14.7-17.6l4.3-20.4h-0.4c-6.1 8.2-15.4 12.4-25.1 12.4 -32.5 0-24.7-29.8-19.6-53.7 5-23.3 11.7-52.9 42.5-52.9 10.5 0 18.5 4.6 20.6 14.5h0.4l2.6-12.4H1614.9zM1554.9 837.4c10.7 0 14.5-11.1 19.3-33.6 4.9-23.3 6.7-36.5-4.2-36.5 -11.1 0-14.7 7.8-21.5 40.1C1546.3 817.3 1540.6 837.4 1554.9 837.4z" class="a"/><path d="M1714 761.9h0.4c7.9-9.4 16.6-12.8 27.7-12.8 14.1 0 24.7 8.4 21.5 23.1l-18.1 85h-29.6l15.5-73c2-9.4 1.9-16.8-7.6-16.8 -9.4 0-12.7 7.3-14.7 16.8l-15.5 73h-29.6l22.5-106h29.6L1714 761.9z" class="a"/><path d="M1867.3 751.2l-23.5 110.4c-1.6 7.3-7.3 36.3-48 36.3 -22 0-39.7-5.7-34.8-31.5h29c-0.9 4.4-1.1 8.2 0.2 10.7 1.3 2.7 4.4 4.2 9 4.2 7.3 0 12.4-6.9 14.7-17.6l4.3-20.4h-0.4c-6.1 8.2-15.4 12.4-25.1 12.4 -32.5 0-24.7-29.8-19.6-53.7 4.9-23.3 11.7-52.9 42.5-52.9 10.5 0 18.5 4.6 20.6 14.5h0.4l2.6-12.4H1867.3zM1807.2 837.4c10.7 0 14.5-11.1 19.3-33.6 5-23.3 6.7-36.5-4.2-36.5 -11.1 0-14.7 7.8-21.5 40.1C1798.7 817.3 1793 837.4 1807.2 837.4z" class="a"/><path d="M1913.5 751.2h29.4l-2.4 79.7h0.4l35.3-79.7h31.4l0.4 79.7h0.4l32.2-79.7h28.4l-50.3 106h-31l-3-70.3h-0.4l-35.5 70.3h-31.4L1913.5 751.2z" class="a"/><path d="M2063 802.8c6.2-28.9 15.6-53.7 54.6-53.7 39 0 38 24.8 31.8 53.7 -7 33.2-17 56.4-55.2 56.4C2056.1 859.2 2056 836 2063 802.8zM2120.2 801.1c4.7-22 6-33.2-6.6-33.2 -12.6 0-16 11.1-20.7 33.2 -6.9 32.3-5.6 39.2 5.3 39.2C2109.2 840.4 2113.4 833.4 2120.2 801.1z" class="a"/><path d="M2204.2 751.2l-2.9 13.6h0.4c8-12.2 18.9-15.7 30.8-15.7l-5.6 26.4c-25.7-1.7-29.3 13.4-31.5 23.9l-12.3 57.7h-29.6l22.5-106h28.1V751.2z" class="a"/><path d="M1291 749.1c-11.1 0-19.8 3.4-27.7 12.8h-0.4l10.9-51.5h-29.6l-31.2 146.8h29.6l15.5-73c2-9.4 5.2-16.8 14.7-16.8 9.4 0 9.6 7.3 7.6 16.8l-15.5 73h29.6l18.1-85C1315.6 757.5 1305 749.1 1291 749.1z" class="a"/><polygon points="2253.5 710.4 2222.3 857.1 2251.9 857.1 2283.1 710.4 " class="a"/><path d="M2352.4 710.4l-10.8 50.9h-0.4c-4.2-9-10.8-12.2-21.6-12.2 -29.8 0-36 32.9-40.5 54.3 -4.6 21.8-12.9 55.8 17.7 55.8 11.3 0 20.3-3.6 27.9-13.9h0.4l-2.5 11.8h28.1l31.2-146.8H2352.4L2352.4 710.4zM2332.6 803.2c-5.7 26.6-9.9 37.8-20 37.8 -10.7 0-10.2-11.1-4.6-37.8 4.5-21.2 7.4-35.9 20.2-35.9C2340.5 767.3 2337.2 782 2332.6 803.2z" class="a"/><path d="M203.5 239.7l2.1 2c58.8 49.3 122.3 44.5 170.7 12.3 30.5-20.3 168.4-112 246.3-163.8V38.5H41.7v136.7C77.1 169.1 134.2 175.1 203.5 239.7z" class="a"/><path d="M422 467.2l-68.6-65.8c-9.6-8.8-17.9-15.2-19.7-16.6 -56.1-39.4-108.4-27.4-130.9-19 -6.8 2.7-13.3 6.5-18 9.4L41.7 470.9v173.5h580.9V519.5C609.8 527.6 531.1 570.2 422 467.2z" class="a"/><path d="M198.3 255.2c-3.1-2.9-6.5-6-9.6-8.5 -68.6-53.1-133.4-27.1-147.1-20.6v59.7l33.6-22.6c0 0 51.8-38.4 132.4 1L198.3 255.2z" class="a"/><path d="M278.6 332.4c-3-2.7-6.1-5.4-9.1-8.3 -60.9-51.5-119.7-38.4-144.1-29.4 -6.9 2.8-13.5 6.5-18.2 9.4l-6.6 4.4h0l-58.9 39.4v61.5l109.6-73.8c0 0 51.2-38 131 0.3L278.6 332.4z" class="a"/><path d="M622.7 396.8c-22.8 15.4-46.1 31.1-55.3 37.2 -20.7 13.8-68.5 48.1-148.1 10.1l9.4 9c3.5 3.2 7.8 6.8 12.4 10.4 57.2 42.7 118.2 36.6 164 6.2 6.3-4.2 12.1-8.1 17.6-11.7V396.8z" class="a"/><path d="M622.7 273.6c-50.2 33.7-117.9 79.3-130.6 87.8 -20.7 13.8-69.2 48.6-147.1 11.4l9 8.7c1 0.9 2 1.9 3.1 2.8l-0.1 0.1 0 0 0.1-0.1c60.5 51.2 127 43.3 172.7 12.8 25.8-17.2 62.4-42 92.9-62.6L622.7 273.6 622.7 273.6z" class="a"/><path d="M622.7 151c-72.3 48.5-191 128-205.4 137.6 -20.8 14-70 49.2-146.9 12.5l9.3 9c5.6 5 13 11.3 21 16.5 53.2 34 108.9 28 154.3-2.3 32.6-21.8 112.8-75.1 167.7-111.5L622.7 151 622.7 151z" class="a"/></svg>
\ No newline at end of file
diff --git a/src/lib/src/test/java/gov/usgs/earthquake/nshmp/netcdf/SiteClassTests.java b/src/lib/src/test/java/gov/usgs/earthquake/nshmp/netcdf/SiteClassTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..67f4f1cb2c9e53d212763590bad0b9c58f1875bf
--- /dev/null
+++ b/src/lib/src/test/java/gov/usgs/earthquake/nshmp/netcdf/SiteClassTests.java
@@ -0,0 +1,41 @@
+package gov.usgs.earthquake.nshmp.netcdf;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+class SiteClassTests {
+
+  @Test
+  final void testVs30() {
+    assertEquals(2000, SiteClass.A.vs30());
+    assertEquals(1500, SiteClass.AB.vs30());
+    assertEquals(1080, SiteClass.B.vs30());
+    assertEquals(760, SiteClass.BC.vs30());
+    assertEquals(530, SiteClass.C.vs30());
+    assertEquals(365, SiteClass.CD.vs30());
+    assertEquals(260, SiteClass.D.vs30());
+    assertEquals(185, SiteClass.DE.vs30());
+    assertEquals(150, SiteClass.E.vs30());
+  }
+
+  @Test
+  final void testOfValue() {
+    assertEquals(SiteClass.A, SiteClass.ofValue(2000));
+    assertEquals(SiteClass.AB, SiteClass.ofValue(1500));
+    assertEquals(SiteClass.B, SiteClass.ofValue(1080));
+    assertEquals(SiteClass.BC, SiteClass.ofValue(760));
+    assertEquals(SiteClass.C, SiteClass.ofValue(530));
+    assertEquals(SiteClass.CD, SiteClass.ofValue(365));
+    assertEquals(SiteClass.D, SiteClass.ofValue(260));
+    assertEquals(SiteClass.DE, SiteClass.ofValue(185));
+    assertEquals(SiteClass.E, SiteClass.ofValue(150));
+
+    assertThrows(IllegalArgumentException.class,
+        () -> {
+          SiteClass.ofValue(0);
+        });
+  }
+
+}
diff --git a/src/lib/src/test/resources/invalid-netcdf-file.nc b/src/lib/src/test/resources/invalid-netcdf-file.nc
new file mode 100644
index 0000000000000000000000000000000000000000..24d309ee84916c2518cfd90f8acbb2741cc34795
--- /dev/null
+++ b/src/lib/src/test/resources/invalid-netcdf-file.nc
@@ -0,0 +1 @@
+invalid netcdf file
diff --git a/src/lib/src/test/resources/map-netcdf-test-0p05.geojson b/src/lib/src/test/resources/map-netcdf-test-0p05.geojson
new file mode 100644
index 0000000000000000000000000000000000000000..819addfaddbb0da515d7f47c0bf94e657394d4c7
--- /dev/null
+++ b/src/lib/src/test/resources/map-netcdf-test-0p05.geojson
@@ -0,0 +1,50 @@
+{
+  "type": "FeatureCollection",
+  "features": [
+    {
+      "type": "Feature",
+      "id": "Extents",
+      "geometry": {
+        "type": "Polygon",
+        "coordinates": [
+          [
+            [-105.31, 39.15],
+            [-105.31, 39.31],
+            [-105.10, 39.31],
+            [-105.10, 39.15],
+            [-105.31, 39.15]
+          ]
+        ]
+      },
+      "properties": {
+        "fill": "#AA0078",
+        "stroke": "#AA0078",
+        "title": "Conterminous US Map Extents"
+      }
+    },
+    {
+      "type": "Feature",
+      "geometry": {
+        "type": "Polygon",
+        "coordinates": [
+          [
+            [-105.31, 39.15],
+            [-105.25, 39.25],
+            [-105.30, 39.25],
+            [-105.30, 39.31],
+            [-105.22, 39.30],
+            [-105.15, 39.30],
+            [-105.15, 39.25],
+            [-105.10, 39.25],
+            [-105.13, 39.15],
+            [-105.31, 39.15]
+          ]
+        ]
+      },
+      "properties": {
+        "spacing": 0.05,
+        "title": "Conterminous US netCDF Test Region"
+      }
+    }
+  ]
+}
diff --git a/src/lib/src/test/resources/nshmp-conus-test-fv.0.3.nc b/src/lib/src/test/resources/nshmp-conus-test-fv.0.3.nc
new file mode 100644
index 0000000000000000000000000000000000000000..1cff28e4aabc1888e673b28fe86f0a8c4444e7af
Binary files /dev/null and b/src/lib/src/test/resources/nshmp-conus-test-fv.0.3.nc differ
diff --git a/src/lib/src/test/resources/nshmp-conus-test-fv.0.4.nc b/src/lib/src/test/resources/nshmp-conus-test-fv.0.4.nc
new file mode 100644
index 0000000000000000000000000000000000000000..3d1966c875908dba6b6c2a5d83e06a8456600750
Binary files /dev/null and b/src/lib/src/test/resources/nshmp-conus-test-fv.0.4.nc differ
diff --git a/src/lib/src/test/resources/nshmp-conus-test-fv.1.0.nc b/src/lib/src/test/resources/nshmp-conus-test-fv.1.0.nc
new file mode 100644
index 0000000000000000000000000000000000000000..ac2e5d506110070fbba0ac34edb9748d38df4c15
Binary files /dev/null and b/src/lib/src/test/resources/nshmp-conus-test-fv.1.0.nc differ