anemoi-datasets 0.5.16__py3-none-any.whl → 0.5.17__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/__init__.py +4 -1
- anemoi/datasets/__main__.py +12 -2
- anemoi/datasets/_version.py +9 -4
- anemoi/datasets/commands/cleanup.py +17 -2
- anemoi/datasets/commands/compare.py +18 -2
- anemoi/datasets/commands/copy.py +196 -14
- anemoi/datasets/commands/create.py +50 -7
- anemoi/datasets/commands/finalise-additions.py +17 -2
- anemoi/datasets/commands/finalise.py +17 -2
- anemoi/datasets/commands/init-additions.py +17 -2
- anemoi/datasets/commands/init.py +16 -2
- anemoi/datasets/commands/inspect.py +283 -62
- anemoi/datasets/commands/load-additions.py +16 -2
- anemoi/datasets/commands/load.py +16 -2
- anemoi/datasets/commands/patch.py +17 -2
- anemoi/datasets/commands/publish.py +17 -2
- anemoi/datasets/commands/scan.py +31 -3
- anemoi/datasets/compute/recentre.py +47 -11
- anemoi/datasets/create/__init__.py +612 -85
- anemoi/datasets/create/check.py +142 -20
- anemoi/datasets/create/chunks.py +64 -4
- anemoi/datasets/create/config.py +185 -21
- anemoi/datasets/create/filter.py +50 -0
- anemoi/datasets/create/filters/__init__.py +33 -0
- anemoi/datasets/create/filters/empty.py +37 -0
- anemoi/datasets/create/filters/legacy.py +93 -0
- anemoi/datasets/create/filters/noop.py +37 -0
- anemoi/datasets/create/filters/orog_to_z.py +58 -0
- anemoi/datasets/create/{functions/filters → filters}/pressure_level_relative_humidity_to_specific_humidity.py +33 -10
- anemoi/datasets/create/{functions/filters → filters}/pressure_level_specific_humidity_to_relative_humidity.py +32 -8
- anemoi/datasets/create/filters/rename.py +205 -0
- anemoi/datasets/create/{functions/filters → filters}/rotate_winds.py +43 -28
- anemoi/datasets/create/{functions/filters → filters}/single_level_dewpoint_to_relative_humidity.py +32 -9
- anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_dewpoint.py +33 -9
- anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_specific_humidity.py +55 -7
- anemoi/datasets/create/{functions/filters → filters}/single_level_specific_humidity_to_relative_humidity.py +98 -37
- anemoi/datasets/create/filters/speeddir_to_uv.py +95 -0
- anemoi/datasets/create/{functions/filters → filters}/sum.py +24 -27
- anemoi/datasets/create/filters/transform.py +53 -0
- anemoi/datasets/create/{functions/filters → filters}/unrotate_winds.py +27 -18
- anemoi/datasets/create/filters/uv_to_speeddir.py +94 -0
- anemoi/datasets/create/{functions/filters → filters}/wz_to_w.py +51 -33
- anemoi/datasets/create/input/__init__.py +76 -5
- anemoi/datasets/create/input/action.py +149 -13
- anemoi/datasets/create/input/concat.py +81 -10
- anemoi/datasets/create/input/context.py +39 -4
- anemoi/datasets/create/input/data_sources.py +72 -6
- anemoi/datasets/create/input/empty.py +21 -3
- anemoi/datasets/create/input/filter.py +60 -12
- anemoi/datasets/create/input/function.py +154 -37
- anemoi/datasets/create/input/join.py +86 -14
- anemoi/datasets/create/input/misc.py +67 -17
- anemoi/datasets/create/input/pipe.py +33 -6
- anemoi/datasets/create/input/repeated_dates.py +189 -41
- anemoi/datasets/create/input/result.py +202 -87
- anemoi/datasets/create/input/step.py +119 -22
- anemoi/datasets/create/input/template.py +100 -13
- anemoi/datasets/create/input/trace.py +62 -7
- anemoi/datasets/create/patch.py +52 -4
- anemoi/datasets/create/persistent.py +134 -17
- anemoi/datasets/create/size.py +15 -1
- anemoi/datasets/create/source.py +51 -0
- anemoi/datasets/create/sources/__init__.py +36 -0
- anemoi/datasets/create/{functions/sources → sources}/accumulations.py +296 -30
- anemoi/datasets/create/{functions/sources → sources}/constants.py +27 -2
- anemoi/datasets/create/{functions/sources → sources}/eccc_fstd.py +7 -3
- anemoi/datasets/create/sources/empty.py +37 -0
- anemoi/datasets/create/{functions/sources → sources}/forcings.py +25 -1
- anemoi/datasets/create/sources/grib.py +297 -0
- anemoi/datasets/create/{functions/sources → sources}/hindcasts.py +38 -4
- anemoi/datasets/create/sources/legacy.py +93 -0
- anemoi/datasets/create/{functions/sources → sources}/mars.py +168 -20
- anemoi/datasets/create/sources/netcdf.py +42 -0
- anemoi/datasets/create/sources/opendap.py +43 -0
- anemoi/datasets/create/{functions/sources/__init__.py → sources/patterns.py} +35 -4
- anemoi/datasets/create/sources/recentre.py +150 -0
- anemoi/datasets/create/{functions/sources → sources}/source.py +27 -5
- anemoi/datasets/create/{functions/sources → sources}/tendencies.py +64 -7
- anemoi/datasets/create/sources/xarray.py +92 -0
- anemoi/datasets/create/sources/xarray_kerchunk.py +36 -0
- anemoi/datasets/create/sources/xarray_support/README.md +1 -0
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/__init__.py +109 -8
- anemoi/datasets/create/sources/xarray_support/coordinates.py +442 -0
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/field.py +94 -16
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/fieldlist.py +90 -25
- anemoi/datasets/create/sources/xarray_support/flavour.py +1036 -0
- anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/grid.py +92 -31
- anemoi/datasets/create/sources/xarray_support/metadata.py +395 -0
- anemoi/datasets/create/sources/xarray_support/patch.py +91 -0
- anemoi/datasets/create/sources/xarray_support/time.py +391 -0
- anemoi/datasets/create/sources/xarray_support/variable.py +331 -0
- anemoi/datasets/create/sources/xarray_zarr.py +41 -0
- anemoi/datasets/create/{functions/sources → sources}/zenodo.py +34 -5
- anemoi/datasets/create/statistics/__init__.py +233 -44
- anemoi/datasets/create/statistics/summary.py +52 -6
- anemoi/datasets/create/testing.py +76 -0
- anemoi/datasets/create/{functions/filters/noop.py → typing.py} +6 -3
- anemoi/datasets/create/utils.py +97 -6
- anemoi/datasets/create/writer.py +26 -4
- anemoi/datasets/create/zarr.py +170 -23
- anemoi/datasets/data/__init__.py +51 -4
- anemoi/datasets/data/complement.py +191 -40
- anemoi/datasets/data/concat.py +141 -16
- anemoi/datasets/data/dataset.py +552 -61
- anemoi/datasets/data/debug.py +197 -26
- anemoi/datasets/data/ensemble.py +93 -8
- anemoi/datasets/data/fill_missing.py +165 -18
- anemoi/datasets/data/forwards.py +428 -56
- anemoi/datasets/data/grids.py +323 -97
- anemoi/datasets/data/indexing.py +112 -19
- anemoi/datasets/data/interpolate.py +92 -12
- anemoi/datasets/data/join.py +158 -19
- anemoi/datasets/data/masked.py +129 -15
- anemoi/datasets/data/merge.py +137 -23
- anemoi/datasets/data/misc.py +172 -16
- anemoi/datasets/data/missing.py +233 -29
- anemoi/datasets/data/rescale.py +111 -10
- anemoi/datasets/data/select.py +168 -26
- anemoi/datasets/data/statistics.py +67 -6
- anemoi/datasets/data/stores.py +149 -64
- anemoi/datasets/data/subset.py +159 -25
- anemoi/datasets/data/unchecked.py +168 -57
- anemoi/datasets/data/xy.py +168 -25
- anemoi/datasets/dates/__init__.py +191 -16
- anemoi/datasets/dates/groups.py +189 -47
- anemoi/datasets/grids.py +270 -31
- anemoi/datasets/testing.py +28 -1
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/METADATA +9 -6
- anemoi_datasets-0.5.17.dist-info/RECORD +137 -0
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/WHEEL +1 -1
- anemoi/datasets/create/functions/__init__.py +0 -66
- anemoi/datasets/create/functions/filters/__init__.py +0 -9
- anemoi/datasets/create/functions/filters/empty.py +0 -17
- anemoi/datasets/create/functions/filters/orog_to_z.py +0 -58
- anemoi/datasets/create/functions/filters/rename.py +0 -79
- anemoi/datasets/create/functions/filters/speeddir_to_uv.py +0 -78
- anemoi/datasets/create/functions/filters/uv_to_speeddir.py +0 -56
- anemoi/datasets/create/functions/sources/empty.py +0 -15
- anemoi/datasets/create/functions/sources/grib.py +0 -150
- anemoi/datasets/create/functions/sources/netcdf.py +0 -15
- anemoi/datasets/create/functions/sources/opendap.py +0 -15
- anemoi/datasets/create/functions/sources/recentre.py +0 -60
- anemoi/datasets/create/functions/sources/xarray/coordinates.py +0 -255
- anemoi/datasets/create/functions/sources/xarray/flavour.py +0 -472
- anemoi/datasets/create/functions/sources/xarray/metadata.py +0 -148
- anemoi/datasets/create/functions/sources/xarray/patch.py +0 -44
- anemoi/datasets/create/functions/sources/xarray/time.py +0 -177
- anemoi/datasets/create/functions/sources/xarray/variable.py +0 -188
- anemoi/datasets/create/functions/sources/xarray_kerchunk.py +0 -42
- anemoi/datasets/create/functions/sources/xarray_zarr.py +0 -15
- anemoi/datasets/utils/fields.py +0 -47
- anemoi_datasets-0.5.16.dist-info/RECORD +0 -129
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/entry_points.txt +0 -0
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info/licenses}/LICENSE +0 -0
- {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/top_level.txt +0 -0
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import importlib
|
|
12
|
-
|
|
13
|
-
import entrypoints
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def assert_is_fieldlist(obj):
|
|
17
|
-
from earthkit.data.indexing.fieldlist import FieldList
|
|
18
|
-
|
|
19
|
-
assert isinstance(obj, FieldList), type(obj)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def import_function(name, kind):
|
|
23
|
-
|
|
24
|
-
from anemoi.transform.filters import filter_registry
|
|
25
|
-
from anemoi.transform.sources import source_registry
|
|
26
|
-
|
|
27
|
-
name = name.replace("-", "_")
|
|
28
|
-
|
|
29
|
-
plugins = {}
|
|
30
|
-
for e in entrypoints.get_group_all(f"anemoi.datasets.{kind}"):
|
|
31
|
-
plugins[e.name.replace("_", "-")] = e
|
|
32
|
-
|
|
33
|
-
if name in plugins:
|
|
34
|
-
return plugins[name].load()
|
|
35
|
-
|
|
36
|
-
try:
|
|
37
|
-
module = importlib.import_module(
|
|
38
|
-
f".{kind}.{name}",
|
|
39
|
-
package=__name__,
|
|
40
|
-
)
|
|
41
|
-
return module.execute
|
|
42
|
-
except ModuleNotFoundError:
|
|
43
|
-
pass
|
|
44
|
-
|
|
45
|
-
if kind == "filters":
|
|
46
|
-
if filter_registry.lookup(name, return_none=True):
|
|
47
|
-
|
|
48
|
-
def proc(context, data, *args, **kwargs):
|
|
49
|
-
filter = filter_registry.create(name, *args, **kwargs)
|
|
50
|
-
filter.context = context
|
|
51
|
-
# filter = filter_registry.create(context, name, *args, **kwargs)
|
|
52
|
-
return filter.forward(data)
|
|
53
|
-
|
|
54
|
-
return proc
|
|
55
|
-
|
|
56
|
-
if kind == "sources":
|
|
57
|
-
if source_registry.lookup(name, return_none=True):
|
|
58
|
-
|
|
59
|
-
def proc(context, data, *args, **kwargs):
|
|
60
|
-
source = source_registry.create(name, *args, **kwargs)
|
|
61
|
-
# source = source_registry.create(context, name, *args, **kwargs)
|
|
62
|
-
return source.forward(data)
|
|
63
|
-
|
|
64
|
-
return proc
|
|
65
|
-
|
|
66
|
-
raise ValueError(f"Unknown {kind} '{name}'")
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
#
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import earthkit.data as ekd
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def execute(context, input, **kwargs):
|
|
15
|
-
# Useful to create a pipeline that returns an empty result
|
|
16
|
-
# So we can reference an earlier step in a function like 'constants'
|
|
17
|
-
return ekd.from_source("empty")
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from collections import defaultdict
|
|
12
|
-
|
|
13
|
-
from earthkit.data.indexing.fieldlist import FieldArray
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class NewDataField:
|
|
17
|
-
def __init__(self, field, data, new_name):
|
|
18
|
-
self.field = field
|
|
19
|
-
self.data = data
|
|
20
|
-
self.new_name = new_name
|
|
21
|
-
|
|
22
|
-
def to_numpy(self, *args, **kwargs):
|
|
23
|
-
return self.data
|
|
24
|
-
|
|
25
|
-
def metadata(self, key=None, **kwargs):
|
|
26
|
-
if key is None:
|
|
27
|
-
return self.field.metadata(**kwargs)
|
|
28
|
-
|
|
29
|
-
value = self.field.metadata(key, **kwargs)
|
|
30
|
-
if key == "param":
|
|
31
|
-
return self.new_name
|
|
32
|
-
return value
|
|
33
|
-
|
|
34
|
-
def __getattr__(self, name):
|
|
35
|
-
return getattr(self.field, name)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def execute(context, input, orog, z="z"):
|
|
39
|
-
"""Convert orography [m] to z (geopotential height)"""
|
|
40
|
-
result = FieldArray()
|
|
41
|
-
|
|
42
|
-
processed_fields = defaultdict(dict)
|
|
43
|
-
|
|
44
|
-
for f in input:
|
|
45
|
-
key = f.metadata(namespace="mars")
|
|
46
|
-
param = key.pop("param")
|
|
47
|
-
if param == orog:
|
|
48
|
-
key = tuple(key.items())
|
|
49
|
-
|
|
50
|
-
if param in processed_fields[key]:
|
|
51
|
-
raise ValueError(f"Duplicate field {param} for {key}")
|
|
52
|
-
|
|
53
|
-
output = f.to_numpy(flatten=True) * 9.80665
|
|
54
|
-
result.append(NewDataField(f, output, z))
|
|
55
|
-
else:
|
|
56
|
-
result.append(f)
|
|
57
|
-
|
|
58
|
-
return result
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import re
|
|
12
|
-
|
|
13
|
-
from earthkit.data.indexing.fieldlist import FieldArray
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class RenamedFieldMapping:
|
|
17
|
-
"""Rename a field based on the value of another field.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
field (Field): The field to be renamed.
|
|
21
|
-
what (str): The name of the field that will be used to rename the field.
|
|
22
|
-
renaming (dict): A dictionary mapping the values of 'what' to the new names.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def __init__(self, field, what, renaming):
|
|
26
|
-
self.field = field
|
|
27
|
-
self.what = what
|
|
28
|
-
self.renaming = {}
|
|
29
|
-
for k, v in renaming.items():
|
|
30
|
-
self.renaming[k] = {str(a): str(b) for a, b in v.items()}
|
|
31
|
-
|
|
32
|
-
def metadata(self, key=None, **kwargs):
|
|
33
|
-
if key is None:
|
|
34
|
-
return self.field.metadata(**kwargs)
|
|
35
|
-
|
|
36
|
-
value = self.field.metadata(key, **kwargs)
|
|
37
|
-
if key == self.what:
|
|
38
|
-
return self.renaming.get(self.what, {}).get(value, value)
|
|
39
|
-
|
|
40
|
-
return value
|
|
41
|
-
|
|
42
|
-
def __getattr__(self, name):
|
|
43
|
-
return getattr(self.field, name)
|
|
44
|
-
|
|
45
|
-
def __repr__(self) -> str:
|
|
46
|
-
return repr(self.field)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class RenamedFieldFormat:
|
|
50
|
-
"""Rename a field based on a format string.
|
|
51
|
-
|
|
52
|
-
Args:
|
|
53
|
-
format (str): A string that defines the new name of the field.
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
def __init__(self, field, what, format):
|
|
57
|
-
self.field = field
|
|
58
|
-
self.what = what
|
|
59
|
-
self.format = format
|
|
60
|
-
self.bits = re.findall(r"{(\w+)}", format)
|
|
61
|
-
|
|
62
|
-
def metadata(self, *args, **kwargs):
|
|
63
|
-
value = self.field.metadata(*args, **kwargs)
|
|
64
|
-
if args:
|
|
65
|
-
assert len(args) == 1
|
|
66
|
-
if args[0] == self.what:
|
|
67
|
-
bits = {b: self.field.metadata(b, **kwargs) for b in self.bits}
|
|
68
|
-
return self.format.format(**bits)
|
|
69
|
-
return value
|
|
70
|
-
|
|
71
|
-
def __getattr__(self, name):
|
|
72
|
-
return getattr(self.field, name)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def execute(context, input, what="param", **kwargs):
|
|
76
|
-
if what in kwargs and isinstance(kwargs[what], str):
|
|
77
|
-
return FieldArray([RenamedFieldFormat(fs, what, kwargs[what]) for fs in input])
|
|
78
|
-
|
|
79
|
-
return FieldArray([RenamedFieldMapping(fs, what, kwargs) for fs in input])
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from collections import defaultdict
|
|
12
|
-
|
|
13
|
-
import numpy as np
|
|
14
|
-
from earthkit.data.indexing.fieldlist import FieldArray
|
|
15
|
-
from earthkit.meteo.wind.array import polar_to_xy
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class NewDataField:
|
|
19
|
-
def __init__(self, field, data, new_name):
|
|
20
|
-
self.field = field
|
|
21
|
-
self.data = data
|
|
22
|
-
self.new_name = new_name
|
|
23
|
-
|
|
24
|
-
def to_numpy(self, *args, **kwargs):
|
|
25
|
-
return self.data
|
|
26
|
-
|
|
27
|
-
def metadata(self, key=None, **kwargs):
|
|
28
|
-
if key is None:
|
|
29
|
-
return self.field.metadata(**kwargs)
|
|
30
|
-
|
|
31
|
-
value = self.field.metadata(key, **kwargs)
|
|
32
|
-
if key == "param":
|
|
33
|
-
return self.new_name
|
|
34
|
-
return value
|
|
35
|
-
|
|
36
|
-
def __getattr__(self, name):
|
|
37
|
-
return getattr(self.field, name)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def execute(context, input, wind_speed, wind_dir, u_component="u", v_component="v", in_radians=False):
|
|
41
|
-
|
|
42
|
-
result = FieldArray()
|
|
43
|
-
|
|
44
|
-
wind_params = (wind_speed, wind_dir)
|
|
45
|
-
wind_pairs = defaultdict(dict)
|
|
46
|
-
|
|
47
|
-
for f in input:
|
|
48
|
-
key = f.metadata(namespace="mars")
|
|
49
|
-
param = key.pop("param")
|
|
50
|
-
|
|
51
|
-
if param not in wind_params:
|
|
52
|
-
result.append(f)
|
|
53
|
-
continue
|
|
54
|
-
|
|
55
|
-
key = tuple(key.items())
|
|
56
|
-
|
|
57
|
-
if param in wind_pairs[key]:
|
|
58
|
-
raise ValueError(f"Duplicate wind component {param} for {key}")
|
|
59
|
-
|
|
60
|
-
wind_pairs[key][param] = f
|
|
61
|
-
|
|
62
|
-
for _, pairs in wind_pairs.items():
|
|
63
|
-
if len(pairs) != 2:
|
|
64
|
-
raise ValueError("Missing wind component")
|
|
65
|
-
|
|
66
|
-
magnitude = pairs[wind_speed]
|
|
67
|
-
direction = pairs[wind_dir]
|
|
68
|
-
|
|
69
|
-
# assert speed.grid_mapping == dir.grid_mapping
|
|
70
|
-
if in_radians:
|
|
71
|
-
direction = np.rad2deg(direction)
|
|
72
|
-
|
|
73
|
-
u, v = polar_to_xy(magnitude.to_numpy(flatten=True), direction.to_numpy(flatten=True))
|
|
74
|
-
|
|
75
|
-
result.append(NewDataField(magnitude, u, u_component))
|
|
76
|
-
result.append(NewDataField(direction, v, v_component))
|
|
77
|
-
|
|
78
|
-
return result
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from collections import defaultdict
|
|
12
|
-
|
|
13
|
-
import numpy as np
|
|
14
|
-
from earthkit.data.indexing.fieldlist import FieldArray
|
|
15
|
-
from earthkit.meteo.wind.array import xy_to_polar
|
|
16
|
-
|
|
17
|
-
from anemoi.datasets.create.functions.filters.speeddir_to_uv import NewDataField
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def execute(context, input, u_component, v_component, wind_speed, wind_dir, in_radians=False):
|
|
21
|
-
result = FieldArray()
|
|
22
|
-
|
|
23
|
-
wind_params = (u_component, v_component)
|
|
24
|
-
wind_pairs = defaultdict(dict)
|
|
25
|
-
|
|
26
|
-
for f in input:
|
|
27
|
-
key = f.metadata(namespace="mars")
|
|
28
|
-
param = key.pop("param")
|
|
29
|
-
|
|
30
|
-
if param not in wind_params:
|
|
31
|
-
result.append(f)
|
|
32
|
-
continue
|
|
33
|
-
|
|
34
|
-
key = tuple(key.items())
|
|
35
|
-
|
|
36
|
-
if param in wind_pairs[key]:
|
|
37
|
-
raise ValueError(f"Duplicate wind component {param} for {key}")
|
|
38
|
-
|
|
39
|
-
wind_pairs[key][param] = f
|
|
40
|
-
|
|
41
|
-
for _, pairs in wind_pairs.items():
|
|
42
|
-
if len(pairs) != 2:
|
|
43
|
-
raise ValueError("Missing wind component")
|
|
44
|
-
|
|
45
|
-
u = pairs[u_component]
|
|
46
|
-
v = pairs[v_component]
|
|
47
|
-
|
|
48
|
-
# assert speed.grid_mapping == dir.grid_mapping
|
|
49
|
-
magnitude, direction = xy_to_polar(u.to_numpy(flatten=True), v.to_numpy(flatten=True))
|
|
50
|
-
if in_radians:
|
|
51
|
-
direction = np.deg2rad(direction)
|
|
52
|
-
|
|
53
|
-
result.append(NewDataField(u, magnitude, wind_speed))
|
|
54
|
-
result.append(NewDataField(v, direction, wind_dir))
|
|
55
|
-
|
|
56
|
-
return result
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import earthkit.data as ekd
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def execute(context, dates, **kwargs):
|
|
15
|
-
return ekd.from_source("empty")
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import glob
|
|
12
|
-
import logging
|
|
13
|
-
|
|
14
|
-
from earthkit.data import from_source
|
|
15
|
-
from earthkit.data.indexing.fieldlist import FieldArray
|
|
16
|
-
from earthkit.data.utils.patterns import Pattern
|
|
17
|
-
|
|
18
|
-
LOG = logging.getLogger(__name__)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _load(context, name, record):
|
|
22
|
-
ds = None
|
|
23
|
-
|
|
24
|
-
param = record["param"]
|
|
25
|
-
|
|
26
|
-
if "path" in record:
|
|
27
|
-
context.info(f"Using {name} from {record['path']} (param={param})")
|
|
28
|
-
ds = from_source("file", record["path"])
|
|
29
|
-
|
|
30
|
-
if "url" in record:
|
|
31
|
-
context.info(f"Using {name} from {record['url']} (param={param})")
|
|
32
|
-
ds = from_source("url", record["url"])
|
|
33
|
-
|
|
34
|
-
ds = ds.sel(param=param)
|
|
35
|
-
|
|
36
|
-
assert len(ds) == 1, f"{name} {param}, expected one field, got {len(ds)}"
|
|
37
|
-
ds = ds[0]
|
|
38
|
-
|
|
39
|
-
return ds.to_numpy(flatten=True), ds.metadata("uuidOfHGrid")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class Geography:
|
|
43
|
-
"""This class retrieve the latitudes and longitudes of unstructured grids,
|
|
44
|
-
and checks if the fields are compatible with the grid.
|
|
45
|
-
"""
|
|
46
|
-
|
|
47
|
-
def __init__(self, context, latitudes, longitudes):
|
|
48
|
-
|
|
49
|
-
latitudes, uuidOfHGrid_lat = _load(context, "latitudes", latitudes)
|
|
50
|
-
longitudes, uuidOfHGrid_lon = _load(context, "longitudes", longitudes)
|
|
51
|
-
|
|
52
|
-
assert (
|
|
53
|
-
uuidOfHGrid_lat == uuidOfHGrid_lon
|
|
54
|
-
), f"uuidOfHGrid mismatch: lat={uuidOfHGrid_lat} != lon={uuidOfHGrid_lon}"
|
|
55
|
-
|
|
56
|
-
context.info(f"Latitudes: {len(latitudes)}, Longitudes: {len(longitudes)}")
|
|
57
|
-
assert len(latitudes) == len(longitudes)
|
|
58
|
-
|
|
59
|
-
self.uuidOfHGrid = uuidOfHGrid_lat
|
|
60
|
-
self.latitudes = latitudes
|
|
61
|
-
self.longitudes = longitudes
|
|
62
|
-
self.first = True
|
|
63
|
-
|
|
64
|
-
def check(self, field):
|
|
65
|
-
if self.first:
|
|
66
|
-
# We only check the first field, for performance reasons
|
|
67
|
-
assert (
|
|
68
|
-
field.metadata("uuidOfHGrid") == self.uuidOfHGrid
|
|
69
|
-
), f"uuidOfHGrid mismatch: {field.metadata('uuidOfHGrid')} != {self.uuidOfHGrid}"
|
|
70
|
-
self.first = False
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class AddGrid:
|
|
74
|
-
"""An earth-kit.data.Field wrapper that adds grid information."""
|
|
75
|
-
|
|
76
|
-
def __init__(self, field, geography):
|
|
77
|
-
self._field = field
|
|
78
|
-
|
|
79
|
-
geography.check(field)
|
|
80
|
-
|
|
81
|
-
self._latitudes = geography.latitudes
|
|
82
|
-
self._longitudes = geography.longitudes
|
|
83
|
-
|
|
84
|
-
def __getattr__(self, name):
|
|
85
|
-
return getattr(self._field, name)
|
|
86
|
-
|
|
87
|
-
def __repr__(self) -> str:
|
|
88
|
-
return repr(self._field)
|
|
89
|
-
|
|
90
|
-
def grid_points(self):
|
|
91
|
-
return self._latitudes, self._longitudes
|
|
92
|
-
|
|
93
|
-
@property
|
|
94
|
-
def resolution(self):
|
|
95
|
-
return "unknown"
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def check(ds, paths, **kwargs):
|
|
99
|
-
count = 1
|
|
100
|
-
for k, v in kwargs.items():
|
|
101
|
-
if isinstance(v, (tuple, list)):
|
|
102
|
-
count *= len(v)
|
|
103
|
-
|
|
104
|
-
if len(ds) != count:
|
|
105
|
-
raise ValueError(f"Expected {count} fields, got {len(ds)} (kwargs={kwargs}, paths={paths})")
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _expand(paths):
|
|
109
|
-
for path in paths:
|
|
110
|
-
cnt = 0
|
|
111
|
-
for p in glob.glob(path):
|
|
112
|
-
yield p
|
|
113
|
-
cnt += 1
|
|
114
|
-
if cnt == 0:
|
|
115
|
-
yield path
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
def execute(context, dates, path, latitudes=None, longitudes=None, *args, **kwargs):
|
|
119
|
-
given_paths = path if isinstance(path, list) else [path]
|
|
120
|
-
|
|
121
|
-
geography = None
|
|
122
|
-
if latitudes is not None and longitudes is not None:
|
|
123
|
-
geography = Geography(context, latitudes, longitudes)
|
|
124
|
-
|
|
125
|
-
ds = from_source("empty")
|
|
126
|
-
dates = [d.isoformat() for d in dates]
|
|
127
|
-
|
|
128
|
-
for path in given_paths:
|
|
129
|
-
paths = Pattern(path, ignore_missing_keys=True).substitute(*args, date=dates, **kwargs)
|
|
130
|
-
|
|
131
|
-
for name in ("grid", "area", "rotation", "frame", "resol", "bitmap"):
|
|
132
|
-
if name in kwargs:
|
|
133
|
-
raise ValueError(f"MARS interpolation parameter '{name}' not supported")
|
|
134
|
-
|
|
135
|
-
for path in _expand(paths):
|
|
136
|
-
context.trace("📁", "PATH", path)
|
|
137
|
-
s = from_source("file", path)
|
|
138
|
-
s = s.sel(valid_datetime=dates, **kwargs)
|
|
139
|
-
ds = ds + s
|
|
140
|
-
|
|
141
|
-
if kwargs and not context.partial_ok:
|
|
142
|
-
check(ds, given_paths, valid_datetime=dates, **kwargs)
|
|
143
|
-
|
|
144
|
-
if geography is not None:
|
|
145
|
-
ds = FieldArray([AddGrid(_, geography) for _ in ds])
|
|
146
|
-
|
|
147
|
-
if len(ds) == 0:
|
|
148
|
-
LOG.warning(f"No fields found for {dates} in {given_paths} (kwargs={kwargs})")
|
|
149
|
-
|
|
150
|
-
return ds
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from .xarray import load_many
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def execute(context, dates, path, *args, **kwargs):
|
|
15
|
-
return load_many("📁", context, dates, path, *args, **kwargs)
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from .xarray import load_many
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def execute(context, dates, url, *args, **kwargs):
|
|
15
|
-
return load_many("🌐", context, dates, url, *args, **kwargs)
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# (C) Copyright 2024 Anemoi contributors.
|
|
2
|
-
#
|
|
3
|
-
# This software is licensed under the terms of the Apache Licence Version 2.0
|
|
4
|
-
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
|
|
5
|
-
#
|
|
6
|
-
# In applying this licence, ECMWF does not waive the privileges and immunities
|
|
7
|
-
# granted to it by virtue of its status as an intergovernmental organisation
|
|
8
|
-
# nor does it submit to any jurisdiction.
|
|
9
|
-
|
|
10
|
-
from copy import deepcopy
|
|
11
|
-
|
|
12
|
-
from anemoi.datasets.compute.recentre import recentre as _recentre
|
|
13
|
-
|
|
14
|
-
from .mars import mars
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def to_list(x):
|
|
18
|
-
if isinstance(x, (list, tuple)):
|
|
19
|
-
return x
|
|
20
|
-
if isinstance(x, str):
|
|
21
|
-
return x.split("/")
|
|
22
|
-
return [x]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def normalise_number(number):
|
|
26
|
-
number = to_list(number)
|
|
27
|
-
|
|
28
|
-
if len(number) > 4 and (number[1] == "to" and number[3] == "by"):
|
|
29
|
-
return list(range(int(number[0]), int(number[2]) + 1, int(number[4])))
|
|
30
|
-
|
|
31
|
-
if len(number) > 2 and number[1] == "to":
|
|
32
|
-
return list(range(int(number[0]), int(number[2]) + 1))
|
|
33
|
-
|
|
34
|
-
return number
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def normalise_request(request):
|
|
38
|
-
request = deepcopy(request)
|
|
39
|
-
if "number" in request:
|
|
40
|
-
request["number"] = normalise_number(request["number"])
|
|
41
|
-
if "time" in request:
|
|
42
|
-
request["time"] = to_list(request["time"])
|
|
43
|
-
request["param"] = to_list(request["param"])
|
|
44
|
-
return request
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def load_if_needed(context, dates, dict_or_dataset):
|
|
48
|
-
if isinstance(dict_or_dataset, dict):
|
|
49
|
-
dict_or_dataset = normalise_request(dict_or_dataset)
|
|
50
|
-
dict_or_dataset = mars(context, dates, dict_or_dataset)
|
|
51
|
-
return dict_or_dataset
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def recentre(context, dates, members, centre, alpha=1.0, remapping={}, patches={}):
|
|
55
|
-
members = load_if_needed(context, dates, members)
|
|
56
|
-
centre = load_if_needed(context, dates, centre)
|
|
57
|
-
return _recentre(members=members, centre=centre, alpha=alpha)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
execute = recentre
|