meteodata-lab 0.3.0__tar.gz

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 (50) hide show
  1. meteodata_lab-0.3.0/AUTHORS.md +13 -0
  2. meteodata_lab-0.3.0/LICENSE +19 -0
  3. meteodata_lab-0.3.0/PKG-INFO +88 -0
  4. meteodata_lab-0.3.0/README.md +53 -0
  5. meteodata_lab-0.3.0/pyproject.toml +202 -0
  6. meteodata_lab-0.3.0/src/meteodatalab/__init__.py +8 -0
  7. meteodata_lab-0.3.0/src/meteodatalab/cli.py +151 -0
  8. meteodata_lab-0.3.0/src/meteodatalab/config.py +61 -0
  9. meteodata_lab-0.3.0/src/meteodatalab/data/fdb_config_balfrin.yaml +8 -0
  10. meteodata_lab-0.3.0/src/meteodatalab/data/fdb_config_tsa.yaml +8 -0
  11. meteodata_lab-0.3.0/src/meteodatalab/data/field_mappings.yml +190 -0
  12. meteodata_lab-0.3.0/src/meteodatalab/data_cache.py +90 -0
  13. meteodata_lab-0.3.0/src/meteodatalab/data_source.py +174 -0
  14. meteodata_lab-0.3.0/src/meteodatalab/grib_decoder.py +531 -0
  15. meteodata_lab-0.3.0/src/meteodatalab/icon_grid.py +94 -0
  16. meteodata_lab-0.3.0/src/meteodatalab/mars.py +216 -0
  17. meteodata_lab-0.3.0/src/meteodatalab/mch_model_data.py +141 -0
  18. meteodata_lab-0.3.0/src/meteodatalab/metadata.py +276 -0
  19. meteodata_lab-0.3.0/src/meteodatalab/ogd_api.py +334 -0
  20. meteodata_lab-0.3.0/src/meteodatalab/operators/__init__.py +1 -0
  21. meteodata_lab-0.3.0/src/meteodatalab/operators/atmo.py +103 -0
  22. meteodata_lab-0.3.0/src/meteodatalab/operators/brn.py +74 -0
  23. meteodata_lab-0.3.0/src/meteodatalab/operators/crop.py +80 -0
  24. meteodata_lab-0.3.0/src/meteodatalab/operators/curl.py +98 -0
  25. meteodata_lab-0.3.0/src/meteodatalab/operators/destagger.py +173 -0
  26. meteodata_lab-0.3.0/src/meteodatalab/operators/diff.py +120 -0
  27. meteodata_lab-0.3.0/src/meteodatalab/operators/flexpart.py +40 -0
  28. meteodata_lab-0.3.0/src/meteodatalab/operators/gis.py +273 -0
  29. meteodata_lab-0.3.0/src/meteodatalab/operators/hzerocl.py +79 -0
  30. meteodata_lab-0.3.0/src/meteodatalab/operators/lateral_operators.py +203 -0
  31. meteodata_lab-0.3.0/src/meteodatalab/operators/omega_slope.py +80 -0
  32. meteodata_lab-0.3.0/src/meteodatalab/operators/pot_vortic.py +84 -0
  33. meteodata_lab-0.3.0/src/meteodatalab/operators/radiation.py +52 -0
  34. meteodata_lab-0.3.0/src/meteodatalab/operators/regrid.py +565 -0
  35. meteodata_lab-0.3.0/src/meteodatalab/operators/relhum.py +61 -0
  36. meteodata_lab-0.3.0/src/meteodatalab/operators/rho.py +53 -0
  37. meteodata_lab-0.3.0/src/meteodatalab/operators/support_operators.py +123 -0
  38. meteodata_lab-0.3.0/src/meteodatalab/operators/theta.py +33 -0
  39. meteodata_lab-0.3.0/src/meteodatalab/operators/thetav.py +35 -0
  40. meteodata_lab-0.3.0/src/meteodatalab/operators/time_operators.py +184 -0
  41. meteodata_lab-0.3.0/src/meteodatalab/operators/total_diff.py +51 -0
  42. meteodata_lab-0.3.0/src/meteodatalab/operators/vertical_interpolation.py +399 -0
  43. meteodata_lab-0.3.0/src/meteodatalab/operators/vertical_reduction.py +300 -0
  44. meteodata_lab-0.3.0/src/meteodatalab/operators/wind.py +91 -0
  45. meteodata_lab-0.3.0/src/meteodatalab/physical_constants.py +30 -0
  46. meteodata_lab-0.3.0/src/meteodatalab/product.py +120 -0
  47. meteodata_lab-0.3.0/src/meteodatalab/products/ninjo_k2th.py +130 -0
  48. meteodata_lab-0.3.0/src/meteodatalab/py.typed +0 -0
  49. meteodata_lab-0.3.0/src/meteodatalab/tasking.py +23 -0
  50. meteodata_lab-0.3.0/src/meteodatalab/util.py +35 -0
