Repository: ecmwf/earthkit-data (python)

Issue:
String coordinates in netcdf datasets
https://github.com/ecmwf/earthkit-data/blob/fe88303a00cc3dcbb2890f3dab1ebbb13a9d5c15/earthkit/data/readers/netcdf.py#L36

I'm trying to read a netcdf dataset with string coordinates. The netcdf field is a list of points, each point has an ID (for instance G0001) and a time series, so two dimensions: station and time.
I get the following error when trying to read with earthkit:
```
File /etc/ecmwf/nfs/dh1_home_a/macw/git/test/hat/venv/hat/lib/python3.10/site-packages/earthkit/data/readers/netcdf.py:37, in as_level(self, level)
     36 def as_level(self, level):
---> 37     n = float(level)
     38     if int(n) == n:
     39         return int(n)

ValueError: could not convert string to float: 'G0001'
```
Do we really need to force a casting to float for the not-recognised dimensions?

Pull request:
Allow string coordinates in netcdf
Fixes #260 

Patch:
diff --git a/earthkit/data/readers/netcdf.py b/earthkit/data/readers/netcdf.py
index 355c688..d847d68 100644
--- a/earthkit/data/readers/netcdf.py
+++ b/earthkit/data/readers/netcdf.py
@@ -8,6 +8,7 @@
 #
 
 import datetime
+import logging
 from contextlib import closing
 from itertools import product
 
@@ -23,6 +24,8 @@ from earthkit.data.utils.projections import Projection
 
 from . import Reader
 
+LOG = logging.getLogger(__name__)
+
 GEOGRAPHIC_COORDS = {
     "x": ["x", "projection_x_coordinate", "lon", "longitude"],
     "y": ["y", "projection_y_coordinate", "lat", "latitude"],
@@ -34,6 +37,8 @@ def as_datetime(self, time):
 
 
 def as_level(self, level):
+    if isinstance(level, str):
+        return level
     n = float(level)
     if int(n) == n:
         return int(n)
@@ -187,13 +192,16 @@ def get_fields_from_ds(
             standard_name = getattr(c, "standard_name", "")
             axis = getattr(c, "axis", "")
             long_name = getattr(c, "long_name", "")
+            coord_name = getattr(c, "name", "")
 
+            # LOG.debug(f"{standard_name=} {long_name=} {axis=} {coord_name}")
             use = False
 
             if (
                 standard_name.lower() in GEOGRAPHIC_COORDS["x"]
                 or (long_name == "longitude")
                 or (axis == "X")
+                or coord_name.lower() in GEOGRAPHIC_COORDS["x"]
             ):
                 has_lon = True
                 use = True
@@ -202,12 +210,11 @@ def get_fields_from_ds(
                 standard_name.lower() in GEOGRAPHIC_COORDS["y"]
                 or (long_name == "latitude")
                 or (axis == "Y")
+                or coord_name.lower() in GEOGRAPHIC_COORDS["y"]
             ):
                 has_lat = True
                 use = True
 
-            # print(f"  standard_name={standard_name}")
-
             # Of course, not every one sets the standard_name
             if (
                 standard_name in ["time", "forecast_reference_time"]
@@ -222,13 +229,16 @@ def get_fields_from_ds(
                     break
 
             # TODO: Support other level types
-            if standard_name in [
-                "air_pressure",
-                "model_level_number",
-                "altitude",
-            ] or long_name in [
-                "pressure_level"
-            ]:  # or axis == 'Z':
+            if (
+                standard_name
+                in [
+                    "air_pressure",
+                    "model_level_number",
+                    "altitude",
+                ]
+                or long_name in ["pressure_level"]
+                or coord_name in ["level"]
+            ):  # or axis == 'Z':
                 coordinates.append(LevelCoordinate(c, coord in info))
                 use = True
 


Test patch:
diff --git a/tests/netcdf/test_netcdf_fieldlist.py b/tests/netcdf/test_netcdf_fieldlist.py
new file mode 100644
index 0000000..fe15119
--- /dev/null
+++ b/tests/netcdf/test_netcdf_fieldlist.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python3
+
+# (C) Copyright 2020 ECMWF.
+#
+# This software is licensed under the terms of the Apache Licence Version 2.0
+# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
+# In applying this licence, ECMWF does not waive the privileges and immunities
+# granted to it by virtue of its status as an intergovernmental organisation
+# nor does it submit to any jurisdiction.
+#
+
+import numpy as np
+
+import earthkit.data
+
+
+def test_netcdf_fieldlist_string_coord():
+    import xarray as xr
+
+    a = xr.DataArray(
+        [
+            [[11, 12, 13], [21, 22, 23], [31, 32, 33]],
+            [[14, 15, 16], [24, 25, 26], [34, 35, 36]],
+        ],
+        coords={"level": ["500", "700"], "x": [1, 2, 3], "y": [4, 5, 6]},
+        name="dummyvar",
+    )
+
+    ds = earthkit.data.from_object(a)
+
+    assert ds
+    assert len(ds) == 2
+    assert ds.metadata("level") == ["500", "700"]
+
+    x_ref = np.array([[1, 2, 3], [1, 2, 3], [1, 2, 3]])
+    y_ref = np.array([[4, 4, 4], [5, 5, 5], [6, 6, 6]])
+    assert np.allclose(ds[0].to_points()["x"], x_ref)
+    assert np.allclose(ds[0].to_points()["y"], y_ref)
+
+
+if __name__ == "__main__":
+    from earthkit.data.testing import main
+
+    main()