Repository: ioos/compliance-checker

Issue:
CF: check_two_dimensional doesn't use COARDS/CF-compatible method of determining lat/lon
Given the following dimensions and variables, we fail:

``` bash
$ ncdump -h Equirectangular sss_rc201401.v3.0cap.nc
netcdf sss_rc201401.v3.0cap {
dimensions:
    idlon = 360 ;
    idlat = 180 ;
variables:
    float idlon(idlon) ;
        idlon:long_name = "longitude" ;
        idlon:standard_name = "longitude" ;
        idlon:units = "degrees_east" ;
        idlon:comment = "midpoint of interval on uniform grid from -180 to 180 in 1 degree longitude increments" ;
        idlon:point_spacing = "1deg" ;
        idlon:axis = "X" ;
    float idlat(idlat) ;
        idlat:long_name = "latitude" ;
        idlat:standard_name = "latitude" ;
        idlat:units = "degrees_north" ;
        idlat:comment = "midpoint of interval on uniform grid from -90 to 90 in 1 degree latitude increments" ;
        idlat:point_spacing = "1deg" ;
        idlat:axis = "Y" ;
    float sss_cap(idlat, idlon) ;
        sss_cap:long_name = "Sea Surface Salinity" ;
        sss_cap:standard_name = "sea_surface_salinity" ;
        sss_cap:units = "1e-3" ;
        sss_cap:valid_min = 0.f ;
        sss_cap:valid_max = 45.f ;
        sss_cap:scale_factor = 1.f ;
        sss_cap:add_offset = 0.f ;
        sss_cap:grid_mapping = "Equirectangular" ;
        sss_cap:comment = "level-3 analysed sea surface salinity values obtained from the Combined Active Passive -CAP- algorithm with Rain Correction. Cell values are means for the temporal interval & 1degree spatial grid" ;
        sss_cap:_FillValue = -9999.f ;
        sss_cap:coordinates = "idlat idlon" ;
    char Equirectangular ;
        Equirectangular:grid_mapping_name = "latitude_longitude" ;
        Equirectangular:Standard_Parallel = 0.f ;
        Equirectangular:Longitude_of_Central_Meridian = 0.f ;
        Equirectangular:false_northing = 0.f ;
        Equirectangular:false_easting = 0.f ;
        Equirectangular:comment = "projection also referred to as Equidistant Cylindrical" ;
...
```

``` bash
var                                    :3:    13/14 :  
    sss_cap                            :3:     8/ 9 :  
        lat_lon_correct                :3:     0/ 1 :  
```

