Repository: ioos/compliance-checker
Pull request #779 (2020-03-24 20:14:50)

Adds support for forecast reference metadata in CF checks.
This commit updates cfutil's feature detection to ignore variables used
to declare forecast metadata. Specifically, variables with standard
names `forecast_period` and `forecast_reference_time` are used in
conjunction with `time` to indicate to dataset consumers the appropriate
model initialization time, forecast hour, and valid time. This is
useful for datasets that aggregate multiple forecast runs, such as FMRC.


Patch:
diff --git a/compliance_checker/cfutil.py b/compliance_checker/cfutil.py
index a9dbe93..bfb6adf 100644
--- a/compliance_checker/cfutil.py
+++ b/compliance_checker/cfutil.py
@@ -194,6 +194,9 @@ def is_geophysical(ds, variable):
     if variable in get_auxiliary_coordinate_variables(ds):
         return False
 
+    if variable in get_forecast_metadata_variables(ds):
+        return False
+
     # Is it dimensionless and unitless?
     if len(ncvar.shape) == 0 and unitless:
         return False
@@ -296,6 +299,26 @@ def get_auxiliary_coordinate_variables(ds):
     return ret_val
 
 
+def get_forecast_metadata_variables(ds):
+    '''
+    Returns a list of variables that represent forecast reference time
+    metadata.
+
+    :param netCDF4.Dataset ds: An open netCDF4 Dataset.
+    :rtype: list
+    '''
+    forecast_metadata_standard_names = {
+        'forecast_period',
+        'forecast_reference_time',
+    }
+    forecast_metadata_variables = []
+    for varname in ds.variables:
+        standard_name = getattr(ds.variables[varname], 'standard_name', None)
+        if standard_name in forecast_metadata_standard_names:
+            forecast_metadata_variables.append(varname)
+    return forecast_metadata_variables
+
+
 def get_cell_boundary_map(ds):
     '''
     Returns a dictionary mapping a variable to its boundary variable. The


Test patch:
diff --git a/compliance_checker/tests/data/forecast_reference.cdl b/compliance_checker/tests/data/forecast_reference.cdl
new file mode 100644
index 0000000..b12eb57
--- /dev/null
+++ b/compliance_checker/tests/data/forecast_reference.cdl
@@ -0,0 +1,32 @@
+netcdf forecast_reference.cdl {
+dimensions:
+    time = 2 ;
+    lat = 4 ;
+    lon = 4 ;
+variables:
+    float time(time) ;
+        time:units = "seconds since 1970-01-01" ;
+        time:standard_name = "time" ;
+        time:calendar = "gregorian" ;
+        time:axis = "T" ;
+    float lat(lat) ;
+        lat:units = "degrees_north" ;
+        lat:standard_name = "latitude" ;
+        lat:axis = "Y" ;
+    float lon(lon) ;
+        lon:units = "degrees_east" ;
+        lon:standard_name = "longitude" ;
+        lon:axis = "X" ;
+    float air_temp(time, lon, lat) ;
+        air_temp:units = "deg_C" ;
+        air_temp:standard_name = "air_temperature";
+    float forecast_reference_time(time) ;
+        forecast_reference_time:units = "seconds since 1970-01-01" ;
+        forecast_reference_time:standard_name = "forecast_reference_time" ;
+    float forecast_hour(time) ;
+        forecast_hour:units = "hours" ;
+        forecast_hour:standard_name = "forecast_period" ;
+
+    :featureType = "grid" ;
+
+}
diff --git a/compliance_checker/tests/resources.py b/compliance_checker/tests/resources.py
index d125ff6..d305ecb 100644
--- a/compliance_checker/tests/resources.py
+++ b/compliance_checker/tests/resources.py
@@ -48,6 +48,7 @@ STATIC_FILES = {
     'dimension_order'                      : get_filename('tests/data/dimension_order.cdl'),
     'example-grid'                         : get_filename('tests/data/example-grid.cdl'),
     'featureType'                          : get_filename('tests/data/example-grid.cdl'),
+    'forecast_reference'                   : get_filename('tests/data/forecast_reference.cdl'),
     'fvcom'                                : get_filename('tests/data/examples/fvcom.cdl'),
     'ghrsst'                               : get_filename('tests/data/20160919092000-ABOM-L3S_GHRSST-SSTfnd-AVHRR_D-1d_dn_truncate.cdl'),
     'glcfs'                                : get_filename('tests/data/examples/glcfs.cdl'),
diff --git a/compliance_checker/tests/test_feature_detection.py b/compliance_checker/tests/test_feature_detection.py
index cbba1b9..3426e30 100644
--- a/compliance_checker/tests/test_feature_detection.py
+++ b/compliance_checker/tests/test_feature_detection.py
@@ -226,6 +226,20 @@ class TestFeatureDetection(TestCase):
             aux_coord_vards = util.get_auxiliary_coordinate_variables(nc)
             assert set(['lat', 'lon']) == set(aux_coord_vards)
 
+    def test_forecast_reference_metadata(self):
+        '''
+        Tests variables used for forecast reference metadata to ensure they are
+        not misclassified as geophysical variables.
+        '''
+        with Dataset(resources.STATIC_FILES['forecast_reference']) as nc:
+            self.assertFalse(util.is_geophysical(nc, 'forecast_reference_time'))
+            self.assertFalse(util.is_geophysical(nc, 'forecast_hour'))
+            self.assertTrue(util.is_geophysical(nc, 'air_temp'))
+            self.assertFalse(util.is_geophysical(nc, 'time'))
+
+            assert len(util.get_coordinate_variables(nc)) == 3
+            assert len(util.get_geophysical_variables(nc)) == 1
+
     def test_rotated_pole_grid(self):
         with Dataset(resources.STATIC_FILES['rotated_pole_grid']) as nc:
             latitudes = util.get_latitude_variables(nc)