@@ -0,0 +1,13 @@
1
+ # Credits
2
+
3
+ ## Development Lead
4
+
5
+ Carlos Osuna <carlos.osuna@meteoswiss.ch>
6
+
7
+ ## Contributors
8
+
9
+ - Petra Baumann <petra.baumann@meteoswiss.ch>
10
+ - Nina Burgdorfer <nina.burgdorfer@meteoswiss.ch>
11
+ - Victoria Cherkas <victoria.cherkas@meteoswiss.ch>
12
+ - Christian Kanesan <christian.kanesan@meteoswiss.ch>
13
+ - Tobias Wicky <tobias.wicky@meteoswiss.ch>
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2023 Federal Office of Meteorlogy and Climatology MeteoSwiss
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.3
2
+ Name: meteodata-lab
3
+ Version: 0.3.0
4
+ Summary: A data post-processing framework on the basis of xarray.
5
+ Keywords: Icon,Data Processing
6
+ Author: Carlos Osuna
7
+ Author-email: carlos.osuna@meteoswiss.ch
8
+ Requires-Python: >=3.10,<3.13
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Natural Language :: English
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Provides-Extra: polytope
17
+ Provides-Extra: regrid
18
+ Requires-Dist: click (>=8.1.7,<9.0.0)
19
+ Requires-Dist: earthkit-data[covjsonkit] (>=0.11,<1)
20
+ Requires-Dist: eccodes (>=2.38,<2.39)
21
+ Requires-Dist: eccodes-cosmo-resources-python (>=2.38,<2.39)
22
+ Requires-Dist: numpy (>=1.26.4,<2.0.0)
23
+ Requires-Dist: polytope-client (>=0.7.4,<0.8.0) ; extra == "polytope"
24
+ Requires-Dist: pydantic
25
+ Requires-Dist: pyproj (>=3.6.1,<4.0.0) ; extra == "regrid"
26
+ Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
27
+ Requires-Dist: rasterio (>=1.3.10,<2.0.0) ; extra == "regrid"
28
+ Requires-Dist: scipy (>=1.13,<2.0) ; extra == "regrid"
29
+ Requires-Dist: setuptools
30
+ Requires-Dist: xarray (>=2024)
31
+ Project-URL: documentation, https://meteoswiss.github.io/meteodata-lab/
32
+ Project-URL: source, https://github.com/MeteoSwiss/meteodata-lab
33
+ Description-Content-Type: text/markdown
34
+
35
+ <p align="center">
36
+ <picture>
37
+ <img src="meteodata_lab_logo_with_name-optimized.gif" height="160" alt="meteodata-lab animated logo showing a cloud partially hiding the sun in a shape of a gear">
38
+ </picture>
39
+ </p>
40
+
41
+ <p align="center">
42
+ <a href="https://pypi.org/project/meteodata-lab/">
43
+ <img src="https://img.shields.io/pypi/v/meteodata-lab.svg?color=ff69b4" alt="PyPI version">
44
+ </a>
45
+ <a href="https://github.com/meteoswiss/meteodata-lab/releases">
46
+ <img src="https://img.shields.io/github/v/release/meteoswiss/meteodata-lab?color=purple&label=Release" alt="Latest Release">
47
+ </a>
48
+ <a href="https://opensource.org/licenses/mit">
49
+ <img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence">
50
+ </a>
51
+ </p>
52
+
53
+ <p align="center">
54
+ <a href="#installation">Installation</a> •
55
+ <a href="https://meteoswiss.github.io/meteodata-lab/">Documentation</a>
56
+ </p>
57
+
58
+ > [!WARNING]
59
+ > This project is in BETA and under active development. Interfaces and functionality are subject to change.
60
+
61
+ **Meteodata-lab** is a NumPy/Xarray-based Python library for processing and analyzing gridded meteorological data. It supports GRIB (read/write) and is tailored to common workflows that require data interpolation, regridding to custom grids (e.g., Swiss grid or rotated lat/lon), and the computation of advanced meteorological fields. One of the key features of meteodata-lab is its use of operators that ensure the integrity of GRIB metadata is maintained throughout processing, allowing for consistent writing back to GRIB format.
62
+
63
+ ## Installation
64
+
65
+
66
+ ### For Users
67
+
68
+ To install the latest release from PyPI:
69
+
70
+ ```bash
71
+ pip install meteodata-lab
72
+ ```
73
+ #### Optional Extras
74
+ To install optional extras:
75
+ ```bash
76
+ pip install "meteodata-lab[polytope,regrid]"
77
+ ```
78
+ **Note**: The `fdb` extra is currently disabled because its dependency `pyfdb` is not available on PyPI. As an alternative the development setup can be used.
79
+
80
+ ### For Development
81
+ To set up the project for local development, clone the repository and use the provided Poetry setup script:
82
+ ```bash
83
+ git clone git@github.com:MeteoSwiss/meteodata-lab.git
84
+ cd meteodata-lab
85
+ ./scripts/setup_poetry.sh
86
+ ```
87
+ This will install Poetry (if not already available), set up the virtual environment, and install all dependencies with extras.
88
+
@@ -0,0 +1,53 @@
1
+ <p align="center">
2
+ <picture>
3
+ <img src="meteodata_lab_logo_with_name-optimized.gif" height="160" alt="meteodata-lab animated logo showing a cloud partially hiding the sun in a shape of a gear">
4
+ </picture>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://pypi.org/project/meteodata-lab/">
9
+ <img src="https://img.shields.io/pypi/v/meteodata-lab.svg?color=ff69b4" alt="PyPI version">
10
+ </a>
11
+ <a href="https://github.com/meteoswiss/meteodata-lab/releases">
12
+ <img src="https://img.shields.io/github/v/release/meteoswiss/meteodata-lab?color=purple&label=Release" alt="Latest Release">
13
+ </a>
14
+ <a href="https://opensource.org/licenses/mit">
15
+ <img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence">
16
+ </a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <a href="#installation">Installation</a> •
21
+ <a href="https://meteoswiss.github.io/meteodata-lab/">Documentation</a>
22
+ </p>
23
+
24
+ > [!WARNING]
25
+ > This project is in BETA and under active development. Interfaces and functionality are subject to change.
26
+
27
+ **Meteodata-lab** is a NumPy/Xarray-based Python library for processing and analyzing gridded meteorological data. It supports GRIB (read/write) and is tailored to common workflows that require data interpolation, regridding to custom grids (e.g., Swiss grid or rotated lat/lon), and the computation of advanced meteorological fields. One of the key features of meteodata-lab is its use of operators that ensure the integrity of GRIB metadata is maintained throughout processing, allowing for consistent writing back to GRIB format.
28
+
29
+ ## Installation
30
+
31
+
32
+ ### For Users
33
+
34
+ To install the latest release from PyPI:
35
+
36
+ ```bash
37
+ pip install meteodata-lab
38
+ ```
39
+ #### Optional Extras
40
+ To install optional extras:
41
+ ```bash
42
+ pip install "meteodata-lab[polytope,regrid]"
43
+ ```
44
+ **Note**: The `fdb` extra is currently disabled because its dependency `pyfdb` is not available on PyPI. As an alternative the development setup can be used.
45
+
46
+ ### For Development
47
+ To set up the project for local development, clone the repository and use the provided Poetry setup script:
48
+ ```bash
49
+ git clone git@github.com:MeteoSwiss/meteodata-lab.git
50
+ cd meteodata-lab
51
+ ./scripts/setup_poetry.sh
52
+ ```
53
+ This will install Poetry (if not already available), set up the virtual environment, and install all dependencies with extras.
@@ -0,0 +1,202 @@
1
+ [build-system]
2
+ requires = ["poetry-core>=1.0.0"]
3
+ build-backend = "poetry.core.masonry.api"
4
+
5
+ [tool.poetry]
6
+ name = "meteodata-lab"
7
+ version = "0.3.0"
8
+ description = "A data post-processing framework on the basis of xarray."
9
+ readme = "README.md"
10
+ keywords = ["Icon", "Data Processing"]
11
+ classifiers = [
12
+ "Development Status :: 2 - Pre-Alpha",
13
+ "Intended Audience :: Developers",
14
+ "Natural Language :: English",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ ]
18
+ authors = [
19
+ "Carlos Osuna <carlos.osuna@meteoswiss.ch>",
20
+ "Petra Baumann <petra.baumanna@meteoswiss.ch>",
21
+ "Nina Burgdorfer <nina.burgdorfer@meteoswiss.ch>",
22
+ "Victoria Cherkas <victoria.cherkas@meteoswiss.ch>",
23
+ "Christian Kanesan <christian.kanesan@meteoswiss.ch>",
24
+ "Tobias Wicky <tobias.wicky@meteoswiss.ch>",
25
+ ]
26
+ packages = [
27
+ { include = "meteodatalab", from = "src" },
28
+ ]
29
+
30
+ [tool.poetry.dependencies]
31
+ python = ">=3.10,<3.13"
32
+ click = "^8.1.7"
33
+ earthkit-data = { version = ">=0.11,<1", extras = ["covjsonkit"] }
34
+ eccodes = "~2.38"
35
+ eccodes-cosmo-resources-python = "~2.38"
36
+ numpy = "^1.26.4"
37
+ polytope-client = { version = "^0.7.4", optional = true }
38
+ pydantic = "*"
39
+ pyproj = { version = "^3.6.1", optional = true }
40
+ pyyaml = "^6.0.1"
41
+ rasterio = { version = "^1.3.10", optional = true }
42
+ scipy = { version = "^1.13", optional = true }
43
+ setuptools = "*"
44
+ xarray = ">=2024"
45
+
46
+ [tool.poetry.extras]
47
+ # fdb = ["pyfdb"]
48
+ polytope = ["polytope-client"]
49
+ regrid = ["pyproj", "rasterio", "scipy"]
50
+
51
+ [tool.poetry.group.dev.dependencies]
52
+ black = "*"
53
+ codespell = "*"
54
+ flake8 = "*"
55
+ Flake8-pyproject = "*"
56
+ ipykernel = "*"
57
+ isort = "*"
58
+ mypy = "*"
59
+ pandas-stubs = "*"
60
+ pre-commit = "*"
61
+ pydocstyle = "*"
62
+ pyfdb = { url = "https://github.com/ecmwf/pyfdb/archive/refs/tags/0.0.3.zip" }
63
+ pytest = "*"
64
+ rstcheck = { version = "*", extras = ["sphinx"] }
65
+ types-requests = "*"
66
+ types-PyYAML = "*"
67
+
68
+ [tool.poetry.urls]
69
+ source = "https://github.com/MeteoSwiss/meteodata-lab"
70
+ documentation = "https://meteoswiss.github.io/meteodata-lab/"
71
+
72
+ [tool.poetry.scripts]
73
+ # Format: <command> = "<package>.<module>:<function>"
74
+ meteodata-lab = "meteodatalab.cli:main"
75
+
76
+ [tool.pytest.ini_options]
77
+ testpaths = ["tests"]
78
+ addopts = [
79
+ "-ra",
80
+ "--pdbcls=IPython.terminal.debugger:TerminalPdb",
81
+ "--tb=short",
82
+ # "--cov=meteodatalab",
83
+ # "--mypy",
84
+ ]
85
+ markers = [
86
+ "ifs", # marks tests that use IFS grib definitions
87
+ "cosmo", # marks tests that use COSMO grib definitions
88
+ "data", # marks tests that depend on specific test data
89
+ ]
90
+
91
+ [tool.black]
92
+
93
+ [tool.isort]
94
+ default_section = "THIRDPARTY"
95
+ profile = "black"
96
+ # Headings
97
+ import_heading_stdlib = "Standard library"
98
+ import_heading_thirdparty = "Third-party"
99
+ import_heading_firstparty = "First-party"
100
+ import_heading_localfolder = "Local"
101
+ # Known modules to avoid misclassification
102
+ known_standard_library = [
103
+ # Add standard library modules that may be misclassified by isort
104
+ ]
105
+ known_third_party = [
106
+ # Add third-party modules that may be misclassified by isort
107
+ ]
108
+ known_first_party = [
109
+ # Add first-party modules that may be misclassified by isort
110
+ "meteodatalab",
111
+ ]
112
+
113
+ [tool.mypy]
114
+ plugins = ["pydantic.mypy"]
115
+
116
+ [[tool.mypy.overrides]]
117
+ # Note: Only globally ignore missing imports as a matter of last resort!
118
+ # See https://blog.wolt.com/engineering/2021/09/30/professional-grade-mypy-configuration/
119
+ module = [
120
+ # Add external modules w/o type hints here
121
+ "rasterio",
122
+ "rasterio.crs",
123
+ ]
124
+ ignore_missing_imports = true
125
+
126
+ [tool.flake8]
127
+ max-line-length = 88
128
+ # max-complexity = 10
129
+ ignore = [
130
+ "E203", # Allow whitespace before ':' (https://github.com/PyCQA/pycodestyle/issues/373)
131
+ "F811", # Allow redefinition of unused name (necessary for typing.overload)
132
+ "I002", # Don't check for isort configuration
133
+ "W503", # Allow line break before binary operator (PEP 8-compatible)
134
+ "E704", # Allow multiple statements on one line (def)
135
+ ]
136
+ per-file-ignores = [
137
+ "__init__.py: F401", # Allow unused imports
138
+ ]
139
+
140
+ [tool.pylint]
141
+ recursive = true
142
+ ignore-imports = true
143
+ max-line-length = 88
144
+ # Tweak valid name formats
145
+ # Defaults (http://pylint-messages.wikidot.com/messages:c0103):
146
+ # argument-rgx = "^[a-z_][a-z0-9_]{2,30}$"
147
+ # attr-rgx = "^[a-z_][a-z0-9_]{2,30}$"
148
+ # function-rgx = "^[a-z_][a-z0-9_]{2,30}$"
149
+ # method-rgx = "^[a-z_][a-z0-9_]{2,30}$"
150
+ # variable-rgx = "^[a-z_][a-z0-9_]{2,30}$"
151
+ # class-rgx = "^[A-Z_][a-zA-Z0-9]+$"
152
+ # const-rgx = "^(([A-Z_][A-Z0-9_]*)|(__.*__))$"
153
+ # module-rgx = "^(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$"
154
+ argument-rgx = "^[a-z_][a-z0-9_]{,40}$"
155
+ attr-rgx = "^[a-z_][a-z0-9_]{,40}$"
156
+ function-rgx = "^[a-z_][a-z0-9_]{,40}$"
157
+ method-rgx = "^[a-z_][a-z0-9_]{,40}$"
158
+ variable-rgx = "^[a-z_][a-z0-9_]{,40}$"
159
+ class-rgx = "^[A-Z_][a-zA-Z0-9]+$"
160
+ const-rgx = "^(([a-z_][a-z0-9_]*)|([A-Z_][A-Z0-9_]*)|(__[a-zA-Z0-9]+__))$"
161
+ module-rgx = "^(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$"
162
+ inlinevar-rgx = "^[A-Za-z_][A-Za-z0-9_]*$"
163
+ # Disable selected warnings, errors etc. that conflict with style guide etc.
164
+ # Note: To locally ignore certain errors, use "pylint: disable=XXXX" comments instead!
165
+ disable = [
166
+ "C0115", # Missing class docstring
167
+ "C0116", # Missing function or method docstring
168
+ "R0903", # Too few public methods (*/2) (too-few-public-methods)
169
+ # "R0801", # Similar lines in 2 files (duplicate-code)
170
+ # -> see https://github.com/PyCQA/pylint/issues/214
171
+ "R1705", # Unnecessary "elif" after "return" (no-else-return)
172
+ "R1720", # Unnecessary "elif" after "raise" (no-else-raise)
173
+ "R1724", # Unnecessary "elif" after "continue" (no-else-continue)
174
+ "W1116", # Second argument of isinstance is not a type (isinstance-second-argument-not-valid-type)
175
+ ]
176
+ # Ignore (sub-)modules that trigger errors like E1101 (no-member) or E0611 (no-name-in-module)
177
+ ignored-modules = []
178
+
179
+ [tool.pydocstyle]
180
+ # All codes: http://www.pydocstyle.org/en/stable/error_codes.html
181
+ ignore = [
182
+ # "D100", # Missing docstring in public module
183
+ "D101", # Missing docstring in public class
184
+ "D102", # Missing docstring in public method
185
+ "D103", # Missing docstring in public function
186
+ # "D104", # Missing docstring in public package
187
+ "D105", # Missing docstring in magic method
188
+ # "D105", # Missing docstring in public nested class
189
+ "D107", # Missing docstring in __init__
190
+ "D203", # Blank line required before class docstring
191
+ "D213", # Multi-line docstring summary should start at the second line
192
+ # "D405", # Section name should be properly capitalized
193
+ "D406", # Section name should end with a newline
194
+ "D407", # Missing dashed underline after section
195
+ ]
196
+
197
+ [tool.rstcheck]
198
+ ignore_directives = ["automodule", "mdinclude", "autosummary"]
199
+
200
+ [tool.codespell]
201
+ ignore-words-list = "aare,inout,THIRDPARTY"
202
+ skip = "poetry.lock"
@@ -0,0 +1,8 @@
1
+ """Top-level package for meteodata-lab."""
2
+
3
+ # Standard library
4
+ import importlib.metadata
5
+
6
+ __author__ = "Carlos Osuna"
7
+ __email__ = "carlos.osuna@meteoswiss.ch"
8
+ __version__ = importlib.metadata.version("meteodata-lab")
@@ -0,0 +1,151 @@
1
+ """Command line interface of meteodata-lab."""
2
+
3
+ # Standard library
4
+ from importlib.resources import files
5
+ from pathlib import Path
6
+
7
+ # Third-party
8
+ import click
9
+ import yaml
10
+
11
+ # Local
12
+ from . import __version__, grib_decoder
13
+ from .metadata import is_staggered_horizontal
14
+ from .operators import destagger, gis, regrid
15
+
16
+
17
+ def print_version(ctx, _, value: bool) -> None:
18
+ """Print the version number and exit."""
19
+ if value:
20
+ click.echo(__version__)
21
+ ctx.exit(0)
22
+
23
+
24
+ @click.group()
25
+ @click.option(
26
+ "--version",
27
+ "-V",
28
+ help="Print version and exit.",
29
+ is_flag=True,
30
+ expose_value=False,
31
+ callback=print_version,
32
+ )
33
+ def main() -> None:
34
+ """Console script for test_cli_project."""
35
+ print("CLI for meteodatalab")
36
+
37
+
38
+ RESAMPLING = {
39
+ "nearest": regrid.Resampling.nearest,
40
+ "bilinear": regrid.Resampling.bilinear,
41
+ "cubic": regrid.Resampling.cubic,
42
+ }
43
+
44
+
45
+ def _load_mapping():
46
+ mapping_path = files("meteodatalab.data").joinpath("field_mappings.yml")
47
+ return yaml.safe_load(mapping_path.open())
48
+
49
+
50
+ def handle_vector_fields(ds):
51
+ mapping = _load_mapping()
52
+ names = set(ds)
53
+ pairs = []
54
+ while names:
55
+ name = names.pop()
56
+ item = mapping[name]["cosmo"]
57
+ if u := item.get("uComponent"):
58
+ if u not in names:
59
+ msg = f"The u-component {u} must be part of PARAMS"
60
+ raise click.UsageError(msg)
61
+ names.remove(u)
62
+ pairs.append((u, name))
63
+ elif v := item.get("vComponent"):
64
+ if v not in names:
65
+ msg = f"The v-component {v} must be part of PARAMS"
66
+ raise click.UsageError(msg)
67
+ names.remove(v)
68
+ pairs.append((name, v))
69
+
70
+ for u_name, v_name in pairs:
71
+ click.echo(f"Rotating vector field components {u_name}, {v_name} to geolatlon")
72
+ u, v = ds[u_name], ds[v_name]
73
+ if is_staggered_horizontal(u):
74
+ u = destagger.destagger(u, "x")
75
+ if is_staggered_horizontal(v):
76
+ v = destagger.destagger(v, "y")
77
+ if u.vref == "native" and v.vref == "native":
78
+ u_g, v_g = gis.vref_rot2geolatlon(u, v)
79
+ ds[u_name] = u_g
80
+ ds[v_name] = v_g
81
+ elif u.vref == "geo" and v.vref == "geo":
82
+ continue
83
+ else:
84
+ msg = "Differing vector references."
85
+ raise RuntimeError(msg)
86
+
87
+
88
+ @main.command("regrid")
89
+ @click.option(
90
+ "--crs",
91
+ type=click.Choice(["geolatlon"]),
92
+ default="geolatlon",
93
+ help="Coordinate reference system",
94
+ )
95
+ @click.option(
96
+ "--resampling",
97
+ type=click.Choice(list(RESAMPLING.keys())),
98
+ default="nearest",
99
+ help="Resampling method",
100
+ )
101
+ @click.option(
102
+ "--ref-param",
103
+ default="HHL",
104
+ help="Reference parameter with respect to staggering, default: HHL",
105
+ )
106
+ @click.argument(
107
+ "infile",
108
+ nargs=-1,
109
+ type=click.Path(exists=True, path_type=Path),
110
+ )
111
+ @click.argument(
112
+ "outfile",
113
+ type=click.Path(writable=True, path_type=Path),
114
+ )
115
+ @click.argument("params")
116
+ def regrid_cmd(
117
+ crs: str,
118
+ resampling: str,
119
+ ref_param: str,
120
+ infile: tuple[Path, ...],
121
+ outfile: Path,
122
+ params: str,
123
+ ):
124
+ """Regrid the given PARAMS found in INFILE and write to OUTFILE.
125
+
126
+ PARAMS is a comma separated list of short names following the cosmo convention.
127
+ """
128
+ resampling_arg = RESAMPLING[resampling]
129
+ crs_str = regrid.CRS_ALIASES.get(crs, crs)
130
+
131
+ if not infile:
132
+ raise click.UsageError("No input files")
133
+
134
+ if outfile.exists():
135
+ click.confirm(f"OUTFILE {outfile} exists. Overwrite?")
136
+
137
+ reader = grib_decoder.GribReader.from_files(list(infile), ref_param=ref_param)
138
+ ds = reader.load_fieldnames(params.split(","))
139
+
140
+ handle_vector_fields(ds)
141
+
142
+ with outfile.open("wb") as fout:
143
+ for name, field in ds.items():
144
+ src = regrid.RegularGrid.from_field(field)
145
+ dst = src.to_crs(crs_str)
146
+
147
+ click.echo(f"Regridding field {name} to {dst}")
148
+ field_out = regrid.regrid(field, dst, resampling_arg)
149
+
150
+ click.echo(f"Writing GRIB fields to {outfile}")
151
+ grib_decoder.save(field_out, fout)
@@ -0,0 +1,61 @@
1
+ """global configuration for meteodatalab."""
2
+
3
+ # Standard library
4
+ from contextlib import contextmanager
5
+ from typing import Any, Literal
6
+
7
+ config: dict = {}
8
+
9
+
10
+ @contextmanager
11
+ def set_values(config: dict = config, **kwargs):
12
+ """Temporarily set configuration values within a context manager.
13
+
14
+ Parameters
15
+ ----------
16
+ config : dict, optional
17
+ dictionary object use to hold the configuration.
18
+ Default will use the config object in this module
19
+ **kwargs :
20
+ the configuration key-value pairs to set.
21
+
22
+ """
23
+ record: list[tuple[Literal["insert", "replace"], str, Any]] = []
24
+
25
+ for key, value in kwargs.items():
26
+ if key in config:
27
+ record.append(("replace", key, config[key]))
28
+ else:
29
+ record.append(("insert", key, None))
30
+
31
+ config[key] = value
32
+
33
+ try:
34
+ yield config
35
+ finally:
36
+ for op, key, value in reversed(record):
37
+ d = config
38
+ if op == "replace":
39
+ d[key] = value
40
+ else: # insert
41
+ d.pop(key, None)
42
+
43
+
44
+ def get(
45
+ key: str,
46
+ default: Any = None,
47
+ config: dict = config,
48
+ ) -> Any:
49
+ """Get values from global config.
50
+
51
+ Parameters
52
+ ----------
53
+ key: str
54
+ specifies the name of the key for which the value is requested
55
+ default: Any
56
+ default value to be returned in case the key does not exist in config
57
+ config: dict, optional
58
+ config object holding the mapping. Default value is the global config
59
+
60
+ """
61
+ return config.get(key, default)
@@ -0,0 +1,8 @@
1
+ ---
2
+ type: local
3
+ engine: toc
4
+ schema: /store_new/mch/msopr/icon_workflow_2/fdb-schema
5
+ spaces:
6
+ - handler: Default
7
+ roots:
8
+ - path: /store_new/mch/msopr/icon_workflow_2/fdb_32_39x45_51/
@@ -0,0 +1,8 @@
1
+ ---
2
+ type: local
3
+ engine: toc
4
+ schema: /project/s83c/rz+/icon_data_processing_incubator/fdb_schema
5
+ spaces:
6
+ - handler: Default
7
+ roots:
8
+ - path: /scratch/ckanesan/fdb_32_39x45_51