Skip to content

Commit 50189d8

Browse files
Merge pull request #908 from ecmwf/feature/xr-engine-new-field
Feature/xr engine new field
2 parents 402cecf + 509bbf0 commit 50189d8

22 files changed

+412
-198
lines changed

src/earthkit/data/core/field.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"time.base_datetime",
3434
"time.step",
3535
"vertical.level",
36-
"vertical.type",
36+
"vertical.level_type",
3737
"ensemble.member",
3838
"geography.grid_type",
3939
]

src/earthkit/data/indexing/tensor.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -502,8 +502,9 @@ def _subset(self, indexes):
502502

503503
def make_valid_datetime(self, dims_map, dtype="datetime64[ns]"):
504504
# TODO: make it more general
505+
# PW: TODO: make it more general - it could allow to use it when allow_holes=True
505506

506-
for k in ["valid_datetime", "valid_time"]:
507+
for k in ["valid_time", "time.valid_datetime", "metadata.valid_time", "metadata.valid_datetime"]:
507508
if k in self.user_coords:
508509
import datetime
509510

@@ -512,10 +513,24 @@ def make_valid_datetime(self, dims_map, dtype="datetime64[ns]"):
512513
# in the tensor the dims.coords are GRIB keys
513514
# dims_map is a mapping from dim names to GRIB keys
514515
DIM_ROLES = {
515-
"forecast_reference_time": ("forecast_reference_time", "base_datetime"),
516-
"step": ("step_timedelta", "step", "ensStep", "stepRange"),
517-
"date": ("date", "dataDate"),
518-
"time": ("time", "dataTime"),
516+
"forecast_reference_time": (
517+
"forecast_reference_time",
518+
"time.forecast_reference_time",
519+
"time.base_datetime",
520+
"metadata.base_datetime",
521+
"metadata.indexing_datetime",
522+
"metadata.indexing_time",
523+
),
524+
"step": (
525+
"step",
526+
"time.step",
527+
"metadata.step_timedelta",
528+
"metadata.step",
529+
"metadata.endStep",
530+
"metadata.stepRange",
531+
),
532+
"date": ("date", "metadata.dataDate"),
533+
"time": ("time", "metadata.dataTime"),
519534
}
520535

521536
# map dim roles to keys available in the tensor
@@ -561,7 +576,7 @@ def make_valid_datetime(self, dims_map, dtype="datetime64[ns]"):
561576
}
562577

563578
vals = np.array(
564-
[x for x in self.source.sel(**other_coords).get("valid_datetime")],
579+
[x for x in self.source.sel(**other_coords).get("time.valid_datetime")],
565580
dtype=dtype,
566581
)
567582

@@ -573,7 +588,7 @@ def make_valid_datetime(self, dims_map, dtype="datetime64[ns]"):
573588
import numpy as np
574589

575590
vals = np.array(
576-
[x for x in self.source.get("valid_datetime")],
591+
[x for x in self.source.get("time.valid_datetime")],
577592
dtype=dtype,
578593
)
579594

src/earthkit/data/xr_engine/attrs.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,23 @@ class NamespaceAttr(Attr):
107107

108108
def __init__(self, name, ns=None):
109109
super().__init__(name, remove_prefix=ns is None)
110-
self.ns = ns if ns is not None else name
110+
if ns is None:
111+
ns = name
112+
113+
if ns.startswith("metadata."):
114+
self.ns = "metadata"
115+
self.filter = ns.removeprefix("metadata.")
116+
else:
117+
self.ns = ns
118+
self.filter = None
111119

112120
def get(self, field):
113-
# PW: TODO
114-
# print(f"{self=}, {field=}", flush=True)
115-
raise NotImplementedError
116-
return {self.name: field.as_namespace(self.ns)}
121+
namespace_attr = field._dump_component(self.ns, filter=self.filter)
122+
if self.filter is not None:
123+
# namespace_attr is of the form `{self.filter: actual_namespace_attr_dict}`
124+
# so need to extract the `actual_namespace_attr_dict`
125+
namespace_attr = namespace_attr[self.filter]
126+
return {self.name: namespace_attr}
117127

118128
def __repr__(self) -> str:
119129
return f"NamespaceAttr({self.name}, ns={self.ns})"

src/earthkit/data/xr_engine/builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ def parse(self, ds, profile=None, full=False):
645645
return ds_xr, profile
646646

647647
def grid(self, ds):
648-
grids = ds.index("md5GridSection")
648+
grids = ds.index("metadata.md5GridSection")
649649

650650
if not grids:
651651
grid = "_custom_" + str(id(ds))

src/earthkit/data/xr_engine/check.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ def neighbour_field(self, field_num, index):
7676
return f_other, index_other
7777

7878
def namespace_diff(self, f, f_other):
79-
meta = f.namespace()
80-
meta_other = f_other.namespace()
79+
meta = f._dump_component()
80+
meta_other = f_other._dump_component()
8181
return DictDiff.diff(meta, meta_other)
8282

8383
def meta_diff(self, f, f_other, coords_keys):
@@ -147,7 +147,7 @@ def check(self, details=False):
147147
text_ns += f"{k}:\n {v}\n"
148148

149149
md = {"coordinates": self.meta(f, coord_keys)}
150-
md.update(f.namespace())
150+
md.update(f._dump_component())
151151

152152
text_meta = f"\nField[{index}] metadata:\n"
153153
for k, v in md.items():

src/earthkit/data/xr_engine/coord.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,21 +177,30 @@ def attrs(self, profile):
177177