This seems to be because [cf.py determines if a coordinate is lat/lon by checking variable name](https://github.com/ioos/compliance-checker/blob/a758e165ba4a08a336a523a8da73dc68264897ee/compliance_checker/cf/cf.py#L1456) rather than the proper way of [knowing based on units](https://github.com/ioos/compliance-checker/blob/a758e165ba4a08a336a523a8da73dc68264897ee/compliance_checker/cf/cf.py#L85).



Discussion:
We will look into this.  You are right that the units define whether a variable represents lat/lon or not, however I believe we chose to check this way to enforce the use of the standard_name field.  I will address this soon.  


Resolving patch:
diff --git a/compliance_checker/cf/cf.py b/compliance_checker/cf/cf.py
--- a/compliance_checker/cf/cf.py
+++ b/compliance_checker/cf/cf.py
@@ -1414,63 +1414,68 @@ def check_two_dimensional(self, ds):
         ret_val = []
         for name,var in ds.dataset.variables.iteritems():
             g = NCGraph(ds.dataset, name, var)
-            if len(g.coords) != 2:
-                continue # Not a 2d horizontal grid
-            
-            #------------------------------------------------------------
-            # Check all the dims are coordinate variables
-            #------------------------------------------------------------
-            valid_dims = True
-            reasoning = []
-            for dim in g.dims.iterkeys():
-                if dim not in ds.dataset.variables:
-                    valid_2d = False
-                    reasoning.append("Variable %s's dimension %s is not a coordinate variable" % (name, dim))
-
-            result = Result(BaseCheck.HIGH,                             \
-                            valid_dims,                                 \
-                            ('var', name, '2d_hgrid_valid_dimensions'), \
-                            reasoning)
-            ret_val.append(result)
-            
-            #------------------------------------------------------------
-            # Check that the coordinates are correct
-            #------------------------------------------------------------
-            valid_2d = True
-            reasoning = []
-            for cname, coord in g.coords.iteritems():
-                if coord is None:
-                    valid_2d = False
-                    reasoning.append("Variable %s's coordinate, %s, is not a coordinate or auxiliary variable" %(name, cname))
-                    continue
-                for dim in coord.dims.iterkeys():
-                    if dim not in g.dims:
+            #Determine if 2-D coordinate variables (Lat and Lon are of shape (i,j)
+            for each in g.coords:
+                valid = g.coords[each].ndim == 2
+
+            if len(g.coords) == 2 and valid:
+                #------------------------------------------------------------
+                # Check all the dims are coordinate variables
+                #------------------------------------------------------------
+                valid_dims = True
+                reasoning = []
+                for dim in g.dims.iterkeys():
+                    if dim not in ds.dataset.variables:
                         valid_2d = False
-                        reasoning.append("Variable %s's coordinate, %s, does not share dimension %s with the variable" % (name, cname, dim))
-            result = Result(BaseCheck.MEDIUM,                   \
-                            valid_2d,                           \
-                            ('var', name, 'valid_coordinates'), \
-                            reasoning)
-            ret_val.append(result)
+                        reasoning.append("Variable %s's dimension %s is not a coordinate variable" % (name, dim))
+    
+                result = Result(BaseCheck.HIGH,                             \
+                                valid_dims,                                 \
+                                ('var', name, '2d_hgrid_valid_dimensions'), \
+                                reasoning)
+                ret_val.append(result)
+                
+                #------------------------------------------------------------
+                # Check that the coordinates are correct
+                #------------------------------------------------------------
+                valid_2d = True
+                reasoning = []
+                for cname, coord in g.coords.iteritems():
+                    if coord is None:
+                        valid_2d = False
+                        reasoning.append("Variable %s's coordinate, %s, is not a coordinate or auxiliary variable" %(name, cname))
+                        continue
+                    for dim in coord.dims.iterkeys():
+                        if dim not in g.dims:
+                            valid_2d = False
+                            reasoning.append("Variable %s's coordinate, %s, does not share dimension %s with the variable" % (name, cname, dim))
+                result = Result(BaseCheck.MEDIUM,                   \
+                                valid_2d,                           \
+                                ('var', name, 'valid_coordinates'), \
+                                reasoning)
+                ret_val.append(result)
+    
+                
+                #------------------------------------------------------------
+                # Can make lat/lon?
+                #------------------------------------------------------------
+    
+                lat_check = False
+                lon_check = False
 
-            
-            #------------------------------------------------------------
-            # Can make lat/lon?
-            #------------------------------------------------------------
+                for cname, coord in g.coords.iteritems():
 
-            lat_check = False
-            lon_check = False
+                    if coord.units in ['degrees_north', 'degree_north', 'degrees_N', 'degree_N', 'degreesN', 'degreeN']:
+                        lat_check = True
+                    elif coord.units in ['degrees_east', 'degree_east', 'degrees_E', 'degree_E', 'degreesE', 'degreeE']:
+                        lon_check = True
     
-            for cname, coord in g.coords.iteritems():
-                if cname.lower() in ('lat', 'latitude'):
-                    lat_check = True
-                elif cname.lower() in ('lon', 'longitude'):
-                    lon_check = True
-
-            result = Result(BaseCheck.HIGH,          \
-                            lat_check and lon_check, \
-                            ('var', name, 'lat_lon_correct'))
-            ret_val.append(result)
+                result = Result(BaseCheck.HIGH,          \
+                                lat_check and lon_check, \
+                                ('var', name, 'lat_lon_correct'))
+                ret_val.append(result)
+            else:
+                continue # Not a 2d horizontal grid
 
 
         return ret_val


Test patch: