anemoi-datasets 0.4.4__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anemoi/datasets/_version.py +2 -2
- anemoi/datasets/commands/cleanup.py +44 -0
- anemoi/datasets/commands/create.py +52 -21
- anemoi/datasets/commands/finalise-additions.py +45 -0
- anemoi/datasets/commands/finalise.py +39 -0
- anemoi/datasets/commands/init-additions.py +45 -0
- anemoi/datasets/commands/init.py +67 -0
- anemoi/datasets/commands/inspect.py +1 -1
- anemoi/datasets/commands/load-additions.py +47 -0
- anemoi/datasets/commands/load.py +47 -0
- anemoi/datasets/commands/patch.py +39 -0
- anemoi/datasets/create/__init__.py +959 -146
- anemoi/datasets/create/check.py +5 -3
- anemoi/datasets/create/config.py +54 -2
- anemoi/datasets/create/functions/filters/pressure_level_relative_humidity_to_specific_humidity.py +57 -0
- anemoi/datasets/create/functions/filters/pressure_level_specific_humidity_to_relative_humidity.py +57 -0
- anemoi/datasets/create/functions/filters/single_level_dewpoint_to_relative_humidity.py +54 -0
- anemoi/datasets/create/functions/filters/single_level_relative_humidity_to_dewpoint.py +59 -0
- anemoi/datasets/create/functions/filters/single_level_relative_humidity_to_specific_humidity.py +115 -0
- anemoi/datasets/create/functions/filters/single_level_specific_humidity_to_relative_humidity.py +390 -0
- anemoi/datasets/create/functions/filters/speeddir_to_uv.py +77 -0
- anemoi/datasets/create/functions/filters/uv_to_speeddir.py +55 -0
- anemoi/datasets/create/functions/sources/grib.py +86 -1
- anemoi/datasets/create/functions/sources/hindcasts.py +14 -73
- anemoi/datasets/create/functions/sources/mars.py +9 -3
- anemoi/datasets/create/functions/sources/xarray/__init__.py +12 -2
- anemoi/datasets/create/functions/sources/xarray/coordinates.py +7 -0
- anemoi/datasets/create/functions/sources/xarray/field.py +8 -2
- anemoi/datasets/create/functions/sources/xarray/fieldlist.py +0 -2
- anemoi/datasets/create/functions/sources/xarray/flavour.py +21 -1
- anemoi/datasets/create/functions/sources/xarray/metadata.py +40 -40
- anemoi/datasets/create/functions/sources/xarray/time.py +63 -30
- anemoi/datasets/create/functions/sources/xarray/variable.py +15 -38
- anemoi/datasets/create/input.py +62 -39
- anemoi/datasets/create/persistent.py +1 -1
- anemoi/datasets/create/statistics/__init__.py +39 -23
- anemoi/datasets/create/utils.py +6 -2
- anemoi/datasets/data/__init__.py +1 -0
- anemoi/datasets/data/concat.py +46 -2
- anemoi/datasets/data/dataset.py +119 -34
- anemoi/datasets/data/debug.py +5 -1
- anemoi/datasets/data/forwards.py +17 -8
- anemoi/datasets/data/grids.py +17 -3
- anemoi/datasets/data/interpolate.py +133 -0
- anemoi/datasets/data/masked.py +2 -2
- anemoi/datasets/data/misc.py +56 -66
- anemoi/datasets/data/missing.py +240 -0
- anemoi/datasets/data/rescale.py +147 -0
- anemoi/datasets/data/select.py +7 -1
- anemoi/datasets/data/stores.py +23 -10
- anemoi/datasets/data/subset.py +47 -5
- anemoi/datasets/data/unchecked.py +20 -22
- anemoi/datasets/data/xy.py +125 -0
- anemoi/datasets/dates/__init__.py +124 -95
- anemoi/datasets/dates/groups.py +85 -20
- anemoi/datasets/grids.py +66 -48
- {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/METADATA +8 -17
- anemoi_datasets-0.5.0.dist-info/RECORD +105 -0
- {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/WHEEL +1 -1
- anemoi/datasets/create/loaders.py +0 -936
- anemoi_datasets-0.4.4.dist-info/RECORD +0 -86
- {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/LICENSE +0 -0
- {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/entry_points.txt +0 -0
- {anemoi_datasets-0.4.4.dist-info → anemoi_datasets-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
# granted to it by virtue of its status as an intergovernmental organisation
|
|
7
7
|
# nor does it submit to any jurisdiction.
|
|
8
8
|
#
|
|
9
|
-
import datetime
|
|
10
9
|
import logging
|
|
11
10
|
|
|
12
11
|
from earthkit.data.core.fieldlist import MultiFieldList
|
|
@@ -14,7 +13,6 @@ from earthkit.data.core.fieldlist import MultiFieldList
|
|
|
14
13
|
from anemoi.datasets.create.functions.sources.mars import mars
|
|
15
14
|
|
|
16
15
|
LOGGER = logging.getLogger(__name__)
|
|
17
|
-
DEBUG = True
|
|
18
16
|
|
|
19
17
|
|
|
20
18
|
def _to_list(x):
|
|
@@ -23,91 +21,34 @@ def _to_list(x):
|
|
|
23
21
|
return [x]
|
|
24
22
|
|
|
25
23
|
|
|
26
|
-
|
|
27
|
-
def __init__(self, base_times, available_steps, request):
|
|
28
|
-
self.base_times = base_times
|
|
29
|
-
self.available_steps = available_steps
|
|
30
|
-
self.request = request
|
|
31
|
-
|
|
32
|
-
def compute_hindcast(self, date):
|
|
33
|
-
result = []
|
|
34
|
-
for step in sorted(self.available_steps): # Use the shortest step
|
|
35
|
-
start_date = date - datetime.timedelta(hours=step)
|
|
36
|
-
hours = start_date.hour
|
|
37
|
-
if hours in self.base_times:
|
|
38
|
-
r = self.request.copy()
|
|
39
|
-
r["date"] = start_date
|
|
40
|
-
r["time"] = f"{start_date.hour:02d}00"
|
|
41
|
-
r["step"] = step
|
|
42
|
-
result.append(r)
|
|
43
|
-
|
|
44
|
-
if not result:
|
|
45
|
-
raise ValueError(
|
|
46
|
-
f"Cannot find data for {self.request} for {date} (base_times={self.base_times}, "
|
|
47
|
-
f"available_steps={self.available_steps})"
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
if len(result) > 1:
|
|
51
|
-
raise ValueError(
|
|
52
|
-
f"Multiple requests for {self.request} for {date} (base_times={self.base_times}, "
|
|
53
|
-
f"available_steps={self.available_steps})"
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
return result[0]
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def use_reference_year(reference_year, request):
|
|
60
|
-
request = request.copy()
|
|
61
|
-
hdate = request.pop("date")
|
|
62
|
-
|
|
63
|
-
if hdate.year >= reference_year:
|
|
64
|
-
return None, False
|
|
24
|
+
def hindcasts(context, dates, **request):
|
|
65
25
|
|
|
66
|
-
|
|
67
|
-
date = datetime.datetime(reference_year, hdate.month, hdate.day)
|
|
68
|
-
except ValueError:
|
|
69
|
-
if hdate.month == 2 and hdate.day == 29:
|
|
70
|
-
return None, False
|
|
71
|
-
raise
|
|
26
|
+
from anemoi.datasets.dates import HindcastsDates
|
|
72
27
|
|
|
73
|
-
|
|
74
|
-
|
|
28
|
+
provider = context.dates_provider
|
|
29
|
+
assert isinstance(provider, HindcastsDates)
|
|
75
30
|
|
|
31
|
+
context.trace("H️", f"hindcasts {len(dates)=}")
|
|
76
32
|
|
|
77
|
-
def hindcasts(context, dates, **request):
|
|
78
33
|
request["param"] = _to_list(request["param"])
|
|
79
|
-
request["step"] = _to_list(request
|
|
34
|
+
request["step"] = _to_list(request.get("step", 0))
|
|
80
35
|
request["step"] = [int(_) for _ in request["step"]]
|
|
81
36
|
|
|
82
|
-
|
|
83
|
-
request["base_times"] = [0]
|
|
84
|
-
|
|
85
|
-
available_steps = request.pop("step")
|
|
86
|
-
available_steps = _to_list(available_steps)
|
|
87
|
-
|
|
88
|
-
base_times = request.pop("base_times")
|
|
89
|
-
|
|
90
|
-
reference_year = request.pop("reference_year")
|
|
37
|
+
context.trace("H️", f"hindcast {request}")
|
|
91
38
|
|
|
92
|
-
context.trace("H️", f"hindcast {request} {base_times} {available_steps} {reference_year}")
|
|
93
|
-
|
|
94
|
-
c = HindcastCompute(base_times, available_steps, request)
|
|
95
39
|
requests = []
|
|
96
40
|
for d in dates:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
41
|
+
r = request.copy()
|
|
42
|
+
hindcast = provider.mapping[d]
|
|
43
|
+
r["hdate"] = hindcast.hdate.strftime("%Y-%m-%d")
|
|
44
|
+
r["date"] = hindcast.refdate.strftime("%Y-%m-%d")
|
|
45
|
+
r["time"] = hindcast.refdate.strftime("%H")
|
|
46
|
+
r["step"] = hindcast.step
|
|
47
|
+
requests.append(r)
|
|
104
48
|
|
|
105
49
|
if len(requests) == 0:
|
|
106
|
-
# print("HINDCASTS no requests")
|
|
107
50
|
return MultiFieldList([])
|
|
108
51
|
|
|
109
|
-
# print("HINDCASTS requests", requests)
|
|
110
|
-
|
|
111
52
|
return mars(
|
|
112
53
|
context,
|
|
113
54
|
dates,
|
|
@@ -203,16 +203,22 @@ def mars(context, dates, *requests, request_already_using_valid_datetime=False,
|
|
|
203
203
|
request_already_using_valid_datetime=request_already_using_valid_datetime,
|
|
204
204
|
date_key=date_key,
|
|
205
205
|
)
|
|
206
|
+
|
|
207
|
+
requests = list(requests)
|
|
208
|
+
|
|
206
209
|
ds = from_source("empty")
|
|
210
|
+
context.trace("✅", f"{[str(d) for d in dates]}")
|
|
211
|
+
context.trace("✅", f"Will run {len(requests)} requests")
|
|
212
|
+
for r in requests:
|
|
213
|
+
r = {k: v for k, v in r.items() if v != ("-",)}
|
|
214
|
+
context.trace("✅", f"mars {r}")
|
|
215
|
+
|
|
207
216
|
for r in requests:
|
|
208
217
|
r = {k: v for k, v in r.items() if v != ("-",)}
|
|
209
218
|
|
|
210
219
|
if context.use_grib_paramid and "param" in r:
|
|
211
220
|
r = use_grib_paramid(r)
|
|
212
221
|
|
|
213
|
-
if DEBUG:
|
|
214
|
-
context.trace("✅", f"from_source(mars, {r}")
|
|
215
|
-
|
|
216
222
|
for k, v in r.items():
|
|
217
223
|
if k not in MARS_KEYS:
|
|
218
224
|
raise ValueError(
|
|
@@ -52,9 +52,19 @@ def load_one(emoji, context, dates, dataset, options={}, flavour=None, **kwargs)
|
|
|
52
52
|
result = MultiFieldList([fs.sel(valid_datetime=date, **kwargs) for date in dates])
|
|
53
53
|
|
|
54
54
|
if len(result) == 0:
|
|
55
|
-
LOG.warning(f"No data found for {dataset} and dates {dates}")
|
|
55
|
+
LOG.warning(f"No data found for {dataset} and dates {dates} and {kwargs}")
|
|
56
56
|
LOG.warning(f"Options: {options}")
|
|
57
|
-
|
|
57
|
+
|
|
58
|
+
for i, k in enumerate(fs):
|
|
59
|
+
a = ["valid_datetime", k.metadata("valid_datetime", default=None)]
|
|
60
|
+
for n in kwargs.keys():
|
|
61
|
+
a.extend([n, k.metadata(n, default=None)])
|
|
62
|
+
print([str(x) for x in a])
|
|
63
|
+
|
|
64
|
+
if i > 16:
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
# LOG.warning(data)
|
|
58
68
|
|
|
59
69
|
return result
|
|
60
70
|
|
|
@@ -55,6 +55,7 @@ class Coordinate:
|
|
|
55
55
|
is_time = False
|
|
56
56
|
is_step = False
|
|
57
57
|
is_date = False
|
|
58
|
+
is_member = False
|
|
58
59
|
|
|
59
60
|
def __init__(self, variable):
|
|
60
61
|
self.variable = variable
|
|
@@ -201,8 +202,14 @@ class LevelCoordinate(Coordinate):
|
|
|
201
202
|
|
|
202
203
|
|
|
203
204
|
class EnsembleCoordinate(Coordinate):
|
|
205
|
+
is_member = True
|
|
204
206
|
mars_names = ("number",)
|
|
205
207
|
|
|
208
|
+
def normalise(self, value):
|
|
209
|
+
if int(value) == value:
|
|
210
|
+
return int(value)
|
|
211
|
+
return value
|
|
212
|
+
|
|
206
213
|
|
|
207
214
|
class LongitudeCoordinate(Coordinate):
|
|
208
215
|
is_grid = True
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
# nor does it submit to any jurisdiction.
|
|
8
8
|
#
|
|
9
9
|
|
|
10
|
+
import datetime
|
|
10
11
|
import logging
|
|
11
12
|
|
|
12
13
|
from earthkit.data.core.fieldlist import Field
|
|
@@ -80,7 +81,7 @@ class XArrayField(Field):
|
|
|
80
81
|
return values.reshape(self.shape)
|
|
81
82
|
|
|
82
83
|
def _make_metadata(self):
|
|
83
|
-
return XArrayMetadata(self
|
|
84
|
+
return XArrayMetadata(self)
|
|
84
85
|
|
|
85
86
|
def grid_points(self):
|
|
86
87
|
return self.owner.grid_points()
|
|
@@ -103,7 +104,12 @@ class XArrayField(Field):
|
|
|
103
104
|
|
|
104
105
|
@property
|
|
105
106
|
def forecast_reference_time(self):
|
|
106
|
-
|
|
107
|
+
date, time = self.metadata("date", "time")
|
|
108
|
+
assert len(time) == 4, time
|
|
109
|
+
assert len(date) == 8, date
|
|
110
|
+
yyyymmdd = int(date)
|
|
111
|
+
time = int(time) // 100
|
|
112
|
+
return datetime.datetime(yyyymmdd // 10000, yyyymmdd // 100 % 100, yyyymmdd % 100, time)
|
|
107
113
|
|
|
108
114
|
def __repr__(self):
|
|
109
115
|
return repr(self._metadata)
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
from .coordinates import DateCoordinate
|
|
12
|
+
from .coordinates import EnsembleCoordinate
|
|
12
13
|
from .coordinates import LatitudeCoordinate
|
|
13
14
|
from .coordinates import LevelCoordinate
|
|
14
15
|
from .coordinates import LongitudeCoordinate
|
|
@@ -135,6 +136,17 @@ class CoordinateGuesser:
|
|
|
135
136
|
if d is not None:
|
|
136
137
|
return d
|
|
137
138
|
|
|
139
|
+
d = self._is_number(
|
|
140
|
+
c,
|
|
141
|
+
axis=axis,
|
|
142
|
+
name=name,
|
|
143
|
+
long_name=long_name,
|
|
144
|
+
standard_name=standard_name,
|
|
145
|
+
units=units,
|
|
146
|
+
)
|
|
147
|
+
if d is not None:
|
|
148
|
+
return d
|
|
149
|
+
|
|
138
150
|
if c.shape in ((1,), tuple()):
|
|
139
151
|
return ScalarCoordinate(c)
|
|
140
152
|
|
|
@@ -249,9 +261,13 @@ class DefaultCoordinateGuesser(CoordinateGuesser):
|
|
|
249
261
|
if standard_name == "depth":
|
|
250
262
|
return LevelCoordinate(c, "depth")
|
|
251
263
|
|
|
252
|
-
if name == "
|
|
264
|
+
if name == "vertical" and units == "hPa":
|
|
253
265
|
return LevelCoordinate(c, "pl")
|
|
254
266
|
|
|
267
|
+
def _is_number(self, c, *, axis, name, long_name, standard_name, units):
|
|
268
|
+
if name in ("realization", "number"):
|
|
269
|
+
return EnsembleCoordinate(c)
|
|
270
|
+
|
|
255
271
|
|
|
256
272
|
class FlavourCoordinateGuesser(CoordinateGuesser):
|
|
257
273
|
def __init__(self, ds, flavour):
|
|
@@ -328,3 +344,7 @@ class FlavourCoordinateGuesser(CoordinateGuesser):
|
|
|
328
344
|
return self.flavour["levtype"]
|
|
329
345
|
|
|
330
346
|
raise NotImplementedError(f"levtype for {c=}")
|
|
347
|
+
|
|
348
|
+
def _is_number(self, c, *, axis, name, long_name, standard_name, units):
|
|
349
|
+
if self._match(c, "number", locals()):
|
|
350
|
+
return DateCoordinate(c)
|
|
@@ -10,29 +10,37 @@
|
|
|
10
10
|
import logging
|
|
11
11
|
from functools import cached_property
|
|
12
12
|
|
|
13
|
+
from anemoi.utils.dates import as_datetime
|
|
13
14
|
from earthkit.data.core.geography import Geography
|
|
14
15
|
from earthkit.data.core.metadata import RawMetadata
|
|
15
|
-
from earthkit.data.utils.dates import to_datetime
|
|
16
16
|
from earthkit.data.utils.projections import Projection
|
|
17
17
|
|
|
18
18
|
LOG = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
class
|
|
21
|
+
class _MDMapping:
|
|
22
22
|
|
|
23
|
-
def __init__(self,
|
|
24
|
-
self.
|
|
23
|
+
def __init__(self, variable):
|
|
24
|
+
self.variable = variable
|
|
25
|
+
self.time = variable.time
|
|
26
|
+
self.mapping = dict(param="variable")
|
|
27
|
+
for c in variable.coordinates:
|
|
28
|
+
for v in c.mars_names:
|
|
29
|
+
assert v not in self.mapping, f"Duplicate key '{v}' in {c}"
|
|
30
|
+
self.mapping[v] = c.variable.name
|
|
25
31
|
|
|
26
|
-
def
|
|
27
|
-
|
|
28
|
-
return self.user_to_internal.get(kwargs, kwargs)
|
|
29
|
-
return {self.user_to_internal.get(k, k): v for k, v in kwargs.items()}
|
|
32
|
+
def _from_user(self, key):
|
|
33
|
+
return self.mapping.get(key, key)
|
|
30
34
|
|
|
31
|
-
def
|
|
32
|
-
|
|
35
|
+
def from_user(self, kwargs):
|
|
36
|
+
print("from_user", kwargs, self)
|
|
37
|
+
return {self._from_user(k): v for k, v in kwargs.items()}
|
|
33
38
|
|
|
34
39
|
def __repr__(self):
|
|
35
|
-
return f"MDMapping({self.
|
|
40
|
+
return f"MDMapping({self.mapping})"
|
|
41
|
+
|
|
42
|
+
def fill_time_metadata(self, field, md):
|
|
43
|
+
md["valid_datetime"] = as_datetime(self.variable.time.fill_time_metadata(field._md, md)).isoformat()
|
|
36
44
|
|
|
37
45
|
|
|
38
46
|
class XArrayMetadata(RawMetadata):
|
|
@@ -40,23 +48,11 @@ class XArrayMetadata(RawMetadata):
|
|
|
40
48
|
NAMESPACES = ["default", "mars"]
|
|
41
49
|
MARS_KEYS = ["param", "step", "levelist", "levtype", "number", "date", "time"]
|
|
42
50
|
|
|
43
|
-
def __init__(self, field
|
|
51
|
+
def __init__(self, field):
|
|
44
52
|
self._field = field
|
|
45
53
|
md = field._md.copy()
|
|
46
|
-
|
|
47
|
-
self._mapping
|
|
48
|
-
if mapping is None:
|
|
49
|
-
time_coord = [c for c in field.owner.coordinates if c.is_time]
|
|
50
|
-
if len(time_coord) == 1:
|
|
51
|
-
time_key = time_coord[0].name
|
|
52
|
-
else:
|
|
53
|
-
time_key = "time"
|
|
54
|
-
else:
|
|
55
|
-
time_key = mapping.from_user("valid_datetime")
|
|
56
|
-
self._time = to_datetime(md.pop(time_key))
|
|
57
|
-
self._field.owner.time.fill_time_metadata(self._time, md)
|
|
58
|
-
md["valid_datetime"] = self._time.isoformat()
|
|
59
|
-
|
|
54
|
+
self._mapping = _MDMapping(field.owner)
|
|
55
|
+
self._mapping.fill_time_metadata(field, md)
|
|
60
56
|
super().__init__(md)
|
|
61
57
|
|
|
62
58
|
@cached_property
|
|
@@ -74,24 +70,29 @@ class XArrayMetadata(RawMetadata):
|
|
|
74
70
|
return self._as_mars()
|
|
75
71
|
|
|
76
72
|
def _as_mars(self):
|
|
77
|
-
return
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
73
|
+
return {}
|
|
74
|
+
# p = dict(
|
|
75
|
+
# param=self.get("variable", self.get("param")),
|
|
76
|
+
# step=self.get("step"),
|
|
77
|
+
# levelist=self.get("levelist", self.get("level")),
|
|
78
|
+
# levtype=self.get("levtype"),
|
|
79
|
+
# number=self.get("number"),
|
|
80
|
+
# date=self.get("date"),
|
|
81
|
+
# time=self.get("time"),
|
|
82
|
+
# )
|
|
83
|
+
# return {k: v for k, v in p.items() if v is not None}
|
|
86
84
|
|
|
87
85
|
def _base_datetime(self):
|
|
88
86
|
return self._field.forecast_reference_time
|
|
89
87
|
|
|
90
88
|
def _valid_datetime(self):
|
|
91
|
-
return self.
|
|
89
|
+
return self._get("valid_datetime")
|
|
92
90
|
|
|
93
91
|
def _get(self, key, **kwargs):
|
|
94
92
|
|
|
93
|
+
if key in self._d:
|
|
94
|
+
return self._d[key]
|
|
95
|
+
|
|
95
96
|
if key.startswith("mars."):
|
|
96
97
|
key = key[5:]
|
|
97
98
|
if key not in self.MARS_KEYS:
|
|
@@ -100,8 +101,7 @@ class XArrayMetadata(RawMetadata):
|
|
|
100
101
|
else:
|
|
101
102
|
return kwargs.get("default", None)
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
key = self._mapping.from_user(key)
|
|
104
|
+
key = self._mapping._from_user(key)
|
|
105
105
|
|
|
106
106
|
return super()._get(key, **kwargs)
|
|
107
107
|
|
|
@@ -137,12 +137,12 @@ class XArrayFieldGeography(Geography):
|
|
|
137
137
|
# TODO: implement resolution
|
|
138
138
|
return None
|
|
139
139
|
|
|
140
|
-
@property
|
|
140
|
+
# @property
|
|
141
141
|
def mars_grid(self):
|
|
142
142
|
# TODO: implement mars_grid
|
|
143
143
|
return None
|
|
144
144
|
|
|
145
|
-
@property
|
|
145
|
+
# @property
|
|
146
146
|
def mars_area(self):
|
|
147
147
|
# TODO: code me
|
|
148
148
|
# return [self.north, self.west, self.south, self.east]
|
|
@@ -10,8 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
import datetime
|
|
12
12
|
|
|
13
|
+
from anemoi.utils.dates import as_datetime
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
class Time:
|
|
17
|
+
|
|
15
18
|
@classmethod
|
|
16
19
|
def from_coordinates(cls, coordinates):
|
|
17
20
|
time_coordinate = [c for c in coordinates if c.is_time]
|
|
@@ -19,16 +22,16 @@ class Time:
|
|
|
19
22
|
date_coordinate = [c for c in coordinates if c.is_date]
|
|
20
23
|
|
|
21
24
|
if len(date_coordinate) == 0 and len(time_coordinate) == 1 and len(step_coordinate) == 1:
|
|
22
|
-
return
|
|
25
|
+
return ForecastFromValidTimeAndStep(time_coordinate[0], step_coordinate[0])
|
|
23
26
|
|
|
24
27
|
if len(date_coordinate) == 0 and len(time_coordinate) == 1 and len(step_coordinate) == 0:
|
|
25
|
-
return Analysis()
|
|
28
|
+
return Analysis(time_coordinate[0])
|
|
26
29
|
|
|
27
30
|
if len(date_coordinate) == 0 and len(time_coordinate) == 0 and len(step_coordinate) == 0:
|
|
28
31
|
return Constant()
|
|
29
32
|
|
|
30
33
|
if len(date_coordinate) == 1 and len(time_coordinate) == 1 and len(step_coordinate) == 0:
|
|
31
|
-
return ForecastFromValidTimeAndBaseTime(date_coordinate[0])
|
|
34
|
+
return ForecastFromValidTimeAndBaseTime(date_coordinate[0], time_coordinate[0])
|
|
32
35
|
|
|
33
36
|
if len(date_coordinate) == 1 and len(time_coordinate) == 0 and len(step_coordinate) == 1:
|
|
34
37
|
return ForecastFromBaseTimeAndDate(date_coordinate[0], step_coordinate[0])
|
|
@@ -38,61 +41,91 @@ class Time:
|
|
|
38
41
|
|
|
39
42
|
class Constant(Time):
|
|
40
43
|
|
|
41
|
-
def fill_time_metadata(self,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
metadata["
|
|
44
|
+
def fill_time_metadata(self, coords_values, metadata):
|
|
45
|
+
raise NotImplementedError("Constant time not implemented")
|
|
46
|
+
# print("Constant", coords_values, metadata)
|
|
47
|
+
# metadata["date"] = time.strftime("%Y%m%d")
|
|
48
|
+
# metadata["time"] = time.strftime("%H%M")
|
|
49
|
+
# metadata["step"] = 0
|
|
45
50
|
|
|
46
51
|
|
|
47
52
|
class Analysis(Time):
|
|
48
53
|
|
|
49
|
-
def
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
def __init__(self, time_coordinate):
|
|
55
|
+
self.time_coordinate_name = time_coordinate.variable.name
|
|
56
|
+
|
|
57
|
+
def fill_time_metadata(self, coords_values, metadata):
|
|
58
|
+
valid_datetime = coords_values[self.time_coordinate_name]
|
|
59
|
+
|
|
60
|
+
metadata["date"] = as_datetime(valid_datetime).strftime("%Y%m%d")
|
|
61
|
+
metadata["time"] = as_datetime(valid_datetime).strftime("%H%M")
|
|
52
62
|
metadata["step"] = 0
|
|
53
63
|
|
|
64
|
+
return valid_datetime
|
|
54
65
|
|
|
55
|
-
class ForecasstFromValidTimeAndStep(Time):
|
|
56
|
-
def __init__(self, step_coordinate):
|
|
57
|
-
self.step_name = step_coordinate.variable.name
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
class ForecastFromValidTimeAndStep(Time):
|
|
68
|
+
|
|
69
|
+
def __init__(self, time_coordinate, step_coordinate):
|
|
70
|
+
self.time_coordinate_name = time_coordinate.variable.name
|
|
71
|
+
self.step_coordinate_name = step_coordinate.variable.name
|
|
72
|
+
|
|
73
|
+
def fill_time_metadata(self, coords_values, metadata):
|
|
74
|
+
valid_datetime = coords_values[self.time_coordinate_name]
|
|
75
|
+
step = coords_values[self.step_coordinate_name]
|
|
76
|
+
|
|
61
77
|
assert isinstance(step, datetime.timedelta)
|
|
62
|
-
|
|
78
|
+
base_datetime = valid_datetime - step
|
|
63
79
|
|
|
64
80
|
hours = step.total_seconds() / 3600
|
|
65
81
|
assert int(hours) == hours
|
|
66
82
|
|
|
67
|
-
metadata["date"] =
|
|
68
|
-
metadata["time"] =
|
|
83
|
+
metadata["date"] = as_datetime(base_datetime).strftime("%Y%m%d")
|
|
84
|
+
metadata["time"] = as_datetime(base_datetime).strftime("%H%M")
|
|
69
85
|
metadata["step"] = int(hours)
|
|
86
|
+
return valid_datetime
|
|
70
87
|
|
|
71
88
|
|
|
72
89
|
class ForecastFromValidTimeAndBaseTime(Time):
|
|
73
|
-
def __init__(self, date_coordinate):
|
|
74
|
-
self.date_coordinate = date_coordinate
|
|
75
90
|
|
|
76
|
-
def
|
|
91
|
+
def __init__(self, date_coordinate, time_coordinate):
|
|
92
|
+
self.date_coordinate.name = date_coordinate.name
|
|
93
|
+
self.time_coordinate.name = time_coordinate.name
|
|
94
|
+
|
|
95
|
+
def fill_time_metadata(self, coords_values, metadata):
|
|
96
|
+
valid_datetime = coords_values[self.time_coordinate_name]
|
|
97
|
+
base_datetime = coords_values[self.date_coordinate_name]
|
|
77
98
|
|
|
78
|
-
step =
|
|
99
|
+
step = valid_datetime - base_datetime
|
|
79
100
|
|
|
80
101
|
hours = step.total_seconds() / 3600
|
|
81
102
|
assert int(hours) == hours
|
|
82
103
|
|
|
83
|
-
metadata["date"] =
|
|
84
|
-
metadata["time"] =
|
|
104
|
+
metadata["date"] = as_datetime(base_datetime).strftime("%Y%m%d")
|
|
105
|
+
metadata["time"] = as_datetime(base_datetime).strftime("%H%M")
|
|
85
106
|
metadata["step"] = int(hours)
|
|
86
107
|
|
|
108
|
+
return valid_datetime
|
|
109
|
+
|
|
87
110
|
|
|
88
111
|
class ForecastFromBaseTimeAndDate(Time):
|
|
112
|
+
|
|
89
113
|
def __init__(self, date_coordinate, step_coordinate):
|
|
90
|
-
self.
|
|
91
|
-
self.
|
|
114
|
+
self.date_coordinate_name = date_coordinate.name
|
|
115
|
+
self.step_coordinate_name = step_coordinate.name
|
|
116
|
+
|
|
117
|
+
def fill_time_metadata(self, coords_values, metadata):
|
|
118
|
+
|
|
119
|
+
date = coords_values[self.date_coordinate_name]
|
|
120
|
+
step = coords_values[self.step_coordinate_name]
|
|
121
|
+
assert isinstance(step, datetime.timedelta)
|
|
122
|
+
|
|
123
|
+
metadata["date"] = as_datetime(date).strftime("%Y%m%d")
|
|
124
|
+
metadata["time"] = as_datetime(date).strftime("%H%M")
|
|
125
|
+
|
|
126
|
+
hours = step.total_seconds() / 3600
|
|
92
127
|
|
|
93
|
-
def fill_time_metadata(self, time, metadata):
|
|
94
|
-
metadata["date"] = time.strftime("%Y%m%d")
|
|
95
|
-
metadata["time"] = time.strftime("%H%M")
|
|
96
|
-
hours = metadata[self.step_coordinate.name].total_seconds() / 3600
|
|
97
128
|
assert int(hours) == hours
|
|
98
129
|
metadata["step"] = int(hours)
|
|
130
|
+
|
|
131
|
+
return date + step
|
|
@@ -14,34 +14,32 @@ from functools import cached_property
|
|
|
14
14
|
import numpy as np
|
|
15
15
|
from earthkit.data.utils.array import ensure_backend
|
|
16
16
|
|
|
17
|
-
from anemoi.datasets.create.functions.sources.xarray.metadata import MDMapping
|
|
18
|
-
|
|
19
17
|
from .field import XArrayField
|
|
20
18
|
|
|
21
19
|
LOG = logging.getLogger(__name__)
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
class Variable:
|
|
25
|
-
def __init__(
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
*,
|
|
26
|
+
ds,
|
|
27
|
+
var,
|
|
28
|
+
coordinates,
|
|
29
|
+
grid,
|
|
30
|
+
time,
|
|
31
|
+
metadata,
|
|
32
|
+
array_backend=None,
|
|
33
|
+
):
|
|
26
34
|
self.ds = ds
|
|
27
35
|
self.var = var
|
|
28
36
|
|
|
29
37
|
self.grid = grid
|
|
30
38
|
self.coordinates = coordinates
|
|
31
39
|
|
|
32
|
-
# print("Variable", var.name)
|
|
33
|
-
# for c in coordinates:
|
|
34
|
-
# print(" ", c)
|
|
35
|
-
|
|
36
40
|
self._metadata = metadata.copy()
|
|
37
|
-
# self._metadata.update(var.attrs)
|
|
38
41
|
self._metadata.update({"variable": var.name})
|
|
39
42
|
|
|
40
|
-
# self._metadata.setdefault("level", None)
|
|
41
|
-
# self._metadata.setdefault("number", 0)
|
|
42
|
-
# self._metadata.setdefault("levtype", "sfc")
|
|
43
|
-
self._mapping = mapping
|
|
44
|
-
|
|
45
43
|
self.time = time
|
|
46
44
|
|
|
47
45
|
self.shape = tuple(len(c.variable) for c in coordinates if c.is_dim and not c.scalar and not c.is_grid)
|
|
@@ -51,23 +49,6 @@ class Variable:
|
|
|
51
49
|
self.length = math.prod(self.shape)
|
|
52
50
|
self.array_backend = ensure_backend(array_backend)
|
|
53
51
|
|
|
54
|
-
def update_metadata_mapping(self, kwargs):
|
|
55
|
-
|
|
56
|
-
result = {}
|
|
57
|
-
|
|
58
|
-
for k, v in kwargs.items():
|
|
59
|
-
if k == "param":
|
|
60
|
-
result[k] = "variable"
|
|
61
|
-
continue
|
|
62
|
-
|
|
63
|
-
for c in self.coordinates:
|
|
64
|
-
if k in c.mars_names:
|
|
65
|
-
for v in c.mars_names:
|
|
66
|
-
result[v] = c.variable.name
|
|
67
|
-
break
|
|
68
|
-
|
|
69
|
-
self._mapping = MDMapping(result)
|
|
70
|
-
|
|
71
52
|
@property
|
|
72
53
|
def name(self):
|
|
73
54
|
return self.var.name
|
|
@@ -111,17 +92,11 @@ class Variable:
|
|
|
111
92
|
kwargs = {k: v for k, v in zip(self.names, coords)}
|
|
112
93
|
return XArrayField(self, self.var.isel(kwargs))
|
|
113
94
|
|
|
114
|
-
@property
|
|
115
|
-
def mapping(self):
|
|
116
|
-
return self._mapping
|
|
117
|
-
|
|
118
95
|
def sel(self, missing, **kwargs):
|
|
119
96
|
|
|
120
97
|
if not kwargs:
|
|
121
98
|
return self
|
|
122
99
|
|
|
123
|
-
kwargs = self._mapping.from_user(kwargs)
|
|
124
|
-
|
|
125
100
|
k, v = kwargs.popitem()
|
|
126
101
|
|
|
127
102
|
c = self.by_name.get(k)
|
|
@@ -147,13 +122,15 @@ class Variable:
|
|
|
147
122
|
grid=self.grid,
|
|
148
123
|
time=self.time,
|
|
149
124
|
metadata=metadata,
|
|
150
|
-
mapping=self.mapping,
|
|
151
125
|
)
|
|
152
126
|
|
|
153
127
|
return variable.sel(missing, **kwargs)
|
|
154
128
|
|
|
155
129
|
def match(self, **kwargs):
|
|
156
|
-
|
|
130
|
+
|
|
131
|
+
if "param" in kwargs:
|
|
132
|
+
assert "variable" not in kwargs
|
|
133
|
+
kwargs["variable"] = kwargs.pop("param")
|
|
157
134
|
|
|
158
135
|
if "variable" in kwargs:
|
|
159
136
|
name = kwargs.pop("variable")
|