178178
class LevelCoord(Coord):
179179
def __init__(self, name, vals, dims=None, ds=None, **kwargs):
180-
self._level_type = None
180+
self._level_type = {}
181181
self._cf = None
182182
if ds is not None:
183183
self._cf = ds[0].vertical.cf()
184-
self._level_type = ds[0].vertical.level_type()
184+
for k in ["vertical.level_type", "metadata.typeOfLevel", "metadata.levtype"]:
185+
v = ds[0]._get_fast(k)
186+
if v is not None:
187+
self._level_type[k] = v
185188

186189
super().__init__(name, vals, dims, **kwargs)
187190

188191
def attrs(self, profile):
189192
attrs = profile.attrs
190193
conf = attrs.coord_attrs.get(self.name, {})
191-
res = {}
194+
_attrs = {}
192195
if conf:
193-
res = conf.get(self._level_type, {})
194-
if not res:
195-
res = self._cf
196-
return res
196+
keys = conf["keys"]
197+
for key in keys:
198+
level_type = self._level_type.get(key)
199+
if level_type is None:
200+
continue
201+
_attrs = conf.get(level_type)
202+
if _attrs is not None:
203+
return _attrs
204+
_attrs = self._cf
205+
return _attrs
197206
# raise ValueError(f"Cannot determine level type for coordinate {name}")

src/earthkit/data/xr_engine/defaults.yaml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ coord_attrs:
8383
# "calendar": "proleptic_gregorian",
8484
standard_name: time
8585
long_name: valid_time
86-
level:
86+
metadata.level: &_level
87+
keys:
88+
- level_type
89+
- vertical.level_type
90+
- metadata.typeOfLevel
8791
depthBelowLand:
8892
units: m
8993
positive: down
@@ -151,6 +155,7 @@ coord_attrs:
151155
positive: up
152156
long_name: air_potential_temperature
153157
standard_name: air_potential_temperature
158+
_metadata.levelist: &_levelist
154159
ml:
155160
units: 1
156161
positive: down
@@ -180,3 +185,19 @@ coord_attrs:
180185
units: 1
181186
long_name: snow layer
182187
standard_name: unknown
188+
metadata.levelist:
189+
<<: *_levelist
190+
keys:
191+
- level_type
192+
- vertical.level_type
193+
- metadata.levtype
194+
levelist:
195+
<<: *_levelist
196+
keys:
197+
- level_type
198+
- vertical.level_type
199+
- metadata.levtype
200+
vertical.level:
201+
<<: [*_level, *_levelist]
202+
level:
203+
<<: [*_level, *_levelist]

src/earthkit/data/xr_engine/diff.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def _flatten_dict(d, parent_key="", sep="."):
2727
flat = {}
2828
for key, value in d.items():
2929
new_key = f"{parent_key}{sep}{key}" if parent_key else key
30-
if isinstance(value, dict) and len(value) > 0:
30+
if isinstance(value, dict):
3131
flat.update(DictDiff._flatten_dict(value, parent_key=new_key, sep=sep))
3232
else:
3333
flat[new_key] = value
@@ -89,6 +89,8 @@ def _compare(v1, v2):
8989
return v1 == v2, ListDiff.VALUE_DIFF
9090
elif isinstance(v1, datetime.timedelta) and isinstance(v2, datetime.timedelta):
9191
return v1 == v2, ListDiff.VALUE_DIFF
92+
elif v1 is None and v2 is None:
93+
return True, ListDiff.VALUE_DIFF
9294
elif type(v1) is not type(v2):
9395
return False, ListDiff.TYPE_DIFF
9496
else:

src/earthkit/data/xr_engine/dim.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,26 +75,24 @@ def _get_metadata_keys(keys):
7575
_GRIB_MONTH_KEYS = ["forecastMonth", "fcmonth"]
7676
MONTH_KEYS = _get_metadata_keys(_GRIB_MONTH_KEYS)
7777

78-
_GRIB_VALID_DATETIME_KEYS = ["valid_time", "valid_datetime"]
79-
_VALID_DATETIME_KEYS = ["time.valid_datetime"]
78+
_GRIB_VALID_DATETIME_KEYS = ["valid_datetime"]
79+
_VALID_DATETIME_KEYS = ["valid_datetime"]
8080
VALID_DATETIME_KEYS = (
8181
["valid_time"]
8282
+ _get_component_keys("time", _VALID_DATETIME_KEYS)
8383
+ _get_metadata_keys(_GRIB_VALID_DATETIME_KEYS)
8484
)
8585

8686
_GRIB_BASE_DATETIME_KEYS = [
87-
"forecast_reference_time",
88-
"base_time",
8987
"base_datetime",
90-
"reference_time",
91-
"reference_datetime",
92-
"indexing_time",
88+
"indexingDate",
89+
"indexingTime",
9390
"indexing_datetime",
91+
"indexing_time",
9492
]
9593
_BASE_DATETIME_KEYS = ["forecast_reference_time", "base_datetime"]
9694
BASE_DATETIME_KEYS = (
97-
["forecast_reference_time"]
95+
["forecast_reference_time", "indexing_time"]
9896
+ _get_component_keys("time", _BASE_DATETIME_KEYS)
9997
+ _get_metadata_keys(_GRIB_BASE_DATETIME_KEYS)
10098
)

src/earthkit/data/xr_engine/fieldlist.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def index(self, key, maker=None):
5858
if key not in self._index:
5959
# # LOG.debug(f"Key={key} not found in IndexDB")
6060
if maker is not None:
61-
self._index[key] = maker(key)[0][key]
61+
self._index[key] = maker(key)[key]
6262
else:
6363
raise KeyError(f"Could not find index for {key=}")
6464
return self._index[key]

0 commit comments

Comments
 (0)