Repository: ioos/compliance-checker
Pull request #1089 (2024-06-04 21:03:31)

Implement checking of domain variables against cell_measures
Implementation of 5.8 Domain Variables when checking against cell measures, conformance required 4/4 in CF 1.9 standard.


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 64ae8fb..8faab34 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -31,7 +31,7 @@ repos:
 
 
 - repo: https://github.com/astral-sh/ruff-pre-commit
-  rev: v0.4.7
+  rev: v0.4.5
   hooks:
     - id: ruff
 
@@ -41,11 +41,11 @@ repos:
     - id: pyproject-fmt
 
 - repo: https://github.com/codespell-project/codespell
-  rev: v2.3.0
+  rev: v2.2.6
   hooks:
     - id: codespell
       args:
-        - --ignore-words-list=degreeE,degreee,varn,poit,uint,sur,herat,claus,tung,messsages,assertin
+        - --ignore-words-list=degreeE,degreee,varn,poit,uint,sur,herat,claus,tung,messsages
       exclude: >
           (?x)^(
               .*\.xml|
diff --git a/README.md b/README.md
index cdbd4ed..a01b1ff 100644
--- a/README.md
+++ b/README.md
@@ -204,10 +204,9 @@ optional arguments:
 
 ### Check a local file against CF 1.6
 ```
-compliance-checker --test=cf:1.6 compliance_checker/tests/data/examples/hycom_global.nc
-```
+$ compliance-checker --test=cf:1.6 compliance_checker/tests/data/examples/hycom_global.nc
+
 
-```
 --------------------------------------------------------------------------------
                          IOOS Compliance Checker Report
                                   cf:1.6 check
@@ -248,7 +247,7 @@ contains valid packing:                are not of type float or double.
 The remote dataset url is taken from the Data URL section of an OPeNDAP endpoint.
 
 ```shell
-compliance-checker --test=acdd:1.3 "http://sos.maracoos.org/stable/dodsC/hrecos/stationHRMARPH-agg.ncml"
+$ compliance-checker --test=acdd:1.3 "http://sos.maracoos.org/stable/dodsC/hrecos/stationHRMARPH-agg.ncml"
 ```
 
 ### Checking against remote ERDDAP Datasets
@@ -256,7 +255,7 @@ compliance-checker --test=acdd:1.3 "http://sos.maracoos.org/stable/dodsC/hrecos/
 ERDDAP datasets are becoming a popular way to access data. Supply an ERDDAP `TableDAP` or `GridDAP` URL to the checker:
 
 ```shell
-compliance-checker --test ioos:1.2 "https://pae-paha.pacioos.hawaii.edu/erddap/griddap/pibhmc_bathy_60m_guam"
+$ compliance-checker --test ioos:1.2 "https://pae-paha.pacioos.hawaii.edu/erddap/griddap/pibhmc_bathy_60m_guam"
 ```
 
 Ensure to supply the URL *without* the format extension at the end (no `.nc`, `.ncCF`, etc.).
@@ -277,37 +276,37 @@ Some examples of ERDDAP datasets:
 ### Write results to text file
 
 ```shell
-compliance-checker --test=acdd:1.3 --format=text --output=/tmp/report.txt compliance_checker/tests/data/examples/hycom_global.nc
+$ compliance-checker --test=acdd:1.3 --format=text --output=/tmp/report.txt compliance_checker/tests/data/examples/hycom_global.nc
 ```
 
 ### Write results to JSON file
 
 ```shell
-compliance-checker --test=acdd:1.3 --format=json --output=/tmp/report.json compliance_checker/tests/data/examples/hycom_global.nc
+$ compliance-checker --test=acdd:1.3 --format=json --output=/tmp/report.json compliance_checker/tests/data/examples/hycom_global.nc
 ```
 
 ### Write results to HTML file
 
 ```shell
-compliance-checker --test=acdd:1.3 --format=html --output=/tmp/report.html compliance_checker/tests/data/examples/hycom_global.nc
+$ compliance-checker --test=acdd:1.3 --format=html --output=/tmp/report.html compliance_checker/tests/data/examples/hycom_global.nc
 ```
 
 ### Output text from multiple input files to one output file
 
 ```
-compliance-checker --test=cf:1.6 --format text --output=/tmp/combined_output.txt compliance_checker/tests/data/examples/hycom_global.nc compliance_checker/tests/data/examples/ww3.nc
+$ compliance-checker --test=cf:1.6 --format text --output=/tmp/combined_output.txt compliance_checker/tests/data/examples/hycom_global.nc compliance_checker/tests/data/examples/ww3.nc
 ```
 
 ### Output html and text files from multiple input files (part 1)
 In this case you'll get 2 files ```/tmp/combined_output.txt``` and ```/tmp/combined_output.html``` that contain cf check results for both input files because you only specified 1 output filename.
 ```
-compliance-checker --test=cf:1.6 --format text --format html --output=/tmp/combined_output.txt compliance_checker/tests/data/examples/hycom_global.nc compliance_checker/tests/data/examples/ww3.nc
+$ compliance-checker --test=cf:1.6 --format text --format html --output=/tmp/combined_output.txt compliance_checker/tests/data/examples/hycom_global.nc compliance_checker/tests/data/examples/ww3.nc
 ```
 
 ### Output html and text files from multiple input files (part 2)
 In this case you'll get 4 files ```/tmp/hycom.txt```, ```/tmp/hycom.html```, ```/tmp/ww3.txt```, and ```/tmp/ww3.html``` that contain cf check results because you specified as many output filenames as input filenames.
 ```
-compliance-checker --test=cf:1.6 --format text --format html --output=/tmp/hycom.txt --output=/tmp/ww3.txt compliance_checker/tests/data/examples/hycom_global.nc compliance_checker/tests/data/examples/ww3.nc
+$ compliance-checker --test=cf:1.6 --format text --format html --output=/tmp/hycom.txt --output=/tmp/ww3.txt compliance_checker/tests/data/examples/hycom_global.nc compliance_checker/tests/data/examples/ww3.nc
 ```
 
 ### Download a particular CF standard names table for use in the test
@@ -318,8 +317,7 @@ During the CF test, if a file has a particular version of the cf standard name t
 try to download the specified version. If it fails, it will fall back to packaged version.
 
 ```
-compliance-checker -d 35
-```
+$ compliance-checker -d 35
 
 Downloading cf-standard-names table version 35 from: http://cfconventions.org/Data/cf-standard-names/35/src/cf-standard-name-table.xml
 ```
diff --git a/compliance_checker/cf/cf_1_9.py b/compliance_checker/cf/cf_1_9.py
index 5e085bd..73211e4 100644
--- a/compliance_checker/cf/cf_1_9.py
+++ b/compliance_checker/cf/cf_1_9.py
@@ -113,8 +113,9 @@ class CF1_9Check(CF1_8Check):
             for var in ds.get_variables_by_attributes(
                 coordinates=lambda c: c is not None,
             )
-            # IMPLICIT
-            if not var.dimensions
+            # IMPLICIT CONFORMANCE REQUIRED 1/4
+            # all variables have dimensions attribute, but should be scalar
+            if var.dimensions == ()
         ):
             domain_valid = TestCtx(BaseCheck.MEDIUM, self.section_titles["5.8"])
             domain_valid.out_of += 1
