Repository: ecmwf/earthkit-data

Issue:
`earthkit-data` fails to provide the resolution for some GRIB files
### Is your feature request related to a problem? Please describe.

`earthkit-data` fails to provide the resolution for some GRIB files with lambert_lam projection.
As detailed here with example of failing grib : https://github.com/ecmwf/anemoi-datasets/issues/157#issuecomment-2539011314

I believe that the issue is that the grid is not regular, and this handling is not implemented yet in earthkit-data.


````
>> ds = ed.from_source('file', 'SUBSET_ARA00_0001_00.grb')
>>> ds[0].resolution
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/.../python3.10/site-packages/earthkit/data/core/fieldlist.py", line 378, in resolution
    return self._metadata.geography.resolution()
  File "/.../python3.10/site-packages/earthkit/data/readers/grib/metadata.py", line 173, in resolution
    assert x == y, (x, y)
AssertionError: (0.028, 0.018)
```

### Describe the solution you'd like

_No response_

### Describe alternatives you've considered

_No response_

### Additional context

_No response_

### Organisation

ecmwf

Hints from issue comments:
@sandorkertesz: @floriankrb, I think the issue is that the `resolution` has to be a single number (as far as I know) but for this grid `dx != dy` (dx=0.028, dy=0.018). @sandorkertesz: Now, None is returned in this case. 0.12.3 has been released with this fix.

Solution patch:
diff --git a/docs/release_notes/version_0.12_updates.rst b/docs/release_notes/version_0.12_updates.rst
index b7227ab..c0c42e3 100644
--- a/docs/release_notes/version_0.12_updates.rst
+++ b/docs/release_notes/version_0.12_updates.rst
@@ -2,6 +2,20 @@ Version 0.12 Updates
 /////////////////////////
 
 
+Version 0.12.3
+===============
+
+Changes
+++++++++
+
+- ``Field.resolution()`` now returns None when the resolution cannot be determined. Previously, it failed with an assertion (:pr:`616`)
+
+Fixes
+++++++++
+
+- Fixed issue when there was a crash during checking missing MARS credentials when calling ``from_source("mars", ...)``. It only happened when ``ecmwfapi`` was used for MARS retrievals. (:pr:`615`)
+
+
 Version 0.12.2
 ===============
 
diff --git a/src/earthkit/data/readers/geotiff.py b/src/earthkit/data/readers/geotiff.py
index 11936dc..05360dc 100644
--- a/src/earthkit/data/readers/geotiff.py
+++ b/src/earthkit/data/readers/geotiff.py
@@ -82,14 +82,14 @@ class GeoTIFFGeography(Geography):
         # Get width and height of pixels in units of the CRS
         x, y = self._ds.rio.resolution()
         crs = self._ds.rio.crs
-        units, factor = crs.units_factor
+        units, _ = crs.units_factor
         # Geographic coordinate systems use latitude and longitude
         if crs.is_geographic and units == "degree":
             x = abs(round(x * 1_000_000) / 1_000_000)
             y = abs(round(y * 1_000_000) / 1_000_000)
-            assert x == y, (x, y)
-            return x
-        raise NotImplementedError(f"resolution for {crs} ({units}, {factor})")
+            if x == y:
+                return x
+        # raise NotImplementedError(f"resolution for {crs} ({units}, {factor})")
 
     def mars_grid(self):
         raise NotImplementedError
diff --git a/src/earthkit/data/readers/grib/metadata.py b/src/earthkit/data/readers/grib/metadata.py
index a5d5ad1..9de63e1 100644
--- a/src/earthkit/data/readers/grib/metadata.py
+++ b/src/earthkit/data/readers/grib/metadata.py
@@ -170,16 +170,17 @@ class GribFieldGeography(Geography):
             y = self.metadata.get("DyInDegrees")
             x = round(x * 1_000_000) / 1_000_000
             y = round(y * 1_000_000) / 1_000_000
-            assert x == y, (x, y)
-            return x
+            return x if x == y else None
 
         if grid_type in ["lambert", "lambert_azimuthal_equal_area"]:
             x = self.metadata.get("DxInMetres")
             y = self.metadata.get("DyInMetres")
-            assert x == y, (x, y)
-            return str(x / 1000).replace(".", "p") + "km"
+            if x == y:
+                return str(x / 1000).replace(".", "p") + "km"
+            else:
+                return None
 
-        raise ValueError(f"Unknown gridType={grid_type}")
+        # raise ValueError(f"Unknown gridType={grid_type}")
 
     def mars_grid(self):
         if len(self.shape()) == 2:
diff --git a/src/earthkit/data/utils/metadata/dict.py b/src/earthkit/data/utils/metadata/dict.py
index 9a3e0e0..fe91e70 100644
--- a/src/earthkit/data/utils/metadata/dict.py
+++ b/src/earthkit/data/utils/metadata/dict.py
@@ -175,7 +175,8 @@ class UserGeography(Geography):
         return None
 
     def resolution(self):
-        raise NotImplementedError("resolution is not implemented for this geography")
+        return None
+        # raise NotImplementedError("resolution is not implemented for this geography")
 
     def mars_area(self):
         return [self.north(), self.west(), self.south(), self.east()]
@@ -255,8 +256,8 @@ class RegularDistinctLLGeography(DistinctLLGeography):
     def resolution(self):
         x = self.dx()
         y = self.dy()
-        assert x == y, (x, y)
-        return x
+        if x == y:
+            return x
 
     def mars_grid(self):
         return [self.dx(), self.dy()]


Test patch:
diff --git a/tests/grib/test_grib_geography.py b/tests/grib/test_grib_geography.py
index c20fa42..e0cbc3e 100644
--- a/tests/grib/test_grib_geography.py
+++ b/tests/grib/test_grib_geography.py
@@ -220,6 +220,8 @@ def test_grib_projection_mercator(fl_type):
         (earthkit_test_data_file("rgg_small_subarea_cellarea_ref.grib"), "O1280"),
         (earthkit_test_data_file("rotated_N32_subarea.grib"), "N32"),
         (earthkit_test_data_file("rotated_wind_20x20.grib"), 20),
+        (earthkit_test_data_file("mercator.grib"), None),
+        (earthkit_test_data_file("ll_10_20.grib"), None),
     ],
 )
 def test_grib_resolution(path, expected_value):
@@ -227,6 +229,8 @@ def test_grib_resolution(path, expected_value):
 
     if isinstance(expected_value, str):
         assert ds[0].resolution == expected_value
+    elif expected_value is None:
+        assert ds[0].resolution is None
     else:
         assert np.isclose(ds[0].resolution, expected_value)
 
diff --git a/tests/list_of_dicts/test_lod_core.py b/tests/list_of_dicts/test_lod_core.py
index d37df6a..3ca9e0d 100644
--- a/tests/list_of_dicts/test_lod_core.py
+++ b/tests/list_of_dicts/test_lod_core.py
@@ -87,8 +87,7 @@ def test_lod_ll(lod_distinct_ll, mode):
     assert np.allclose(gr[0], lat_ref.flatten())
     assert np.allclose(gr[1], lon_ref.flatten())
 
-    with pytest.raises(AssertionError):
-        ds[0].resolution
+    assert ds[0].resolution is None
 
     assert ds[0].datetime() == {
         "base_time": None,
diff --git a/tests/list_of_dicts/test_lod_geography.py b/tests/list_of_dicts/test_lod_geography.py
index d62a54b..87e1980 100644
--- a/tests/list_of_dicts/test_lod_geography.py
+++ b/tests/list_of_dicts/test_lod_geography.py
@@ -50,8 +50,7 @@ def test_lod_geo_distinct_ll(lod_distinct_ll, mode):
     assert np.allclose(gr[0], lat_ref.flatten())
     assert np.allclose(gr[1], lon_ref.flatten())
 
-    with pytest.raises(AssertionError):
-        ds[0].resolution
+    assert ds[0].resolution is None
 
     ll = ds.to_latlon()
     lat = ll["lat"]
@@ -88,8 +87,7 @@ def test_lod_geo_ll_flat(lod_ll_flat, mode):
     assert np.allclose(gr[0], lat_ref.flatten())
     assert np.allclose(gr[1], lon_ref.flatten())
 
-    with pytest.raises(NotImplementedError):
-        ds[0].resolution
+    assert ds[0].resolution is None
 
     ll = ds.to_latlon()
     lat = ll["lat"]
@@ -128,8 +126,7 @@ def test_lod_geo_ll_2D(request, data, mode):
     assert np.allclose(gr[0], lat_ref.flatten())
     assert np.allclose(gr[1], lon_ref.flatten())
 
-    with pytest.raises(NotImplementedError):
-        ds[0].resolution
+    assert ds[0].resolution is None
 
     ll = ds.to_latlon()
     lat = ll["lat"]