diff --git a/modules/util/gfs_reader.py b/modules/util/gfs_reader.py
index 1e4a872fb089d347033ef38652a8390af0c770fd..0bc0785ce8f42c10373d19e16debda62027fde8c 100644
--- a/modules/util/gfs_reader.py
+++ b/modules/util/gfs_reader.py
@@ -1,13 +1,10 @@
 import datetime
 from datetime import timezone
 import glob
+import os
 import numpy as np
 import xarray as xr
 from util.util import value_to_index
-from metpy import *
-# import cartopy
-# from cartopy import *
-# import cartopy.crs as ccrs
 
 # gfs_directory = '/apollo/cloud/Ancil_Data/clavrx_ancil_data/dynamic/gfs/'
 gfs_directory = '/Users/tomrink/data/gfs/'
@@ -16,8 +13,15 @@ gfs_date_format = '%y%m%d'
 # force incoming longitude to (0, 360) to match GFS
 lon360 = True
 # GFS half degree resolution
-lat_coords = np.linspace(-90, 90, 361)
-lon_coords = np.linspace(0, 359.5, 720)
+NX = 720
+NY = 361
+lat_coords = np.linspace(-90, 90, NY)
+lon_coords = np.linspace(0, 359.5, NX)
+
+plevs = np.array([10.0, 20.0, 30.0, 50.0, 70.0, 100.0, 150.0, 200.0, 250.0, 300.0,
+                  350.0, 400.0, 450.0, 500.0, 550.0, 600.0, 650.0, 700.0, 750.0, 800.0,
+                  850.0, 900.0, 925.0, 950.0, 975.0, 1000.0])
+NZ = plevs.shape[0]
 
 
 class MyGenericException(Exception):
@@ -25,6 +29,13 @@ class MyGenericException(Exception):
         self.message = message
 
 
+def get_timestamp(filename):
+    toks = filename.split('.')
+    tstr = toks[1].split('_')[0]
+    dto = datetime.datetime.strptime(tstr, gfs_date_format + '%H').replace(tzinfo=timezone.utc)
+    return dto.timestamp()
+
+
 def get_time_tuple_utc(timestamp):
     dt_obj = datetime.datetime.fromtimestamp(timestamp, timezone.utc)
     return dt_obj, dt_obj.timetuple()
@@ -33,15 +44,18 @@ def get_time_tuple_utc(timestamp):
 def get_bounding_gfs_files(timestamp):
     dt_obj, time_tup = get_time_tuple_utc(timestamp)
     date_str = dt_obj.strftime(gfs_date_format)
-    dt_obj0 = datetime.datetime.strptime(date_str, gfs_date_format).replace(tzinfo=timezone.utc)
-    dt_obj1 = dt_obj0 + datetime.timedelta(days=1)
-    date_str_1 = dt_obj1.strftime(gfs_date_format)
-
-    flist_0 = glob.glob(gfs_directory+'gfs.'+date_str+'??_F012.h5')
-    flist_1 = glob.glob(gfs_directory+'gfs.'+date_str_1+'00_F012.h5')
-    if (len(flist_0) == 0) or (len(flist_1) == 0):
+    dt_obj = datetime.datetime.strptime(date_str, gfs_date_format).replace(tzinfo=timezone.utc)
+    dt_obj_r = dt_obj + datetime.timedelta(days=1)
+    date_str_r = dt_obj_r.strftime(gfs_date_format)
+    dt_obj_l = dt_obj - datetime.timedelta(days=1)
+    date_str_l = dt_obj_l.strftime(gfs_date_format)
+
+    flist_l = glob.glob(gfs_directory+'gfs.'+date_str_l+'??_F012.h5')
+    flist = glob.glob(gfs_directory+'gfs.'+date_str+'??_F012.h5')
+    flist_r = glob.glob(gfs_directory+'gfs.'+date_str_r+'??_F012.h5')
+    filelist = flist_l + flist + flist_r
+    if len(filelist) == 0:
         return None, None, None, None
-    filelist = flist_0 + flist_1
 
     ftimes = []
     for pname in filelist:  # TODO: make better with regular expressions (someday)
@@ -77,14 +91,13 @@ def get_bounding_gfs_files(timestamp):
 
 
 def get_horz_layer(xr_dataset, fld_name, press, lon_range=None, lat_range=None):
-    plevs = xr_dataset['pressure levels']
-    p_idx = value_to_index(plevs.values, press)
+    p_idx = value_to_index(plevs, press)
     fld = xr_dataset[fld_name]
 
     x_lo = 0
-    x_hi = xr_dataset.fakeDim2.shape[0]
+    x_hi = NX
     y_lo = 0
-    y_hi = xr_dataset.fakeDim1.shape[0]
+    y_hi = NY
 
     if lon_range is not None:
         lon_lo = lon_range[0]
@@ -111,13 +124,12 @@ def get_horz_layer(xr_dataset, fld_name, press, lon_range=None, lat_range=None):
 
 
 def get_horz_layer_s(xr_dataset, fld_names, press, lon_range=None, lat_range=None):
-    plevs = xr_dataset['pressure levels']
-    p_idx = value_to_index(plevs.values, press)
+    p_idx = value_to_index(plevs, press)
 
     x_lo = 0
-    x_hi = xr_dataset.fakeDim2.shape[0]
+    x_hi = NX
     y_lo = 0
-    y_hi = xr_dataset.fakeDim1.shape[0]
+    y_hi = NY
 
     if lon_range is not None:
         lon_lo = lon_range[0]
@@ -147,26 +159,28 @@ def get_horz_layer_s(xr_dataset, fld_names, press, lon_range=None, lat_range=Non
     return sub_fld
 
 
-def get_time_interpolated_layer(ds_0, ds_1, time_0, time_1, time, fld_name, press, lon_range=None, lat_range=None):
-    lyr_0 = get_horz_layer(ds_0, fld_name, press, lon_range=lon_range, lat_range=lat_range)
-    lyr_1 = get_horz_layer(ds_1, fld_name, press, lon_range=lon_range, lat_range=lat_range)
+def get_time_interpolated_layer(xr_dataset_s, time_s, time, fld_name, press, lon_range=None, lat_range=None, method='linear'):
+    layer_s = []
+    for ds in xr_dataset_s:
+        lyr = get_horz_layer(ds, fld_name, press, lon_range=lon_range, lat_range=lat_range)
+        layer_s.append(lyr)
 
-    lyr = xr.concat([lyr_0, lyr_1], 'time')
-    lyr = lyr.assign_coords(time=[time_0, time_1])
+    lyr = xr.concat(layer_s, 'time')
+    lyr = lyr.assign_coords(time=time_s)
 
-    intrp_lyr = lyr.interp(time=time, method='linear')
-    intrp_lyr = intrp_lyr.expand_dims('channel')
-    intrp_lyr = intrp_lyr.assign_coords(channel=[fld_name])
+    intrp_lyr = lyr.interp(time=time, method=method)
 
     return intrp_lyr
 
 
-def get_time_interpolated_layer_s(ds_0, ds_1, time_0, time_1, time, fld_name_s, press, lon_range=None, lat_range=None):
-    lyr_s_0 = get_horz_layer_s(ds_0, fld_name_s, press, lon_range=lon_range, lat_range=lat_range)
-    lyr_s_1 = get_horz_layer_s(ds_1, fld_name_s, press, lon_range=lon_range, lat_range=lat_range)
+def get_time_interpolated_layer_s(xr_dataset_s, time_s, time, fld_name_s, press, lon_range=None, lat_range=None, method='linear'):
+    layer_s = []
+    for ds in xr_dataset_s:
+        lyr = get_horz_layer_s(ds, fld_name_s, press, lon_range=lon_range, lat_range=lat_range)
+        layer_s.append(lyr)
 
-    lyr = xr.concat([lyr_s_0, lyr_s_1], 'time')
-    lyr = lyr.assign_coords(time=[time_0, time_1])
+    lyr = xr.concat(layer_s, 'time')
+    lyr = lyr.assign_coords(time=time_s)
 
     intrp_lyr = lyr.interp(time=time, method='linear')
 
@@ -177,7 +191,6 @@ def get_vert_profile(xr_dataset, fld_name, lons, lats):
     if lon360:  # convert -180/+180 to 0,360
         lons = np.where(lons < 0, lons + 360, lons)
 
-    plevs = xr_dataset['pressure levels']
 
     fld = xr_dataset[fld_name]
     fld = fld.assign_coords(fakeDim2=lon_coords, fakeDim1=lat_coords, fakeDim0=plevs)
@@ -194,7 +207,6 @@ def get_vert_profile_s(xr_dataset, fld_name_s, lons, lats):
     if lon360:  # convert -180,+180 to 0,360
         lons = np.where(lons < 0, lons + 360, lons)
 
-    plevs = xr_dataset['pressure levels']
 
     fld_s = []
     for fld_name in fld_name_s:
@@ -230,31 +242,31 @@ def get_point(xr_dataset, fld_name, lons, lats):
     return intrp_fld
 
 
-def get_time_interpolated_vert_profile(ds_0, ds_1, time0, time1, fld_name, time, lons, lats):
-
-    prof_0 = get_vert_profile(ds_0, fld_name, lons, lats)
-    prof_1 = get_vert_profile(ds_1, fld_name, lons, lats)
+def get_time_interpolated_vert_profile(xr_dataset_s, time_s, fld_name, time, lons, lats, method='linear'):
+    prof_s = []
+    for ds in xr_dataset_s:
+        vp = get_vert_profile(ds, fld_name, lons, lats)
+        prof_s.append(vp)
 
-    prof = xr.concat([prof_0, prof_1], 'time')
-    prof = prof.assign_coords(time=[time0, time1])
-
-    intrp_prof = prof.interp(time=time, method='linear')
+    prof = xr.concat(prof_s, 'time')
+    prof = prof.assign_coords(time=time_s)
 
+    intrp_prof = prof.interp(time=time, method=method)
     intrp_prof = intrp_prof.values
 
     return intrp_prof
 
 
-def get_time_interpolated_vert_profile_s(ds_0, ds_1, time0, time1, fld_name_s, time, lons, lats):
-
-    prof_0 = get_vert_profile_s(ds_0, fld_name_s, lons, lats)
-    prof_1 = get_vert_profile_s(ds_1, fld_name_s, lons, lats)
-
-    prof = xr.concat([prof_0, prof_1], 'time')
-    prof = prof.assign_coords(time=[time0, time1])
+def get_time_interpolated_vert_profile_s(xr_dataset_s, time_s, fld_name_s, time, lons, lats, method='linear'):
+    prof_s = []
+    for ds in xr_dataset_s:
+        vp = get_vert_profile_s(ds, fld_name_s, lons, lats)
+        prof_s.append(vp)
 
-    intrp_prof = prof.interp(time=time, method='linear')
+    prof = xr.concat(prof_s, 'time')
+    prof = prof.assign_coords(time=time_s)
 
+    intrp_prof = prof.interp(time=time, method=method)
     intrp_prof = intrp_prof.values
 
     return intrp_prof
@@ -272,3 +284,103 @@ def get_time_interpolated_point(ds_0, ds_1, time0, time1, fld_name, time, lons,
     intrp_vals = intrp_vals.values
 
     return intrp_vals
+
+
+def get_time_interpolated_voxel(xr_dataset_s, time_s, time, fld_name, lon, lat, press, x_width=5, y_width=5, z_width=3, method='linear'):
+    vox_s = []
+    for ds in xr_dataset_s:
+        vox = get_voxel(ds, fld_name, lon, lat, press, x_width=x_width, y_width=y_width, z_width=z_width)
+        vox_s.append(vox)
+
+    vox = xr.concat(vox_s, 'time')
+    vox = vox.assign_coords(time=time_s)
+
+    intrp_vox = vox.interp(time=time, method=method)
+
+    return intrp_vox
+
+
+def get_voxel(xr_dataset, fld_name, lon, lat, press, x_width=5, y_width=5, z_width=3):
+    if lon360:
+        if lon < 0:
+            lon += 360
+
+    fld = xr_dataset[fld_name]
+
+    p_c = value_to_index(plevs, press)
+    x_c = value_to_index(lon_coords, lon)
+    y_c = value_to_index(lat_coords, lat)
+    y_h = int(y_width / 2)
+    x_h = int(x_width / 2)
+    p_h = int(z_width / 2)
+
+    y_start = y_c - y_h
+    x_start = x_c - x_h
+    z_start = p_c - p_h
+    if y_start < 0 or x_start < 0 or z_start < 0:
+        return None
+
+    y_stop = y_c + y_h + 1
+    x_stop = x_c + x_h + 1
+    z_stop = p_c + p_h + 1
+    if y_stop > NY-1 or x_stop > NX-1 or z_stop > NZ-1:
+        return None
+
+    sub_fld = fld[y_start:y_stop, x_start:x_stop, z_start:z_stop]
+
+    sub_fld = sub_fld.expand_dims('channel')
+    sub_fld = sub_fld.assign_coords(channel=[fld_name], fakeDim2=lon_coords[x_start:x_stop],
+                                    fakeDim1=lat_coords[y_start:y_stop], fakeDim0=plevs[z_start:z_stop])
+
+    return sub_fld
+
+
+def get_time_interpolated_voxel_s(xr_dataset_s, time_s, time, fld_name_s, lon, lat, press, x_width=5, y_width=5, z_width=3, method='linear'):
+    vox_s = []
+    for ds in xr_dataset_s:
+        vox = get_voxel_s(ds, fld_name_s, lon, lat, press, x_width=x_width, y_width=y_width, z_width=z_width)
+        vox_s.append(vox)
+
+    vox = xr.concat(vox_s, 'time')
+    vox = vox.assign_coords(time=time_s)
+
+    intrp_vox = vox.interp(time=time, method=method)
+
+    return intrp_vox
+
+
+def get_voxel_s(xr_dataset, fld_name_s, lon, lat, press, x_width=5, y_width=5, z_width=3):
+    if lon360:
+        if lon < 0:
+            lon += 360
+
+    p_c = value_to_index(plevs, press)
+    x_c = value_to_index(lon_coords, lon)
+    y_c = value_to_index(lat_coords, lat)
+    y_h = int(y_width / 2)
+    x_h = int(x_width / 2)
+    p_h = int(z_width / 2)
+
+    y_start = y_c - y_h
+    x_start = x_c - x_h
+    z_start = p_c - p_h
+    if y_start < 0 or x_start < 0 or z_start < 0:
+        return None
+
+    y_stop = y_c + y_h + 1
+    x_stop = x_c + x_h + 1
+    z_stop = p_c + p_h + 1
+    if y_stop > NY-1 or x_stop > NX-1 or z_stop > NZ-1:
+        return None
+
+    sub_fld_s = []
+    for name in fld_name_s:
+        fld = xr_dataset[name]
+        sub_fld = fld[y_start:y_stop, x_start:x_stop, z_start:z_stop]
+        sub_fld_s.append(sub_fld)
+    sub_fld = xr.concat(sub_fld_s, 'channel')
+
+    sub_fld = sub_fld.assign_coords(channel=fld_name_s, fakeDim2=lon_coords[x_start:x_stop],
+                                    fakeDim1=lat_coords[y_start:y_stop], fakeDim0=plevs[z_start:z_stop])
+
+    return sub_fld
\ No newline at end of file