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.
Files changed (155) hide show
  1. anemoi/datasets/__init__.py +4 -1
  2. anemoi/datasets/__main__.py +12 -2
  3. anemoi/datasets/_version.py +9 -4
  4. anemoi/datasets/commands/cleanup.py +17 -2
  5. anemoi/datasets/commands/compare.py +18 -2
  6. anemoi/datasets/commands/copy.py +196 -14
  7. anemoi/datasets/commands/create.py +50 -7
  8. anemoi/datasets/commands/finalise-additions.py +17 -2
  9. anemoi/datasets/commands/finalise.py +17 -2
  10. anemoi/datasets/commands/init-additions.py +17 -2
  11. anemoi/datasets/commands/init.py +16 -2
  12. anemoi/datasets/commands/inspect.py +283 -62
  13. anemoi/datasets/commands/load-additions.py +16 -2
  14. anemoi/datasets/commands/load.py +16 -2
  15. anemoi/datasets/commands/patch.py +17 -2
  16. anemoi/datasets/commands/publish.py +17 -2
  17. anemoi/datasets/commands/scan.py +31 -3
  18. anemoi/datasets/compute/recentre.py +47 -11
  19. anemoi/datasets/create/__init__.py +612 -85
  20. anemoi/datasets/create/check.py +142 -20
  21. anemoi/datasets/create/chunks.py +64 -4
  22. anemoi/datasets/create/config.py +185 -21
  23. anemoi/datasets/create/filter.py +50 -0
  24. anemoi/datasets/create/filters/__init__.py +33 -0
  25. anemoi/datasets/create/filters/empty.py +37 -0
  26. anemoi/datasets/create/filters/legacy.py +93 -0
  27. anemoi/datasets/create/filters/noop.py +37 -0
  28. anemoi/datasets/create/filters/orog_to_z.py +58 -0
  29. anemoi/datasets/create/{functions/filters → filters}/pressure_level_relative_humidity_to_specific_humidity.py +33 -10
  30. anemoi/datasets/create/{functions/filters → filters}/pressure_level_specific_humidity_to_relative_humidity.py +32 -8
  31. anemoi/datasets/create/filters/rename.py +205 -0
  32. anemoi/datasets/create/{functions/filters → filters}/rotate_winds.py +43 -28
  33. anemoi/datasets/create/{functions/filters → filters}/single_level_dewpoint_to_relative_humidity.py +32 -9
  34. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_dewpoint.py +33 -9
  35. anemoi/datasets/create/{functions/filters → filters}/single_level_relative_humidity_to_specific_humidity.py +55 -7
  36. anemoi/datasets/create/{functions/filters → filters}/single_level_specific_humidity_to_relative_humidity.py +98 -37
  37. anemoi/datasets/create/filters/speeddir_to_uv.py +95 -0
  38. anemoi/datasets/create/{functions/filters → filters}/sum.py +24 -27
  39. anemoi/datasets/create/filters/transform.py +53 -0
  40. anemoi/datasets/create/{functions/filters → filters}/unrotate_winds.py +27 -18
  41. anemoi/datasets/create/filters/uv_to_speeddir.py +94 -0
  42. anemoi/datasets/create/{functions/filters → filters}/wz_to_w.py +51 -33
  43. anemoi/datasets/create/input/__init__.py +76 -5
  44. anemoi/datasets/create/input/action.py +149 -13
  45. anemoi/datasets/create/input/concat.py +81 -10
  46. anemoi/datasets/create/input/context.py +39 -4
  47. anemoi/datasets/create/input/data_sources.py +72 -6
  48. anemoi/datasets/create/input/empty.py +21 -3
  49. anemoi/datasets/create/input/filter.py +60 -12
  50. anemoi/datasets/create/input/function.py +154 -37
  51. anemoi/datasets/create/input/join.py +86 -14
  52. anemoi/datasets/create/input/misc.py +67 -17
  53. anemoi/datasets/create/input/pipe.py +33 -6
  54. anemoi/datasets/create/input/repeated_dates.py +189 -41
  55. anemoi/datasets/create/input/result.py +202 -87
  56. anemoi/datasets/create/input/step.py +119 -22
  57. anemoi/datasets/create/input/template.py +100 -13
  58. anemoi/datasets/create/input/trace.py +62 -7
  59. anemoi/datasets/create/patch.py +52 -4
  60. anemoi/datasets/create/persistent.py +134 -17
  61. anemoi/datasets/create/size.py +15 -1
  62. anemoi/datasets/create/source.py +51 -0
  63. anemoi/datasets/create/sources/__init__.py +36 -0
  64. anemoi/datasets/create/{functions/sources → sources}/accumulations.py +296 -30
  65. anemoi/datasets/create/{functions/sources → sources}/constants.py +27 -2
  66. anemoi/datasets/create/{functions/sources → sources}/eccc_fstd.py +7 -3
  67. anemoi/datasets/create/sources/empty.py +37 -0
  68. anemoi/datasets/create/{functions/sources → sources}/forcings.py +25 -1
  69. anemoi/datasets/create/sources/grib.py +297 -0
  70. anemoi/datasets/create/{functions/sources → sources}/hindcasts.py +38 -4
  71. anemoi/datasets/create/sources/legacy.py +93 -0
  72. anemoi/datasets/create/{functions/sources → sources}/mars.py +168 -20
  73. anemoi/datasets/create/sources/netcdf.py +42 -0
  74. anemoi/datasets/create/sources/opendap.py +43 -0
  75. anemoi/datasets/create/{functions/sources/__init__.py → sources/patterns.py} +35 -4
  76. anemoi/datasets/create/sources/recentre.py +150 -0
  77. anemoi/datasets/create/{functions/sources → sources}/source.py +27 -5
  78. anemoi/datasets/create/{functions/sources → sources}/tendencies.py +64 -7
  79. anemoi/datasets/create/sources/xarray.py +92 -0
  80. anemoi/datasets/create/sources/xarray_kerchunk.py +36 -0
  81. anemoi/datasets/create/sources/xarray_support/README.md +1 -0
  82. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/__init__.py +109 -8
  83. anemoi/datasets/create/sources/xarray_support/coordinates.py +442 -0
  84. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/field.py +94 -16
  85. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/fieldlist.py +90 -25
  86. anemoi/datasets/create/sources/xarray_support/flavour.py +1036 -0
  87. anemoi/datasets/create/{functions/sources/xarray → sources/xarray_support}/grid.py +92 -31
  88. anemoi/datasets/create/sources/xarray_support/metadata.py +395 -0
  89. anemoi/datasets/create/sources/xarray_support/patch.py +91 -0
  90. anemoi/datasets/create/sources/xarray_support/time.py +391 -0
  91. anemoi/datasets/create/sources/xarray_support/variable.py +331 -0
  92. anemoi/datasets/create/sources/xarray_zarr.py +41 -0
  93. anemoi/datasets/create/{functions/sources → sources}/zenodo.py +34 -5
  94. anemoi/datasets/create/statistics/__init__.py +233 -44
  95. anemoi/datasets/create/statistics/summary.py +52 -6
  96. anemoi/datasets/create/testing.py +76 -0
  97. anemoi/datasets/create/{functions/filters/noop.py → typing.py} +6 -3
  98. anemoi/datasets/create/utils.py +97 -6
  99. anemoi/datasets/create/writer.py +26 -4
  100. anemoi/datasets/create/zarr.py +170 -23
  101. anemoi/datasets/data/__init__.py +51 -4
  102. anemoi/datasets/data/complement.py +191 -40
  103. anemoi/datasets/data/concat.py +141 -16
  104. anemoi/datasets/data/dataset.py +552 -61
  105. anemoi/datasets/data/debug.py +197 -26
  106. anemoi/datasets/data/ensemble.py +93 -8
  107. anemoi/datasets/data/fill_missing.py +165 -18
  108. anemoi/datasets/data/forwards.py +428 -56
  109. anemoi/datasets/data/grids.py +323 -97
  110. anemoi/datasets/data/indexing.py +112 -19
  111. anemoi/datasets/data/interpolate.py +92 -12
  112. anemoi/datasets/data/join.py +158 -19
  113. anemoi/datasets/data/masked.py +129 -15
  114. anemoi/datasets/data/merge.py +137 -23
  115. anemoi/datasets/data/misc.py +172 -16
  116. anemoi/datasets/data/missing.py +233 -29
  117. anemoi/datasets/data/rescale.py +111 -10
  118. anemoi/datasets/data/select.py +168 -26
  119. anemoi/datasets/data/statistics.py +67 -6
  120. anemoi/datasets/data/stores.py +149 -64
  121. anemoi/datasets/data/subset.py +159 -25
  122. anemoi/datasets/data/unchecked.py +168 -57
  123. anemoi/datasets/data/xy.py +168 -25
  124. anemoi/datasets/dates/__init__.py +191 -16
  125. anemoi/datasets/dates/groups.py +189 -47
  126. anemoi/datasets/grids.py +270 -31
  127. anemoi/datasets/testing.py +28 -1
  128. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/METADATA +9 -6
  129. anemoi_datasets-0.5.17.dist-info/RECORD +137 -0
  130. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/WHEEL +1 -1
  131. anemoi/datasets/create/functions/__init__.py +0 -66
  132. anemoi/datasets/create/functions/filters/__init__.py +0 -9
  133. anemoi/datasets/create/functions/filters/empty.py +0 -17
  134. anemoi/datasets/create/functions/filters/orog_to_z.py +0 -58
  135. anemoi/datasets/create/functions/filters/rename.py +0 -79
  136. anemoi/datasets/create/functions/filters/speeddir_to_uv.py +0 -78
  137. anemoi/datasets/create/functions/filters/uv_to_speeddir.py +0 -56
  138. anemoi/datasets/create/functions/sources/empty.py +0 -15
  139. anemoi/datasets/create/functions/sources/grib.py +0 -150
  140. anemoi/datasets/create/functions/sources/netcdf.py +0 -15
  141. anemoi/datasets/create/functions/sources/opendap.py +0 -15
  142. anemoi/datasets/create/functions/sources/recentre.py +0 -60
  143. anemoi/datasets/create/functions/sources/xarray/coordinates.py +0 -255
  144. anemoi/datasets/create/functions/sources/xarray/flavour.py +0 -472
  145. anemoi/datasets/create/functions/sources/xarray/metadata.py +0 -148
  146. anemoi/datasets/create/functions/sources/xarray/patch.py +0 -44
  147. anemoi/datasets/create/functions/sources/xarray/time.py +0 -177
  148. anemoi/datasets/create/functions/sources/xarray/variable.py +0 -188
  149. anemoi/datasets/create/functions/sources/xarray_kerchunk.py +0 -42
  150. anemoi/datasets/create/functions/sources/xarray_zarr.py +0 -15
  151. anemoi/datasets/utils/fields.py +0 -47
  152. anemoi_datasets-0.5.16.dist-info/RECORD +0 -129
  153. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info}/entry_points.txt +0 -0
  154. {anemoi_datasets-0.5.16.dist-info → anemoi_datasets-0.5.17.dist-info/licenses}/LICENSE +0 -0
  155. {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