@@ -149,7 +150,7 @@ class CF1_9Check(CF1_8Check):
                     continue
                 appendix_a_not_recommended_attrs = []
                 for attr_name in domain_var.ncattrs():
-                    if "D" not in self.appendix_a[attr_name]["attr_loc"]:
+                    if attr_name in self.appendix_a and "D" not in self.appendix_a[attr_name]["attr_loc"]:
                         appendix_a_not_recommended_attrs.append(attr_name)
 
                 if appendix_a_not_recommended_attrs:
@@ -162,6 +163,22 @@ class CF1_9Check(CF1_8Check):
                 # no errors occurred
                 domain_valid.score += 1
 
+
+            # IMPLEMENTATION CONFORMANCE 5.8 REQUIRED 4/4
+            if hasattr(domain_var, "cell_measures"):
+                cell_measures_var_names = regex.findall(r"\b(?:area|volume):\s+(\w+)", domain_var.cell_measures)
+                # check exist
+                for var_name in cell_measures_var_names:
+                    try:
+                        cell_measures_variable = ds.variables[var_name]
+                    except ValueError:
+                        # TODO: what to do here?
+                        continue
+                    domain_coord_var_names = {var_like.name for var_like in domain_coord_vars}
+                    domain_valid.assert_true(set(cell_measures_variable.dimensions).issubset(domain_coord_var_names),
+                                             "Variables named in the cell_measures attributes must have a dimensions attribute with "
+                                             "values that are a subset of the referring domain variable's dimension attribute")
+
             results.append(domain_valid.to_result())
 
         return results


Test patch:
diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py
index 5e13bb7..dce2bfa 100644
--- a/compliance_checker/tests/test_cf.py
+++ b/compliance_checker/tests/test_cf.py
@@ -3241,7 +3241,28 @@ class TestCF1_9(BaseTestCase):
             "coordinates attribute from domain variable domain: "
             "xyxz, abc",
         )
-
+        # TEST CONFORMANCE 5.8 REQUIRED 4/4
+        # check reference cell measures
+        domain_var.cell_measures = "volume: cube"
+        domain_var.coordinates = "lon lat depth"
+        # reset to good domain var coordinates
+        dataset.createDimension("lon", 20)
+        dataset.createDimension("lat", 20)
+        dataset.createDimension("depth", 20)
+        domain_var.setncattr("dimensions", "lon lat depth")
+        cube = dataset.createVariable("cube", "f8", ("lon", "lat", "depth"))
+        # OK, coordinates in cell_measures are subset of coordinates of
+        # referring domain variable's coordinates attribute
+        results = self.cf.check_domain_variables(dataset)
+        assert not results[0].msgs
+        # failing example, coordinates for cell_measures variable are no longer subset
+        domain_var.cell_measures = "volume: cube_bad"
+        dataset.createVariable("cube_bad", "f8", ("lon", "lat", "depth", "time"))
+        results = self.cf.check_domain_variables(dataset)
+        self.assertTrue("Variables named in the cell_measures attributes must "
+                        "have a dimensions attribute with values that are a "
+                        "subset of the referring domain variable's dimension "
+                        "attribute" in results[0].msgs)
         del dataset
         dataset = MockTimeSeries()
         # domain should be dimensionless -- currently not an error in