diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java
index 7dd22b3c01317235e45b06ccab50e7773d29c2e7..5c13df52084f3a2bcac317f429588f895edd4007 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/Netcdf.java
@@ -11,6 +11,7 @@ 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.
@@ -39,8 +40,9 @@ public abstract class Netcdf<T> {
     }
 
     dataType = NetcdfDataType.getDataType(netcdfPath);
-    netcdfData = getNetcdfData(netcdfPath);
-    netcdfShape = buildNetcdfShape();
+    var reader = getNetcdfData(netcdfPath);
+    netcdfData = reader.netcdfData();
+    netcdfShape = reader.netcdfShape();
   }
 
   /**
@@ -91,7 +93,5 @@ public abstract class Netcdf<T> {
    */
   public abstract T staticData(Location site, NehrpSiteClass siteClass);
 
-  abstract NetcdfShape buildNetcdfShape();
-
-  abstract NetcdfData getNetcdfData(Path netcdfPath);
+  abstract Reader getNetcdfData(Path netcdfPath);
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfGroundMotions.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfGroundMotions.java
index c8ce2480e36d1979b7c7508f94f45a85c8ae2408..3a9c943b3b74a5f31ff24cbf626dcb54537f0359 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfGroundMotions.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfGroundMotions.java
@@ -10,11 +10,9 @@ 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.NetcdfShape.IndexKey;
 import gov.usgs.earthquake.nshmp.netcdf.data.StaticData;
 import gov.usgs.earthquake.nshmp.netcdf.reader.BoundingReaderGroundMotions;
-import gov.usgs.earthquake.nshmp.netcdf.reader.Reader;
+import gov.usgs.earthquake.nshmp.netcdf.reader.ReaderGroundMotions;
 
 import ucar.nc2.dataset.NetcdfDatasets;
 
@@ -53,21 +51,10 @@ public class NetcdfGroundMotions extends Netcdf<XySequence> {
   }
 
   @Override
-  NetcdfShape buildNetcdfShape() {
-    return NetcdfShape.builder()
-        .add(IndexKey.SITE_CLASS, 0)
-        .add(IndexKey.LATITUDE, 1)
-        .add(IndexKey.LONGITUDE, 2)
-        .add(IndexKey.IMT, 3)
-        .build();
-  }
-
-  @Override
-  NetcdfData getNetcdfData(Path netcdfPath) {
+  ReaderGroundMotions getNetcdfData(Path netcdfPath) {
     try (var ncd = NetcdfDatasets.openDataset(netcdfPath.toString())) {
       var group = ncd.getRootGroup();
-      var reader = new Reader(group);
-      return reader.readData();
+      return new ReaderGroundMotions(group);
     } catch (IOException e) {
       throw new RuntimeException("Could not read Netcdf file [" + netcdfPath + " ]");
     }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfHazardCurves.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfHazardCurves.java
index aa94e7cc2d53a2ff23e856347744ee6b36aadea5..4fedc864f22f334c6599266d233e791a589b3003 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfHazardCurves.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/NetcdfHazardCurves.java
@@ -9,8 +9,6 @@ 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.NetcdfDataHazardCurves;
-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 gov.usgs.earthquake.nshmp.netcdf.data.StaticDataHazardCurves;
 import gov.usgs.earthquake.nshmp.netcdf.reader.BoundingReaderHazardCurves;
@@ -53,22 +51,10 @@ public class NetcdfHazardCurves extends Netcdf<StaticDataHazardCurves> {
   }
 
   @Override
-  NetcdfShape buildNetcdfShape() {
-    return NetcdfShape.builder()
-        .add(IndexKey.SITE_CLASS, 0)
-        .add(IndexKey.IMT, 1)
-        .add(IndexKey.LATITUDE, 2)
-        .add(IndexKey.LONGITUDE, 3)
-        .add(IndexKey.IML, 4)
-        .build();
-  }
-
-  @Override
-  NetcdfDataHazardCurves getNetcdfData(Path netcdfPath) {
+  ReaderHazardCurves getNetcdfData(Path netcdfPath) {
     try (var ncd = NetcdfDatasets.openDataset(netcdfPath.toString())) {
       var group = ncd.getRootGroup();
-      var reader = new ReaderHazardCurves(group);
-      return reader.readData();
+      return new ReaderHazardCurves(group);
     } catch (IOException e) {
       throw new RuntimeException("Could not read Netcdf file [" + netcdfPath + " ]");
     }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java
index 632de5c87c400353264b900a85162704e9258afb..698207bf4ae2a9d5c1c4cd5b24ad11ac491a8633 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/data/NetcdfShape.java
@@ -1,8 +1,13 @@
 package gov.usgs.earthquake.nshmp.netcdf.data;
 
-import java.util.Arrays;
+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.
@@ -17,26 +22,91 @@ public class NetcdfShape {
     indexMap = builder.indexMap;
   }
 
-  public int[] buildShape(IndexMap... sizes) {
-    int[] shape = new int[indexMap.size()];
-    Arrays.fill(shape, 0);
-
-    Arrays.stream(sizes).forEach(size -> {
-      shape[indexMap.get(size.indexKey)] = size.size;
-    });
+  /**
+   * Returns builder to build a NetCDF shape.
+   */
+  public static Builder builder() {
+    return new Builder();
+  }
 
-    return shape;
+  /**
+   * Returns a builder to build an array shape for NetCDF origin and shapes.
+   */
+  public BuildShape buildShape() {
+    return new BuildShape();
   }
 
-  public static Builder builder() {
-    return new Builder();
+  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 IndexMap {
+  public static class KeySize {
     public final IndexKey indexKey;
     public final int size;
 
-    public IndexMap(IndexKey indexKey, int size) {
+    private KeySize(IndexKey indexKey, int size) {
       this.indexKey = indexKey;
       this.size = size;
     }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java
index d750ea5ba47d6582c1436aaf541269f8aedddece..83f5c2e3cfab7d21a6a56b1678b7b6667cc80cb1 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReader.java
@@ -101,9 +101,10 @@ public abstract class BoundingReader<T> {
         int longitudeIndex,
         int latitudeIndex) {
       this.location = location;
-      origin = netcdfShape.buildShape(
-          new NetcdfShape.IndexMap(IndexKey.LATITUDE, latitudeIndex),
-          new NetcdfShape.IndexMap(IndexKey.LONGITUDE, longitudeIndex));
+      origin = netcdfShape.buildShape()
+          .add(IndexKey.LATITUDE, latitudeIndex)
+          .add(IndexKey.LONGITUDE, longitudeIndex)
+          .build();
     }
   }
 
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderGroundMotions.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderGroundMotions.java
index fc4b2f3fb958f5cbc8e86d5d60a66b8030ff31b8..1923bfd08d08719360b9832e3a879fbdb5bc35fa 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderGroundMotions.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderGroundMotions.java
@@ -9,7 +9,6 @@ import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.netcdf.Netcdf;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfGroundMotions;
 import gov.usgs.earthquake.nshmp.netcdf.data.BoundingData;
-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 gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils.Key;
@@ -65,29 +64,31 @@ public class BoundingReaderGroundMotions extends BoundingReader<XySequence> {
       var targetGroup = ncd.getRootGroup();
       var netcdfShape = netcdf.netcdfShape();
 
-      var targetOrigin = netcdfShape.buildShape(
-          new NetcdfShape.IndexMap(IndexKey.LATITUDE, idxLatLL),
-          new NetcdfShape.IndexMap(IndexKey.LONGITUDE, idxLonLL));
-      var targetShape = netcdfShape.buildShape(
-          new NetcdfShape.IndexMap(IndexKey.SITE_CLASS, netcdfData.siteClasses().size()),
-          new NetcdfShape.IndexMap(IndexKey.LATITUDE, 2),
-          new NetcdfShape.IndexMap(IndexKey.LONGITUDE, 2),
-          new NetcdfShape.IndexMap(IndexKey.IMT, netcdfData.imts().size()));
-
-      /*
-       * Array aHazards now has shape [nVs,2,2,nImt] ...so origin will now be
-       * [0,0,0,0] for LL grid point ...and shape of requested array is
-       * [nVs,1,1,nImt]
-       */
+      // Build origin array, e.g. [lon, lat, 0, 0]
+      var targetOrigin = netcdfShape.buildShape()
+          .add(IndexKey.LATITUDE, idxLatLL)
+          .add(IndexKey.LONGITUDE, idxLonLL)
+          .build();
+
+      // Build target shape array, e.g. [2, 2, nImt, nSiteClass]
+      var targetShape = netcdfShape.buildShape()
+          .add(IndexKey.SITE_CLASS, netcdfData.siteClasses().size())
+          .add(IndexKey.LATITUDE, 2)
+          .add(IndexKey.LONGITUDE, 2)
+          .add(IndexKey.IMT, netcdfData.imts().size())
+          .build();
+
       var groundMotionArray = targetGroup
           .findVariableLocal(Key.GROUND_MOTION)
           .read(targetOrigin, targetShape);
 
-      var shape = netcdfShape.buildShape(
-          new NetcdfShape.IndexMap(IndexKey.SITE_CLASS, netcdfData.siteClasses().size()),
-          new NetcdfShape.IndexMap(IndexKey.LATITUDE, 1),
-          new NetcdfShape.IndexMap(IndexKey.LONGITUDE, 1),
-          new NetcdfShape.IndexMap(IndexKey.IMT, netcdfData.imts().size()));
+      // Main data shape, e.g. [1, 1, nImt, nSiteClass]
+      var shape = netcdfShape.buildShape()
+          .add(IndexKey.SITE_CLASS, netcdfData.siteClasses().size())
+          .add(IndexKey.LATITUDE, 1)
+          .add(IndexKey.LONGITUDE, 1)
+          .add(IndexKey.IMT, netcdfData.imts().size())
+          .build();
 
       for (var boundingLocation : boundingLocations) {
         boundingData.put(
@@ -134,8 +135,19 @@ public class BoundingReaderGroundMotions extends BoundingReader<XySequence> {
           })
           .toArray();
 
-      var origin = new int[] { iSiteClass, 0 };
-      var shape = new int[] { 1, imts.size() };
+      // Build origin array, e.g [0, siteClass]
+      var origin = netcdf.netcdfShape().buildShape()
+          .add(IndexKey.IMT, 0)
+          .add(IndexKey.SITE_CLASS, iSiteClass)
+          .reduce()
+          .build();
+
+      // Build shape array, e.g. [nImts, 1]
+      var shape = netcdf.netcdfShape().buildShape()
+          .add(IndexKey.IMT, imts.size())
+          .add(IndexKey.SITE_CLASS, 1)
+          .reduce()
+          .build();
 
       try {
         var xySequence = XySequence.create(
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java
index f48287c2a954d1eedb06e966c29720b487bedbc4..596769d81ff0b96e95879f454ab6b1491cc35e2f 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/BoundingReaderHazardCurves.java
@@ -8,7 +8,6 @@ import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.netcdf.Netcdf;
 import gov.usgs.earthquake.nshmp.netcdf.NetcdfHazardCurves;
 import gov.usgs.earthquake.nshmp.netcdf.data.BoundingData;
-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 gov.usgs.earthquake.nshmp.netcdf.data.StaticDataHazardCurves;
@@ -62,29 +61,31 @@ public class BoundingReaderHazardCurves extends BoundingReader<StaticDataHazardC
       var targetGroup = ncd.getRootGroup();
       var netcdfShape = netcdf.netcdfShape();
 
-      var targetOrigin = netcdfShape.buildShape(
-          new NetcdfShape.IndexMap(IndexKey.LATITUDE, idxLatLL),
-          new NetcdfShape.IndexMap(IndexKey.LONGITUDE, idxLonLL));
-      var targetShape = netcdfShape.buildShape(
-          new NetcdfShape.IndexMap(IndexKey.SITE_CLASS, netcdfData.siteClasses().size()),
-          new NetcdfShape.IndexMap(IndexKey.LATITUDE, 2),
-          new NetcdfShape.IndexMap(IndexKey.LONGITUDE, 2),
-          new NetcdfShape.IndexMap(IndexKey.IMT, netcdfData.imts().size()),
-          new NetcdfShape.IndexMap(IndexKey.IML, netcdfData.nIml()));
-
-      /*
-       * Array aHazards now has shape [nVs,nImt,2,2,nIml] ...so origin will now
-       * be [0,0,0,0,0] for LL grid point ...and shape of requested array is
-       * [nVs,nImt,1,1,nIml]
-       */
+      // Build origin array, e.g. [lon, lat, 0, 0, 0]
+      var targetOrigin = netcdfShape.buildShape()
+          .add(IndexKey.LATITUDE, idxLatLL)
+          .add(IndexKey.LONGITUDE, idxLonLL)
+          .build();
+
+      // Build target shape array, e.g. [2, 2, nIML, nIMT, nSiteClass]
+      var targetShape = netcdfShape.buildShape()
+          .add(IndexKey.LONGITUDE, 2)
+          .add(IndexKey.LATITUDE, 2)
+          .add(IndexKey.IML, netcdfData.nIml())
+          .add(IndexKey.IMT, netcdfData.imts().size())
+          .add(IndexKey.SITE_CLASS, netcdfData.siteClasses().size())
+          .build();
+
       var aHazards = targetGroup.findVariableLocal(Key.HAZARD).read(targetOrigin, targetShape);
 
-      var shape = netcdfShape.buildShape(
-          new NetcdfShape.IndexMap(IndexKey.SITE_CLASS, netcdfData.siteClasses().size()),
-          new NetcdfShape.IndexMap(IndexKey.LATITUDE, 1),
-          new NetcdfShape.IndexMap(IndexKey.LONGITUDE, 1),
-          new NetcdfShape.IndexMap(IndexKey.IMT, netcdfData.imts().size()),
-          new NetcdfShape.IndexMap(IndexKey.IML, netcdfData.nIml()));
+      // Build final shape array, e.g. [1, 1, nIML, nIMT, nSiteClass]
+      var shape = netcdfShape.buildShape()
+          .add(IndexKey.LONGITUDE, 1)
+          .add(IndexKey.LATITUDE, 1)
+          .add(IndexKey.IML, netcdfData.nIml())
+          .add(IndexKey.IMT, netcdfData.imts().size())
+          .add(IndexKey.SITE_CLASS, netcdfData.siteClasses().size())
+          .build();
 
       for (var boundingLocation : boundingLocations) {
         boundingData.put(
@@ -117,12 +118,26 @@ public class BoundingReaderHazardCurves extends BoundingReader<StaticDataHazardC
 
     for (int iSiteClass = 0; iSiteClass < netcdfData.siteClasses().size(); iSiteClass++) {
       var siteClass = netcdfData.siteClasses().get(iSiteClass);
-
       var imtHazardMap = StaticDataHazardCurves.builder();
+
       for (int iImt = 0; iImt < netcdfData.imts().size(); iImt++) {
         var imt = netcdfData.imts().get(iImt);
-        var origin = new int[] { iSiteClass, iImt, 0 };
-        var shape = new int[] { 1, 1, netcdfData.nIml() };
+
+        // Build origin array, e.g [0, imt, siteClass]
+        var origin = netcdf.netcdfShape().buildShape()
+            .add(IndexKey.IML, 0)
+            .add(IndexKey.IMT, iImt)
+            .add(IndexKey.SITE_CLASS, iSiteClass)
+            .reduce()
+            .build();
+
+        // Build shape array, e.g. [nIML, 1, 1]
+        var shape = netcdf.netcdfShape().buildShape()
+            .add(IndexKey.IML, netcdfData.nIml())
+            .add(IndexKey.IMT, 1)
+            .add(IndexKey.SITE_CLASS, 1)
+            .reduce()
+            .build();
 
         try {
           var xySequence = XySequence.create(
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
index 4f779cac6cc9256768d0de98f42a7b1a91880e9f..abedf45dfe5658fc40f45029b96c3b23bddf2bd7 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/NetcdfUtils.java
@@ -324,7 +324,6 @@ public class NetcdfUtils {
     public static final String ID = "id";
     public static final String LABEL = "label";
     public static final String DATA_TYPE = "dataType";
-    public static final String GRID_MASK = "gridMask";
     public static final String GROUND_MOTION = "groundMotion";
     public static final String HAZARD = "hazard";
     public static final String HISTORY = "history";
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java
index 6d641925fc5f3fafea1bb9f6daf7eb3e41ca3208..d2ec1f1f67482bc94dab1954adc0b491fa1674f6 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/Reader.java
@@ -14,6 +14,7 @@ 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;
 
@@ -24,21 +25,32 @@ import ucar.nc2.Group;
  *
  * @author U.S. Geological Survey
  */
-public class Reader {
+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) {
+  public Reader(Group targetGroup) throws IOException {
     this.targetGroup = targetGroup;
+    netcdfData = readData(targetGroup);
+    netcdfShape = buildNetcdfShape(targetGroup);
   }
 
-  /**
-   * Returns the netcdf dara.
-   */
-  public NetcdfData readData() throws IOException {
+  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 vs30s = NetcdfUtils.getDoubleArray(targetGroup, Key.VS30);
     var vImts = targetGroup.findVariableLocal(Key.IMT);
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderGroundMotions.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderGroundMotions.java
new file mode 100644
index 0000000000000000000000000000000000000000..f45da139d717469be4fc52160892913a4d392736
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderGroundMotions.java
@@ -0,0 +1,32 @@
+package gov.usgs.earthquake.nshmp.netcdf.reader;
+
+import java.io.IOException;
+
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfShape;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfShape.IndexKey;
+import gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils.Key;
+
+import ucar.nc2.Group;
+
+/**
+ * Read in ground motions NetCDF files
+ *
+ * @author U.S. Geological Survey
+ */
+public class ReaderGroundMotions extends Reader {
+
+  public ReaderGroundMotions(Group targetGroup) throws IOException {
+    super(targetGroup);
+  }
+
+  @Override
+  NetcdfShape buildNetcdfShape(Group group) {
+    var vData = group.findVariableLocal(Key.GROUND_MOTION);
+    return NetcdfShape.builder()
+        .add(IndexKey.IMT, vData.findDimensionIndex(Key.IMT))
+        .add(IndexKey.LATITUDE, vData.findDimensionIndex(Key.LAT))
+        .add(IndexKey.LONGITUDE, vData.findDimensionIndex(Key.LON))
+        .add(IndexKey.SITE_CLASS, vData.findDimensionIndex(Key.SITE_CLASS))
+        .build();
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderHazardCurves.java b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderHazardCurves.java
index ba76d28fa902c67dfe07a38eb962636e9de1535b..dfcbb892dce8f4bcfcb0c8655a45f1bc7ea9b0c8 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderHazardCurves.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/netcdf/reader/ReaderHazardCurves.java
@@ -12,6 +12,8 @@ import com.google.common.reflect.TypeToken;
 
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfDataHazardCurves;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfShape;
+import gov.usgs.earthquake.nshmp.netcdf.data.NetcdfShape.IndexKey;
 import gov.usgs.earthquake.nshmp.netcdf.reader.NetcdfUtils.Key;
 
 import ucar.ma2.DataType;
@@ -26,13 +28,18 @@ import ucar.nc2.Variable;
  */
 public class ReaderHazardCurves extends Reader {
 
-  public ReaderHazardCurves(Group targetGroup) {
+  public ReaderHazardCurves(Group targetGroup) throws IOException {
     super(targetGroup);
   }
 
   @Override
-  public NetcdfDataHazardCurves readData() throws IOException {
-    var coords = super.readData();
+  public NetcdfDataHazardCurves netcdfData() {
+    return (NetcdfDataHazardCurves) netcdfData;
+  }
+
+  @Override
+  NetcdfDataHazardCurves readData(Group targetGroup) throws IOException {
+    var coords = super.readData(targetGroup);
     var vImls = targetGroup.findVariableLocal(Key.IMLS);
     var vImts = targetGroup.findVariableLocal(Key.IMT);
 
@@ -66,6 +73,18 @@ public class ReaderHazardCurves extends Reader {
         .build();
   }
 
+  @Override
+  NetcdfShape buildNetcdfShape(Group group) {
+    var vData = group.findVariableLocal(Key.HAZARD);
+    return NetcdfShape.builder()
+        .add(IndexKey.IML, vData.findDimensionIndex(Key.IMLS))
+        .add(IndexKey.IMT, vData.findDimensionIndex(Key.IMT))
+        .add(IndexKey.LATITUDE, vData.findDimensionIndex(Key.LAT))
+        .add(IndexKey.LONGITUDE, vData.findDimensionIndex(Key.LON))
+        .add(IndexKey.SITE_CLASS, vData.findDimensionIndex(Key.SITE_CLASS))
+        .build();
+  }
+
   /*
    * convert 2D Iml variable (dimensions Imt, Iml) to map of Imls by Imt
    *
diff --git a/src/main/resources/hazard-example.nc b/src/main/resources/hazard-example.nc
index b7957438c610d4e5c5ee17d3efb711c956206ee7..fa9d0d8324e324a529acb7cdeb13a0c42d7ae094 100644
Binary files a/src/main/resources/hazard-example.nc and b/src/main/resources/hazard-example.nc differ
diff --git a/src/main/resources/rtsa-example.nc b/src/main/resources/rtsa-example.nc
index ed7582449dddea69d8082d11e3267c919390b66a..67961f49950d8840d29573a5744b9499d71470ef 100644
Binary files a/src/main/resources/rtsa-example.nc and b/src/main/resources/rtsa-example.nc differ