├── .dockerignore ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CHANGELOG.rst ├── CONTRIBUTING.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── builder.py ├── docs ├── _static │ └── .gitkeep ├── conf.py └── index.rst ├── environment.yml ├── examples ├── UC-01.py ├── UC-03-bufr.py ├── UC-04-grib.py ├── UC-05-grib.py ├── UC-07-bufr-pandas.py ├── UC-07-bufr.py ├── analysing_data.ipynb ├── compute_wind_speed.ipynb ├── cross_section_cds.ipynb ├── data_uv.grib ├── ens_computing_plotting.ipynb ├── ens_mean_spread_xarray.ipynb ├── ens_storm.grib ├── fc_storm.grib ├── field_obs_difference.ipynb ├── forecast_ml.grib ├── grib_interpolation.ipynb ├── joachim_uv.grib ├── joachim_wind_gust.grib ├── obs_3day.bufr ├── principal_component_analysis.ipynb ├── seaIce_CO2_correlation.py ├── t2_for_UC-04.grib ├── t2m_grib.grib ├── t_n48.grib ├── uv.grib ├── wgust_ens.grib ├── wgust_era5.nc ├── wind_gust_nc_era5_cds.ipynb ├── wind_shear_vertical_cross_section.ipynb ├── z500_ens.grib └── z_for_UC-04.grib ├── metview ├── __init__.py ├── __main__.py ├── bindings.py ├── compat.py ├── dataset.py ├── etc │ ├── areas.yaml │ ├── dataset_template.yaml │ ├── map_styles.yaml │ ├── map_styles_template.yaml │ ├── param_styles.yaml │ ├── params.yaml │ ├── scaling_ecmwf.yaml │ └── units-rules.json ├── gallery.py ├── layout.py ├── metview.h ├── metviewpy │ ├── __init__.py │ ├── fieldset.py │ ├── indexdb.py │ ├── indexer.py │ ├── ipython.py │ ├── maths.py │ ├── param.py │ ├── temporary.py │ └── utils.py ├── plotting.py ├── scaling.py ├── style.py ├── title.py ├── track.py └── ui.py ├── mpy.sublime-project ├── requirements ├── requirements-dev.txt ├── requirements-docs.in ├── requirements-docs.txt ├── requirements-tests.in └── requirements-tests.txt ├── setup.cfg ├── setup.py ├── tests ├── MVPROFILEVIEW.png ├── __init__.py ├── all_missing_vals.grib ├── args.py ├── daily_clims.grib ├── ds │ ├── an │ │ ├── 10u_sfc.grib │ │ ├── 10v_sfc.grib │ │ ├── msl_sfc.grib │ │ ├── pv_pt.grib │ │ ├── t_pl.grib │ │ ├── u_pl.grib │ │ ├── v_pl.grib │ │ └── w_pl.grib │ └── oper │ │ ├── 10u_sfc.grib │ │ ├── 10v_sfc.grib │ │ ├── msl_sfc.grib │ │ ├── pv_pt.grib │ │ ├── t_pl.grib │ │ ├── tp_sfc.grib │ │ ├── u_pl.grib │ │ ├── v_pl.grib │ │ └── w_pl.grib ├── flextra_output.txt ├── geo_ncols_8.gpt ├── geopointset_1.gpts ├── ml_data.grib ├── monthly_avg.grib ├── obs_3day.bufr ├── request.req ├── rgg_small_subarea_cellarea_ref.grib ├── sample.csv ├── sample_metadata.csv ├── small_odb.odb ├── sort │ ├── date.csv.gz │ ├── date_level.csv.gz │ ├── default.csv.gz │ ├── default_desc.csv.gz │ ├── keys.csv.gz │ ├── level.csv.gz │ ├── level_asc.csv.gz │ ├── level_desc.csv.gz │ ├── level_units.csv.gz │ ├── multi_asc.csv.gz │ ├── multi_desc.csv.gz │ ├── multi_mixed.csv.gz │ ├── number.csv.gz │ ├── paramId.csv.gz │ ├── shortName.csv.gz │ ├── sort_data.grib │ ├── step.csv.gz │ ├── time.csv.gz │ └── units.csv.gz ├── t1000_LL_2x2.grb ├── t1000_LL_7x7.grb ├── t2m_3day.gpt ├── t_for_xs.grib ├── t_time_series.grib ├── t_with_missing.grib ├── temp_u.odb ├── test.grib ├── test_bindings.py ├── test_dataset.py ├── test_indexer.py ├── test_metview.py ├── test_pp_metview.py ├── test_style.py ├── test_utils.py ├── tuv_pl.grib ├── uv.gpt ├── xs_date_mv5.nc ├── xyv.gpt ├── zt_multi_expvers.grib └── ztu_multi_dim.grib └── tox.ini /.dockerignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .docker-tox 3 | .coverage 4 | .eggs 5 | .tox 6 | build 7 | dist 8 | htmlcov 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # Unit test / coverage reports 27 | htmlcov/ 28 | .tox/ 29 | .coverage 30 | .coverage.* 31 | .cache 32 | nosetests.xml 33 | coverage.xml 34 | *.cover 35 | .hypothesis/ 36 | 37 | 38 | # Sphinx documentation 39 | docs/_build/ 40 | 41 | # Jupyter Notebook 42 | .ipynb_checkpoints 43 | 44 | # pyenv 45 | .python-version 46 | 47 | # mypy 48 | .mypy_cache/ 49 | 50 | # PyCharm 51 | .idea/ 52 | 53 | # Docker 54 | .docker-tox 55 | .docker-current 56 | .eggs 57 | 58 | # tests 59 | .pytest_cache 60 | tests/*.idx 61 | tests/data 62 | major_basins_of_the_world_0_0_0* 63 | Major_Basins_of_the_World.* 64 | zzz_for_spectra.grib 65 | 66 | # diagnostics 67 | diag/data 68 | diag/mem_result.yaml 69 | diag/.benchmarks 70 | 71 | # vscode 72 | .vscode/ 73 | .env 74 | 75 | # mac 76 | .DS_Store 77 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | #---------------------------------# 2 | # general configuration # 3 | #---------------------------------# 4 | 5 | sudo: false 6 | 7 | branches: 8 | only: 9 | - develop 10 | - master 11 | 12 | language: python 13 | 14 | matrix: 15 | include: 16 | - os: linux 17 | dist: xenial 18 | env: MINICONDA_FILE="Miniconda3-latest-Linux-x86_64.sh" 19 | 20 | env: 21 | global: 22 | - METVIEW_PYTHON_SRC=${TRAVIS_BUILD_DIR} 23 | - PYTHONPATH=${PYTHONPATH}:${METVIEW_PYTHON_SRC}:. 24 | - METVIEW_PYTHON_DEBUG=1 25 | - MAGPLUS_DEBUG=ON 26 | 27 | git: 28 | depth: 1 29 | 30 | before_install: 31 | # install conda 32 | - | 33 | MINICONDA_URL="https://repo.continuum.io/miniconda" 34 | curl -L -O "${MINICONDA_URL}/${MINICONDA_FILE}" 35 | bash ${MINICONDA_FILE} -b 36 | # activate conda 37 | - source ~/miniconda3/bin/activate 38 | # auto-yes for conda 39 | - conda config --set always_yes yes 40 | # update conda 41 | - conda update -n base -c conda-forge conda 42 | 43 | install: 44 | # install deps 45 | - pip install netcdf4 xarray cfgrib scipy pytest pytest-cov black coveralls PyYAML 46 | - conda install -c conda-forge "metview-batch>=5.14.1" 47 | 48 | #---------------------------------# 49 | # build configuration # 50 | #---------------------------------# 51 | 52 | script: 53 | - cd ${METVIEW_PYTHON_SRC} 54 | - black --check . 55 | - pytest --cov=metview --cov-report term-missing $EXTRA_FLAGS tests/ 56 | 57 | 58 | after_success: 59 | - coveralls 60 | 61 | 62 | deploy: 63 | provider: pypi 64 | user: "__token__" 65 | password: 66 | secure: "X83UgpZc8wyryivt8lGxgFYGnVHgeX/pwslTRodmwAIEvbjv/Yg41ehRW4NirKWX5iP0YZwHzSZG0PtdPQLpg0kgwZYASfH0uaMZ63fe8h3ODg3V4mQTboIGcPaw9nEuli7ehlQ0Mp1UzK7ZasXrfI8l89AzDjWtOLlpjkom6tZ6mhsdmaUx2eH5RXDfPdeoryKBWOi/xF0Hl1aXb7a3VDrkTm7dDSJGsrxp8wEfjutohBgH45h6EsoI9BCp+g4nkwgYNQ/b0rw62ZlQbWqOz/CiUK22pX1XGl/I6Xn3hMqHnNV6nRxnn/QcCJ24I5nGpedp/P8KqrrlnQExENeyGEqlRZ1iMdzbu914ygicbT9InJotnnMKsxeYR94/Hlhrbhh0D6oCOmvVKbHeJa4efwM/HFMn6obt2EPwVRJqfZ1N2mbqm3GUciWnY21tzFVYaEqF1MG5RWTyxWs2nhIkHxo4CiANA43UYqOdlkDH50LMzPtVD0NciBTCDqhI1+MIa66OwEyMZUe6mgpxYH6OLV7SuxGfG9Zbtz1LJAfMVP1TuUCWbCHKsskQhVP2VykEvwGR1z3nAribR6Drs2joEQUhuDHm780zsnPbspQGqAsiwkI0WuP3QmMTbqI+piVJ1uChuvQu5UHIalbjDBShcJodp/NKCsgCi3N7YCua6IM=" 67 | on: 68 | tags: true 69 | 70 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Authors 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Iain Russell 9 | 10 | Contributors 11 | ------------ 12 | 13 | * Alessandro Amici 14 | * Marco Di Bari 15 | * Sandor Kertesz 16 | * Fernando Ii 17 | * Aureliana Barghini 18 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | 2 | Changelog for Metview's Python interface 3 | 4 | ======================================== 5 | 6 | 1.16.1 7 | ------------------- 8 | - added support for numpy 2.0 9 | 10 | 11 | 1.16.0 12 | ------------------- 13 | - added helper functions to aid automatic conversion from Macro to Python 14 | - added support for reading PNG files 15 | - added string() function from Metview 16 | - fixed warning message in select() method with pandas 2.2 17 | 18 | 19 | 1.15.0 20 | ------------------ 21 | - Added arguments() function to mimic the behaviour of the Macro function of the same name 22 | - Fixed case of mean(dim=...) where the input fieldset is not a complete hypercube 23 | 24 | 25 | 1.14.0 26 | ------------------ 27 | - Fieldset function stdev() now accepts 'dim' argument to compute over given dimension 28 | - added new function plot_xs_avg() to provide high-level plotting interface for average cross sections 29 | - Fixed issue where functions that take numpy arrays as input did not work properly with multi-dimensional arrays 30 | 31 | 32 | 1.13.1 33 | ------------------ 34 | - fixed memory leaks when passing strings, numbers and dates from the binary layer to the Python layer 35 | 36 | 37 | 1.13.0 38 | ------------------ 39 | - Fieldset functions mean() and sum() now accept 'dim' argument to compute over given dimension 40 | - function gallery.load_dataset() now downloads from a new server 41 | - added new functions smooth_n_point() and smooth_gaussian() to perform spatial smoothing on fieldsets with lat-lon grids 42 | - added new function convolve() to perform spatial 2D convolution on fieldsets with lat-lon grids 43 | 44 | 45 | 1.12.0 46 | ------------------ 47 | - add 'reverse' and/or operators between Fieldsets and bools (e.g. 'True & my_fieldset') 48 | - fixed issue where automatic indexing of a dataset fails 49 | 50 | 51 | 1.11.0 52 | ------------------ 53 | - make fieldset parameter selector operator work in pure Python Fieldset 54 | - make vector data extraction operator work for 100m and 200m wind 55 | - select(): do not try to interpret shortName values as vector field names 56 | - select(): do not apply sorting on results 57 | - Fieldsets: add sort method based on indexed metadata 58 | - select(): fix issue where cannot filter by date when data date has no year component (e.g. monthly climatologies) 59 | - Requests: allow True/False as argument values when creating Requests 60 | 61 | 62 | 1.10.5 63 | ------------------ 64 | - fix for automatic indexing of dataset 65 | 66 | 67 | 1.10.0 68 | ------------------ 69 | - experimental pure Python implementation of Fieldset - for internal testing at the moment 70 | 71 | 72 | 1.9.0 73 | ------------------ 74 | - the plot functions now automatically plot inline if running inside a Jupyter notebook 75 | - it is no longer necessary to call setoutput('jupyter') 76 | - call setoutput('screen') to force the interactive plot window to appear 77 | - inline plots in Jupyter notebooks will be automatically trimmed of surrounding whitespace if pillow is installed 78 | - new functions to build popup dialogs and read in user input. Available via the newly added ui module. 79 | - ui.dialog() 80 | - ui.any() 81 | - ui.colour() 82 | - ui.icon() 83 | - ui.option_menu() 84 | - ui.slider() 85 | - ui.toggle() 86 | - added high-level plotting functions to be used with Datasets or in Jupyter notebooks 87 | - plot_maps() 88 | - plot_diff_maps() 89 | - plot_xs() 90 | - plot_rmse() 91 | - new object Track to represent a storm track 92 | - new function make_geoview() to generate a geoview object with predefined settings 93 | - new interface for Datasets 94 | - a Dataset represents a collection of data files (GRIB and CSV) and a set of predefined styles to visualise the data. Ideal for training courses or case studies. 95 | - see Jupyter notebook example at https://metview.readthedocs.io/en/latest/notebook_gallery.html 96 | - added new keyword argument called check_local to gallery.load_dataset(). If it is True and the data file exists locally it will not be downloaded. 97 | - fixed issue when describe() crashed when called with a paramId 98 | 99 | 100 | 1.8.1 101 | ------------------ 102 | - fixed case where map_area_gallery() crashed 103 | - fixed case where map_style_gallery() crashed 104 | - fixed issue where plot_maps() could not plot wind data 105 | - fixed issue where a style could not be updated when verb argument is specified 106 | 107 | 108 | 1.8.0 109 | ------------------ 110 | - new functions/methods on Fieldset to give an overview of contents: 111 | - fs.describe() 112 | - fs.describe("tp") 113 | - fs.ls() 114 | - see Jupyter notebook example at https://metview.readthedocs.io/en/latest/notebook_gallery.html 115 | - new GRIB filtering function, select(), offers different filtering options from read() and is faster 116 | - see Jupyter notebook example at https://metview.readthedocs.io/en/latest/notebook_gallery.html 117 | - new shorthand way to select parameters from Fieldsets, e.g. 118 | - g = fs["t"] 119 | - g = fs["t500"] 120 | - g = fs["t500hPa"] 121 | - the Fieldset constructor can now take a list of paths to GRIB files or a wildcard: 122 | - e.g. a = mv.Fieldset(path=["/path1/to/data1.grib", "relpath/data2.grib"]) 123 | - e.g. a = mv.Fieldset(path="data/*.grib") 124 | - the result of a call to mcont() etc can now be modified, e.g. 125 | - c = mv.mcont() ; c["contour_line_colour"] = "green" ; mv.plot(data, c) 126 | - gv.update({"MAP_COASTLINE_land_SHADE_COLOUR": "green"}, sub="COASTlines") 127 | - improved the output of print(Fieldset): 128 | - "Fieldset (6 fields)" 129 | 130 | 131 | 1.7.2 132 | ------------------ 133 | - new argument to setoutput(plot_widget=) - default True, set False to allow images to be saved into the notebook 134 | - multi-page plots in Jupyter notebooks now contain the animation controls by default 135 | 136 | 137 | 1.7.1 138 | ------------------ 139 | - added automatic play and speed controls to animated plots in Jupyter notebooks 140 | 141 | 142 | 1.7.0 143 | ------------------ 144 | - added animate=True argument to plot() command for animated plots in Jupyter notebooks 145 | - allowed cfgrib backend keyword arguments to be passed to Fieldset.to_dataset() 146 | - Fieldset out-of-range indexing now raises an IndexError 147 | - Fieldset merge() function now allows a single Fieldset as argument 148 | 149 | 150 | 1.6.1 151 | ------------------ 152 | - renamed function download_gallery_data() to metview.gallery.load_dataset() 153 | 154 | 155 | 1.6.0 156 | ------------------ 157 | - added new function download_gallery_data() to download Gallery example data files 158 | - added write(filename) method for classes Fieldset, Geopoints, GeopointSet, Bufr and NetCDF 159 | - added ability to construct a Fieldset from a list of Fieldsets: Fieldset([f1, f2, f3]) 160 | - added metzoom function (for the future) 161 | - added keyword arguments to setoutput('jupyter') to control output size in notebooks 162 | - added metview_python member to result of version_info() function 163 | 164 | 165 | 1.5.1 166 | ------------------ 167 | - temporarily removed tests that involve writing xarrays as GRIB 168 | 169 | 170 | 1.5.0 171 | ------------------ 172 | - added support for int numpy arrays as input to functions and methods 173 | - added support for bitwise and (&), or (|) and not (~) operators on Fieldsets 174 | - added div() function (already available via the '/' operator) 175 | - added mod() function 176 | - improved timeout message by mentioning how to increase the timeout 177 | - fixed error when updating an mv.Request object 178 | 179 | 180 | (No version update) 181 | ------------------- 182 | - added new Jupyter notebook for data analysis 183 | - added new Jupyter notebook for computing and plotting ensemble data 184 | - fixed issue where Metview Request objects did not respect the input data type 185 | 186 | 1.4.2 187 | ------------------ 188 | - fixed issue when using a numpy array to index a Fieldset 189 | 190 | 1.4.1 191 | ------------------ 192 | - added travis ci and coveralls support 193 | - added automatic upload to PyPi 194 | 195 | 1.4.0 196 | ------------------ 197 | - allow a geopoints column name to be used as index when assigning data to a column 198 | - allow -, + and abs operators to work on Metview classes (e.g. a = -my_fieldset) 199 | - added support for Metview's file object 200 | - fixed issue where negative indexing did not work on a Fieldset 201 | - fixed issue where concurrent iterators on a Fieldset did not work 202 | - added experimental support for pickling Fieldsets 203 | - automatically obtain list of Macro-based functions 204 | - allow example notebooks to run in Binder 205 | 206 | 1.3.4 (2020-02-02) 207 | ------------------ 208 | - fixed issue when passing sliced numpy arrays to Metview 209 | - added environment.yaml for running in Binder 210 | - fixed issue when running example notebooks in Binder 211 | 212 | 213 | 1.3.3 (2020-01-13) 214 | ------------------ 215 | - fixed memory leak in Fieldset.append() method 216 | 217 | 218 | 1.3.2 (2019-12-06) 219 | ------------------ 220 | - added support for reflected operators on Fieldsets, e.g. "2 * Fieldset" 221 | - done for addition, subtraction, multiplication, division and power 222 | 223 | 224 | 1.3.1 (2019-10-11) 225 | ------------------ 226 | - added ml_to_hl() function 227 | 228 | 229 | 1.3.0 (2019-09-26) 230 | ------------------ 231 | 232 | - export the Request class 233 | - fixed memory leak when returning a list of items 234 | - allow bool-typed numpy arrays as input 235 | - fixed issue where the Fieldset iterator could fail if used multiple times 236 | 237 | 238 | 1.2.0 (2019-07-11) 239 | ------------------ 240 | 241 | - Metview startup timeout configurable via environment variable METVIEW_PYTHON_START_TIMEOUT (in seconds) 242 | - Metview startup timeout default set to 8 seconds in case of busy systems 243 | - added integral() function 244 | - fixed memory leak when exporting vectors as numpy arrays 245 | 246 | 247 | 1.1.0 (2019-03-04) 248 | ------------------ 249 | 250 | - added equality (``==``) and non-equality (``!=``) operators for Fieldset and Geopoints objects, e.g. ``same = (a == b)`` will produce a new Fieldset with 1s where the values are the same, and 0s elsewhere. 251 | - added new thermodynamic, gradient and utility functions: 'thermo_data_info', 'thermo_parcel_path', 'thermo_parcel_area', 'xy_curve', 'potential_temperature', 'temperature_from_potential_temperature', 'saturation_mixing_ratio', 'mixing_ratio', 'vapour_pressure', 'saturation_vapour_pressure', 'lifted_condensation_level', 'divergence', 'vorticity', 'laplacian', 'geostrophic_wind_pl', 'geostrophic_wind_ml' 252 | - improved conversion from geopoints to pandas dataframe to cope with new NCOLS subformat 253 | - make conversion from Fieldset to xarray dataset compatible with latest versions of cfgrib 254 | 255 | 256 | 1.0.0 (2018-12-20) 257 | ------------------ 258 | 259 | - code cleanup so that tox and pyflakes pass the tests 260 | 261 | 262 | 0.9.1 (2018-11-24) 263 | ------------------ 264 | 265 | - fixed issue where creating ``Fieldset`` slices of more than 10 fields or so did not work 266 | - allow the creation of a ``Fieldset`` object, either empty ``Fieldsest()`` or with a path to GRIB ``Fieldset('/path/to/grib')`` 267 | - added ``append()`` method to a ``Fieldset`` to append ``Fieldset``s to ``Fieldset``s 268 | - the ``dataset_to_fieldset`` function that converts an xarray dataset to a Metview ``Fieldset`` now accepts the ``no_warn=True`` argument to suppress warnings while the xarray GRIB writer is pre-beta 269 | - ignore errors on exit from a data examiner 270 | - added more example Jupyter notebooks 271 | 272 | 273 | 0.9.0 (2018-10-29) 274 | ------------------ 275 | 276 | - Beta release. 277 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | 2 | .. highlight:: console 3 | 4 | 5 | ============ 6 | Contributing 7 | ============ 8 | 9 | Contributions are welcome, and they are greatly appreciated! Every 10 | little bit helps, and credit will always be given. Note that contributors must 11 | agree to ECMWF's Contribution License Agreement (CLA) before their contribution 12 | can be accepted. 13 | 14 | You can contribute in many ways: 15 | 16 | Types of Contributions 17 | ---------------------- 18 | 19 | Report Bugs 20 | ~~~~~~~~~~~ 21 | 22 | Report bugs at https://github.com/ecmwf/metview-python/issues 23 | 24 | If you are reporting a bug, please include: 25 | 26 | * Your operating system name and version. 27 | * Installation method and version of all dependencies. 28 | * Any details about your local setup that might be helpful in troubleshooting. 29 | * Detailed steps to reproduce the bug, including a (small) sample file. 30 | 31 | Fix Bugs 32 | ~~~~~~~~ 33 | 34 | Look through the GitHub issues for bugs. Anything tagged with "bug" 35 | and "help wanted" is open to whoever wants to implement a fix for it. 36 | 37 | Implement Features 38 | ~~~~~~~~~~~~~~~~~~ 39 | 40 | Look through the GitHub issues for features. Anything tagged with "enhancement" 41 | and "help wanted" is open to whoever wants to implement it. 42 | 43 | Code Cleanup 44 | ~~~~~~~~~~~~ 45 | 46 | Look through the GitHub issues for code cleanliness issues. These are not likely to 47 | affect the behaviour of the software, but are more related to style, e.g. PEP8. 48 | Anything tagged with "code cleanliness" and "help wanted" is open to whoever wants to do it. 49 | 50 | 51 | Get Started! 52 | ------------ 53 | 54 | Ready to contribute? Here's how to set up `metview-python` for local development. Please note this documentation assumes 55 | you already have `virtualenv` and `Git` installed and ready to go. 56 | 57 | 1. Fork the `metview-python` repo on GitHub. 58 | 2. Clone your fork locally:: 59 | 60 | $ cd path_for_the_repo 61 | $ git clone https://github.com/ecmwf/metview-python.git 62 | $ cd metview-python 63 | 64 | 3. Assuming you have virtualenv installed (If you have Python3.5 this should already be there), you can create a new environment for your local development by typing:: 65 | 66 | $ python3 -m venv ../metview-python-env 67 | $ source ../metview-python-env/bin/activate 68 | 69 | This should change the shell to look something like 70 | (metview-python-env) $ 71 | 72 | 4. Install system dependencies as described in the README.rst file, then install the python dependencies into your local environment with:: 73 | 74 | $ pip install -r requirements/requirements-tests.txt 75 | $ pip install -e . 76 | 77 | 5. Create a branch for local development:: 78 | 79 | $ git checkout -b name-of-your-bugfix-or-feature 80 | 81 | Now you can make your changes locally. 82 | 83 | 6. The next step would be to run the test cases. `metview-python` uses py.test, you can run PyTest. Before you run pytest you should ensure all dependancies are installed:: 84 | 85 | $ pip install -r requirements/requirements-dev.txt 86 | $ pytest -v --flakes 87 | 88 | 7. Before raising a pull request you should also run tox. This will run the tests across different versions of Python:: 89 | 90 | $ tox 91 | 92 | 8. If your contribution is a bug fix or new feature, you should add a test to the existing test suite. 93 | 94 | 9. Commit your changes and push your branch to GitHub:: 95 | 96 | $ git add . 97 | $ git commit -m "Description of your changes (#issue-number)" 98 | $ git push origin name-of-your-bugfix-or-feature 99 | 100 | 10. Submit a pull request through the GitHub website. 101 | 102 | Pull Request Guidelines 103 | ----------------------- 104 | 105 | Before you submit a pull request, check that it meets these guidelines: 106 | 107 | 1. The pull request should include tests. Any data files should be a small as possible. 108 | 109 | 2. If the pull request adds functionality, the docs should be updated. Put 110 | your new functionality into a function with a docstring, and add the 111 | feature to the list in README.rst. 112 | 113 | 3. The pull request should work for Python 3.5, 3.6 and 3.7, and for Pypy3. Check 114 | the tox results and make sure that the tests pass for all supported Python versions. 115 | 116 | 117 | 118 | 119 | 120 | 121 | Docker development environment 122 | ------------------------------ 123 | 124 | This provides an alternative way to run and test Metview's Python interface, installing 125 | the binary dependencies into a Docker image with several 'make' commands to interact with it. 126 | 127 | Setup 128 | ~~~~~ 129 | 130 | You need docker working and up. 131 | 132 | To create the development image run:: 133 | 134 | $ make image 135 | 136 | To create the wheelhouse cache of binary packages run:: 137 | 138 | $ make wheelhouse 139 | 140 | 141 | Development tasks 142 | ~~~~~~~~~~~~~~~~~ 143 | 144 | Running unit tests on the target python version:: 145 | 146 | $ make test 147 | 148 | Running quality control:: 149 | 150 | $ make qc 151 | 152 | Running the full test suite on all python supported versions:: 153 | 154 | $ make detox 155 | 156 | To run a shell inside the container run:: 157 | 158 | $ make shell 159 | 160 | To start a Jupyter notebook inside the container run:: 161 | 162 | $ make notebook 163 | 164 | To update the requirements files to the latest versions:: 165 | 166 | $ make update-req 167 | 168 | All tasks can be run locally by adding `RUN=` to the command line, for example:: 169 | 170 | $ make qc RUN= 171 | 172 | 173 | Cleanup 174 | ~~~~~~~ 175 | 176 | Light cleanup with:: 177 | 178 | $ make clean 179 | 180 | Complete cleanup with:: 181 | 182 | $ make distclean 183 | 184 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017- ECMWF 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include Dockerfile 2 | include .dockerignore 3 | include LICENSE 4 | include Makefile 5 | include *.rst 6 | include tox.ini 7 | graft metview/etc 8 | 9 | recursive-include docs *.gitkeep 10 | recursive-include docs *.py 11 | recursive-include docs *.rst 12 | recursive-include examples *.bufr 13 | recursive-include examples *.grib 14 | recursive-include examples *.nc 15 | recursive-include examples *.py 16 | recursive-include examples *.ipynb 17 | recursive-include metview *.h 18 | recursive-include requirements *.in 19 | recursive-include requirements *.txt 20 | recursive-include tests *.bufr 21 | recursive-include tests *.csv 22 | recursive-include tests *.txt 23 | recursive-include tests *.gpt 24 | recursive-include tests *.gpts 25 | recursive-include tests *.grib 26 | recursive-include tests *.nc 27 | recursive-include tests *.odb 28 | recursive-include tests *.py 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | PACKAGE := metview 3 | IMAGE := $(PACKAGE) 4 | MODULE := $(PACKAGE) 5 | PYTHONS := python3.6 python3.5 python3.4 pypy3 6 | 7 | SOURCE := MetviewBundle-2019.02.0-Source.tar.gz 8 | SOURCE_URL := https://software.ecmwf.int/wiki/download/attachments/51731119/$(SOURCE) 9 | 10 | # uncomment after running the command 'make local-wheel' in the xarray-grib folder 11 | # EXTRA_PACKAGES := xarray_grib-0.1.0-py2.py3-none-any.whl 12 | 13 | export WHEELHOUSE := ~/.wheelhouse 14 | export PIP_FIND_LINKS := $(WHEELHOUSE) 15 | export PIP_WHEEL_DIR := $(WHEELHOUSE) 16 | export PIP_INDEX_URL 17 | 18 | DOCKERBUILDFLAGS := --build-arg SOURCE=$(SOURCE) 19 | DOCKERFLAGS := -e WHEELHOUSE=$(WHEELHOUSE) \ 20 | -e PIP_FIND_LINKS=$(PIP_FIND_LINKS) \ 21 | -e PIP_WHEEL_DIR=$(PIP_WHEEL_DIR) \ 22 | -e PIP_INDEX_URL=$$PIP_INDEX_URL 23 | # Development options 24 | # DOCKERFLAGS += -v $$(pwd)/../metview:/tmp/source/metview 25 | # DOCKERFLAGS += -v $$(pwd)/../metview-prefix:/usr/local 26 | # DOCKERFLAGS += -v $$(pwd)/../metpy:/metpy 27 | PIP := pip 28 | MKDIR = mkdir -p 29 | 30 | ifeq ($(shell [ -d $(WHEELHOUSE) ] && echo true),true) 31 | DOCKERFLAGS += -v $(WHEELHOUSE):/root/.wheelhouse 32 | endif 33 | 34 | RUNTIME := $(shell [ -f /proc/1/cgroup ] && cat /proc/1/cgroup | grep -q docker && echo docker) 35 | ifneq ($(RUNTIME),docker) 36 | override TOXFLAGS += --workdir=.docker-tox 37 | RUN = docker run --rm -it -v$$(pwd):/src -w/src $(DOCKERFLAGS) $(IMAGE) 38 | endif 39 | 40 | 41 | default: 42 | @echo No default 43 | 44 | # local targets 45 | 46 | $(PIP_FIND_LINKS): 47 | $(MKDIR) $@ 48 | 49 | local-wheelhouse-one: 50 | $(PIP) install wheel 51 | $(PIP) wheel -r requirements/requirements-tests.txt 52 | $(PIP) wheel -r requirements/requirements-docs.txt 53 | 54 | local-wheelhouse: 55 | for PYTHON in $(PYTHONS); do $(MAKE) local-wheelhouse-one PIP="$$PYTHON -m pip"; done 56 | $(PIP) wheel -r requirements/requirements-dev.txt 57 | 58 | local-install-dev-req: 59 | $(PIP) install -U pip setuptools wheel 60 | $(PIP) install -r requirements/requirements-dev.txt 61 | 62 | local-install-test-req: $(PIP_FIND_LINKS) 63 | $(PIP) install -r requirements/requirements-tests.txt 64 | 65 | local-develop: $(EXTRA_PACKAGES) 66 | for p in $<; do $(PIP) install $$p; done 67 | $(PIP) install -e . 68 | 69 | local-wheel: 70 | $(PIP) wheel -e . 71 | 72 | testclean: 73 | $(RM) -r */__pycache__ .coverage .cache 74 | 75 | clean: 76 | $(RM) -r */*.pyc htmlcov dist build .eggs *.egg-info 77 | 78 | distclean: clean 79 | $(RM) -r .tox .docker-tox 80 | 81 | cacheclean: 82 | $(RM) -r $(WHEELHOUSE)/* ~/.cache/* 83 | 84 | # container targets 85 | 86 | shell: 87 | $(RUN) 88 | 89 | notebook: DOCKERFLAGS += -p 8888:8888 90 | notebook: 91 | $(RUN) jupyter notebook --ip=* --allow-root 92 | 93 | wheelhouse: 94 | $(RUN) make local-wheelhouse 95 | 96 | update-req: 97 | $(RUN) pip-compile -o requirements/requirements-tests.txt -U setup.py requirements/requirements-tests.in 98 | $(RUN) pip-compile -o requirements/requirements-docs.txt -U setup.py requirements/requirements-docs.in 99 | 100 | test: testclean 101 | $(RUN) python setup.py test --addopts "-v --flakes --cov=$(MODULE) --cov-report=html --cache-clear" 102 | 103 | qc: 104 | $(RUN) python setup.py test --addopts "-v --pep8 --mccabe metview tests" 105 | 106 | tox: testclean 107 | $(RUN) tox $(TOXFLAGS) 108 | 109 | detox: testclean 110 | $(RUN) detox $(TOXFLAGS) 111 | 112 | # image build 113 | 114 | %.whl: $(WHEELHOUSE)/%.whl 115 | cp -a $< $@ 116 | 117 | $(SOURCE): 118 | curl -o $@ -L $(SOURCE_URL) 119 | 120 | image: $(SOURCE) $(EXTRA_PACKAGES) 121 | docker build -t $(IMAGE) $(DOCKERBUILDFLAGS) . 122 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | Metview Python bindings 3 | ======================= 4 | 5 | Python interface to Metview, a meteorological workstation and batch system for accessing, examining, manipulating and visualising meteorological data. 6 | See documentation at https://metview.readthedocs.io/en/latest/index.html 7 | 8 | 9 | Try the example notebooks on Binder! 10 | ------------------------------------ 11 | Click the link below to start a Binder session to try the examples online now: 12 | 13 | .. image:: https://mybinder.org/badge_logo.svg 14 | :target: https://mybinder.org/v2/gh/ecmwf/metview-python/master?filepath=examples 15 | 16 | 17 | Requirements 18 | ------------ 19 | 20 | - A working Metview 5 installation (at least version 5.0.3, ideally 5.3.0 or above), either from binaries or built from source. 21 | Conda packages are available for Linux, and native packages are available for many Linux distributions. 22 | See https://metview.readthedocs.io/en/latest/install.html 23 | 24 | - An alternative is to build from the Metview Source Bundle. 25 | See https://confluence.ecmwf.int/metview/The+Metview+Source+Bundle 26 | 27 | - Ensure that the command 'metview' will run this version by setting your PATH to include the 'bin' directory 28 | from where you built or installed it if it's not in a default location. 29 | 30 | - A Python 3 interpreter (ideally version >= 3.5) 31 | 32 | 33 | Install 34 | ------- 35 | 36 | The package is installed from PyPI with:: 37 | 38 | $ pip install metview 39 | 40 | 41 | or from conda-forge with:: 42 | 43 | $ conda install metview-python -c conda-forge 44 | 45 | 46 | Test 47 | ---- 48 | 49 | You may run a simple selfcheck command to ensure that your system is set up correctly:: 50 | 51 | $ python -m metview selfcheck 52 | Hello world - printed from Metview! 53 | Trying to connect to a Metview installation... 54 | Metview version 5.2.0 found 55 | Your system is ready. 56 | 57 | 58 | To manually test that your system is properly setup open a Python 3 interpreter and try:: 59 | 60 | >>> import metview as mv 61 | >>> mv.lowercase('Hello World!') 62 | 'hello world!' 63 | 64 | 65 | Examples 66 | -------- 67 | 68 | The [examples](examples) folder contains some Jupyter notebooks and some standalone examples for you to try out! 69 | 70 | 71 | Project resources 72 | ----------------- 73 | 74 | ============= ========================================================= 75 | Development https://github.com/ecmwf/metview-python 76 | Download https://pypi.org/project/metview 77 | Code quality .. image:: https://travis-ci.com/ecmwf/metview-python.svg?branch=master 78 | :target: https://travis-ci.com/ecmwf/metview-python 79 | :alt: Build Status on Travis CI 80 | .. image:: https://coveralls.io/repos/ecmwf/metview-python/badge.svg?branch=master&service=github 81 | :target: https://coveralls.io/github/ecmwf/metview-python 82 | :alt: Coverage Status on Coveralls 83 | ============= ========================================================= 84 | 85 | 86 | License 87 | ------- 88 | 89 | Copyright 2017-2021 European Centre for Medium-Range Weather Forecasts (ECMWF). 90 | 91 | Licensed under the Apache License, Version 2.0 (the "License"); 92 | you may not use this file except in compliance with the License. 93 | You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 94 | Unless required by applicable law or agreed to in writing, software 95 | distributed under the License is distributed on an "AS IS" BASIS, 96 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 97 | See the License for the specific language governing permissions and 98 | limitations under the License. 99 | -------------------------------------------------------------------------------- /builder.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | import logging 11 | 12 | import cffi 13 | import sys 14 | 15 | ffibuilder = cffi.FFI() 16 | ffibuilder.set_source( 17 | "metview._bindings", 18 | "#include ", 19 | libraries=["MvMacro"], 20 | ) 21 | metview_h_file = open("metview/metview.h") 22 | ffibuilder.cdef(metview_h_file.read()) 23 | metview_h_file.close() 24 | 25 | if __name__ == "__main__": 26 | try: 27 | ffibuilder.compile(verbose=True) 28 | except Exception: 29 | logging.exception("can't compile Metview bindings") 30 | sys.exit(1) 31 | -------------------------------------------------------------------------------- /docs/_static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/docs/_static/.gitkeep -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (C) Copyright 2017- ECMWF. 5 | # 6 | # This software is licensed under the terms of the Apache Licence Version 2.0 7 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 8 | # 9 | # In applying this licence, ECMWF does not waive the privileges and immunities 10 | # granted to it by virtue of its status as an intergovernmental organisation 11 | # nor does it submit to any jurisdiction. 12 | 13 | import pkg_resources 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another 19 | # directory, add these directories to sys.path here. If the directory is 20 | # relative to the documentation root, use os.path.abspath to make it 21 | # absolute, like shown here. 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # Get the project root dir, which is the parent dir of this 25 | cwd = os.getcwd() 26 | project_root = os.path.dirname(cwd) 27 | 28 | # Insert the project root dir as the first element in the PYTHONPATH. 29 | # This lets us ensure that the source package is imported, and that its 30 | # version is used. 31 | sys.path.insert(0, project_root) 32 | 33 | # -- General configuration --------------------------------------------- 34 | 35 | # If your documentation needs a minimal Sphinx version, state it here. 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 40 | extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode"] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ["_templates"] 44 | 45 | # The suffix of source filenames. 46 | source_suffix = ".rst" 47 | 48 | # The encoding of source files. 49 | # source_encoding = 'utf-8-sig' 50 | 51 | # The master toctree document. 52 | master_doc = "index" 53 | 54 | # General information about the project. 55 | project = "metview" 56 | copyright = "2017-2019, European Centre for Medium-Range Weather Forecasts (ECMWF)." 57 | 58 | # The version info for the project you're documenting, acts as replacement 59 | # for |version| and |release|, also used in various other places throughout 60 | # the built documents. 61 | # 62 | # The full version, including alpha/beta/rc tags. 63 | release = pkg_resources.get_distribution("metview").version 64 | # The short X.Y version. 65 | version = ".".join(release.split(".")[:2]) 66 | 67 | # The language for content autogenerated by Sphinx. Refer to documentation 68 | # for a list of supported languages. 69 | # language = None 70 | 71 | # There are two options for replacing |today|: either, you set today to 72 | # some non-false value, then it is used: 73 | # today = '' 74 | # Else, today_fmt is used as the format for a strftime call. 75 | # today_fmt = '%B %d, %Y' 76 | 77 | # List of patterns, relative to source directory, that match files and 78 | # directories to ignore when looking for source files. 79 | exclude_patterns = ["_build"] 80 | 81 | # The reST default role (used for this markup: `text`) to use for all 82 | # documents. 83 | # default_role = None 84 | 85 | # If true, '()' will be appended to :func: etc. cross-reference text. 86 | # add_function_parentheses = True 87 | 88 | # If true, the current module name will be prepended to all description 89 | # unit titles (such as .. function::). 90 | # add_module_names = True 91 | 92 | # If true, sectionauthor and moduleauthor directives will be shown in the 93 | # output. They are ignored by default. 94 | # show_authors = False 95 | 96 | # The name of the Pygments (syntax highlighting) style to use. 97 | pygments_style = "sphinx" 98 | 99 | # A list of ignored prefixes for module index sorting. 100 | # modindex_common_prefix = [] 101 | 102 | # If true, keep warnings as "system message" paragraphs in the built 103 | # documents. 104 | # keep_warnings = False 105 | 106 | 107 | # -- Options for HTML output ------------------------------------------- 108 | 109 | # The theme to use for HTML and HTML Help pages. See the documentation for 110 | # a list of builtin themes. 111 | html_theme = "default" 112 | 113 | # Theme options are theme-specific and customize the look and feel of a 114 | # theme further. For a list of options available for each theme, see the 115 | # documentation. 116 | # html_theme_options = {} 117 | 118 | # Add any paths that contain custom themes here, relative to this directory. 119 | # html_theme_path = [] 120 | 121 | # The name for this set of Sphinx documents. If None, it defaults to 122 | # " v documentation". 123 | # html_title = None 124 | 125 | # A shorter title for the navigation bar. Default is the same as 126 | # html_title. 127 | # html_short_title = None 128 | 129 | # The name of an image file (relative to this directory) to place at the 130 | # top of the sidebar. 131 | # html_logo = None 132 | 133 | # The name of an image file (within the static path) to use as favicon 134 | # of the docs. This file should be a Windows icon file (.ico) being 135 | # 16x16 or 32x32 pixels large. 136 | # html_favicon = None 137 | 138 | # Add any paths that contain custom static files (such as style sheets) 139 | # here, relative to this directory. They are copied after the builtin 140 | # static files, so a file named "default.css" will overwrite the builtin 141 | # "default.css". 142 | html_static_path = ["_static"] 143 | 144 | # If not '', a 'Last updated on:' timestamp is inserted at every page 145 | # bottom, using the given strftime format. 146 | # html_last_updated_fmt = '%b %d, %Y' 147 | 148 | # If true, SmartyPants will be used to convert quotes and dashes to 149 | # typographically correct entities. 150 | # html_use_smartypants = True 151 | 152 | # Custom sidebar templates, maps document names to template names. 153 | # html_sidebars = {} 154 | 155 | # Additional templates that should be rendered to pages, maps page names 156 | # to template names. 157 | # html_additional_pages = {} 158 | 159 | # If false, no module index is generated. 160 | # html_domain_indices = True 161 | 162 | # If false, no index is generated. 163 | # html_use_index = True 164 | 165 | # If true, the index is split into individual pages for each letter. 166 | # html_split_index = False 167 | 168 | # If true, links to the reST sources are added to the pages. 169 | # html_show_sourcelink = True 170 | 171 | # If true, "Created using Sphinx" is shown in the HTML footer. 172 | # Default is True. 173 | # html_show_sphinx = True 174 | 175 | # If true, "(C) Copyright ..." is shown in the HTML footer. 176 | # Default is True. 177 | # html_show_copyright = True 178 | 179 | # If true, an OpenSearch description file will be output, and all pages 180 | # will contain a tag referring to it. The value of this option 181 | # must be the base URL from which the finished HTML is served. 182 | # html_use_opensearch = '' 183 | 184 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 185 | # html_file_suffix = None 186 | 187 | # Output file base name for HTML help builder. 188 | htmlhelp_basename = "metviewdoc" 189 | 190 | 191 | # -- Options for LaTeX output ------------------------------------------ 192 | 193 | latex_elements = { 194 | # The paper size ('letterpaper' or 'a4paper'). 195 | # 'papersize': 'letterpaper', 196 | # The font size ('10pt', '11pt' or '12pt'). 197 | # 'pointsize': '10pt', 198 | # Additional stuff for the LaTeX preamble. 199 | # 'preamble': '', 200 | } 201 | 202 | # Grouping the document tree into LaTeX files. List of tuples 203 | # (source start file, target name, title, author, documentclass 204 | # [howto/manual]). 205 | latex_documents = [ 206 | ("index", "metview.tex", "metview Documentation", "Metview python API", "manual"), 207 | ] 208 | 209 | # The name of an image file (relative to this directory) to place at 210 | # the top of the title page. 211 | # latex_logo = None 212 | 213 | # For "manual" documents, if this is true, then toplevel headings 214 | # are parts, not chapters. 215 | # latex_use_parts = False 216 | 217 | # If true, show page references after internal links. 218 | # latex_show_pagerefs = False 219 | 220 | # If true, show URL addresses after external links. 221 | # latex_show_urls = False 222 | 223 | # Documents to append as an appendix to all manuals. 224 | # latex_appendices = [] 225 | 226 | # If false, no module index is generated. 227 | # latex_domain_indices = True 228 | 229 | 230 | # -- Options for manual page output ------------------------------------ 231 | 232 | # One entry per manual page. List of tuples 233 | # (source start file, name, description, authors, manual section). 234 | man_pages = [("index", "metview", "metview Documentation", ["Metview python API"], 1)] 235 | 236 | # If true, show URL addresses after external links. 237 | # man_show_urls = False 238 | 239 | 240 | # -- Options for Texinfo output ---------------------------------------- 241 | 242 | # Grouping the document tree into Texinfo files. List of tuples 243 | # (source start file, target name, title, author, 244 | # dir menu entry, description, category) 245 | texinfo_documents = [ 246 | ( 247 | "index", 248 | "metview", 249 | "metview Documentation", 250 | "Metview python API", 251 | "metview", 252 | "One line description of project.", 253 | "Miscellaneous", 254 | ), 255 | ] 256 | 257 | # Documents to append as an appendix to all manuals. 258 | # texinfo_appendices = [] 259 | 260 | # If false, no module index is generated. 261 | # texinfo_domain_indices = True 262 | 263 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 264 | # texinfo_show_urls = 'footnote' 265 | 266 | # If true, do not generate a @detailmenu in the "Top" node's menu. 267 | # texinfo_no_detailmenu = False 268 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | ======= 4 | metview 5 | ======= 6 | 7 | :Version: |release| 8 | :Date: |today| 9 | 10 | Metview Python API. 11 | 12 | TODO -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | dependencies: 4 | - metview-batch >=5.16.0 5 | - pip 6 | - pip: 7 | - metview >=1.4.0 8 | - scipy 9 | - matplotlib 10 | - cdsapi 11 | - xarray 12 | - pandas 13 | -------------------------------------------------------------------------------- /examples/UC-01.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | Metview Python use case 12 | 13 | UC-01. The Analyst produces plots and files for the Product user 14 | 15 | -------------------------------------------------------------------------------- 16 | 1. Analyst creates plots and files thanks to his Python applications and scripts 17 | that benefits from the underlying tools of the framework 18 | -------------------------------------------------------------------------------- 19 | 20 | Analyst reads data from a GRIB file and derives another quantity from it. Then, 21 | Analyst saves his data as a GRIB file and creates a plot in PNG format. 22 | """ 23 | 24 | import metview as mv 25 | 26 | 27 | mydata = mv.read("../tests/test.grib") 28 | 29 | derived = mydata * 2 + 5 30 | 31 | mv.write("derived_data.grib", derived) 32 | 33 | grid_shade = mv.mcont( 34 | legend=True, 35 | contour=False, 36 | contour_highlight=True, 37 | contour_shade=True, 38 | contour_shade_technique="grid_shading", 39 | contour_shade_max_level_colour="red", 40 | contour_shade_min_level_colour="blue", 41 | contour_shade_colour_direction="clockwise", 42 | ) 43 | 44 | 45 | # Macro-like PNG creation: 46 | png = mv.png_output(output_width=1200, output_name="./myplot") 47 | 48 | mv.plot(png, derived, grid_shade) 49 | 50 | 51 | # Using a different notation: 52 | png_output = {"output_type": "png", "output_width": 1200, "output_name": "./myplot2"} 53 | 54 | mv.plot(derived, grid_shade, **png_output) 55 | -------------------------------------------------------------------------------- /examples/UC-03-bufr.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | Metview Python use case 12 | 13 | The Python analyst reads some BUFR data and plots it in various ways 14 | 15 | -------------------------------------------------------------------------------- 16 | 1. Python analyst reads BUFR data and plots it using the default style 17 | -------------------------------------------------------------------------------- 18 | 19 | -------------------------------------------------------------------------------- 20 | 2. Python analyst reads BUFR data and applies a visual definition 21 | to alter its plotting style 22 | -------------------------------------------------------------------------------- 23 | 24 | -------------------------------------------------------------------------------- 25 | 3. Python analyst reads BUFR data and filters a single parameter from it 26 | and plots it with a colour scale 27 | -------------------------------------------------------------------------------- 28 | 29 | """ 30 | import metview as mv 31 | 32 | 33 | # define a view over the area of interest and set land shading on 34 | 35 | land_shade = mv.mcoast( 36 | map_coastline_land_shade=True, 37 | map_coastline_land_shade_colour="RGB(0.98,0.95,0.82)", 38 | map_coastline_sea_shade=False, 39 | map_coastline_sea_shade_colour="RGB(0.85,0.93,1)", 40 | ) 41 | 42 | area_view = mv.geoview( 43 | map_area_definition="corners", 44 | area=[45.83, -13.87, 62.03, 8.92], 45 | coastlines=land_shade, 46 | ) 47 | 48 | 49 | # Simplest plot: 50 | # NOTE that when plotting a 'raw' BUFR file, Magics will plot synop symbols as shown in 51 | # https://software.ecmwf.int/wiki/display/METV/Data+Part+1 "Plotting BUFR Data" 52 | 53 | obs = mv.read("../tests/obs_3day.bufr") 54 | 55 | mv.setoutput(mv.png_output(output_width=1200, output_name="./obsplot1")) 56 | mv.plot(area_view, obs) 57 | 58 | 59 | # ALTERNATIVELY, add an Observations Plotting visual definition 60 | 61 | obs_plotting = mv.mobs( 62 | obs_temperature=False, 63 | obs_cloud=False, 64 | obs_low_cloud=False, 65 | obs_dewpoint_colour="purple", 66 | ) 67 | 68 | 69 | mv.setoutput(mv.png_output(output_width=1200, output_name="./obsplot2")) 70 | mv.plot(area_view, obs, obs_plotting) 71 | 72 | 73 | # ALTERNATIVELY, if we don't want to plot the whole observations, but instead want to 74 | # extract a specific parameter from the BUFR messages, then we use the Observation Filter 75 | # as shown here: 76 | 77 | # dewpoint_t is a 'geopoints' variable 78 | dewpoint_t = mv.obsfilter(output="geopoints", parameter="012006", data=obs) 79 | 80 | # add an optional Symbol Plotting definition to get nice coloured circles 81 | # at each point 82 | symb_visdef = mv.msymb( 83 | legend=True, 84 | symbol_type="marker", 85 | symbol_table_mode="advanced", 86 | symbol_advanced_table_max_level_colour="red", 87 | symbol_advanced_table_min_level_colour="blue", 88 | symbol_advanced_table_colour_direction="clockwise", 89 | ) 90 | 91 | mv.setoutput(mv.png_output(output_width=1200, output_name="./obsplot3")) 92 | mv.plot(area_view, dewpoint_t, symb_visdef) 93 | -------------------------------------------------------------------------------- /examples/UC-04-grib.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | Metview Python use case 12 | 13 | UC-04. The Analyst retrieves, for a given time interval, the values of 14 | two parameters and combines their values on the same map 15 | 16 | -------------------------------------------------------------------------------- 17 | 1. Analyst retrieves, for a given time interval, the values of two chosen 18 | parameters (e.g. temperature, and geopotential) from a given source (i.e. MARS, 19 | files, observation databases) 20 | -------------------------------------------------------------------------------- 21 | 22 | -------------------------------------------------------------------------------- 23 | 2. Analyst customises many features of his map for each field he wants to plot 24 | (e.g. temperature field as shaded areas and geopotenti2. al field as isolines) 25 | -------------------------------------------------------------------------------- 26 | 27 | -------------------------------------------------------------------------------- 28 | 3. Analyst plots the data 29 | -------------------------------------------------------------------------------- 30 | Analyst plots data variable t2 with contouring definition t_shade_c, and data 31 | variable z with contouring definition mslp_isolines. 32 | The fields will be plotted in the order they appear in the mv.plot() command, 33 | with the shaded temperature at the bottom, and the geopotential on top. 34 | """ 35 | 36 | import metview as mv 37 | 38 | 39 | # read 2m temperature 40 | t2 = mv.read("./t2_for_UC-04.grib") 41 | 42 | # read geopotential 43 | z = mv.read("./z_for_UC-04.grib") 44 | 45 | t_shade_c = mv.mcont( 46 | legend=True, 47 | contour_highlight=False, 48 | contour_level_selection_type="interval", 49 | contour_interval=10, 50 | contour_shade=True, 51 | contour_shade_max_level=60, 52 | contour_shade_min_level=-60, 53 | contour_shade_method="area_fill", 54 | contour_shade_max_level_colour="red", 55 | contour_shade_min_level_colour="blue", 56 | contour_shade_colour_direction="clockwise", 57 | ) 58 | 59 | z_isolines = mv.mcont( 60 | legend=True, 61 | contour_line_thickness=2, 62 | contour_line_colour="black", 63 | contour_highlight_colour="black", 64 | contour_highlight_thickness=4, 65 | contour_level_selection_type="interval", 66 | contour_interval=5, 67 | contour_legend_text="Geopotential", 68 | ) 69 | 70 | mv.setoutput(mv.png_output(output_width=1000, output_name="./gribplot")) 71 | mv.plot(t2, t_shade_c, z, z_isolines) 72 | -------------------------------------------------------------------------------- /examples/UC-05-grib.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | Metview Python use case 12 | 13 | UC-05. The Analyst retrieves certain parameters, computes a derived 14 | parameter and plots its values for a specific time. 15 | 16 | -------------------------------------------------------------------------------- 17 | 1. Analyst filters, the chosen parameters of a file from a given 18 | source (i.e. MARS, files, observation databases) 19 | -------------------------------------------------------------------------------- 20 | 21 | -------------------------------------------------------------------------------- 22 | 2. Analyst computes the desired derived parameter (i.e. wind velocity from zonal 23 | and meridional components) 24 | -------------------------------------------------------------------------------- 25 | 26 | -------------------------------------------------------------------------------- 27 | 3. Analyst plots the derived parameter values 28 | -------------------------------------------------------------------------------- 29 | """ 30 | 31 | import metview as mv 32 | 33 | uv = mv.read("./uv.grib") 34 | 35 | u = mv.read(data=uv, param="u") 36 | v = mv.read(data=uv, param="v") 37 | 38 | wind_speed = mv.sqrt(u * u + v * v) 39 | 40 | mv.setoutput(mv.png_output(output_width=1000, output_name="./uvplot")) 41 | mv.plot(wind_speed) 42 | -------------------------------------------------------------------------------- /examples/UC-07-bufr-pandas.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | Metview Python use case 12 | 13 | UC-07-pandas. The Analyst compute simple differences between observations and analysis 14 | and use pandas to perform further computations 15 | 16 | BUFR version - BUFR is not tabular or gridded, but we can use Metview Python 17 | framework to extract a particular parameter to a tabular format (geopoints) 18 | 19 | -------------------------------------------------------------------------------- 20 | 1. Analyst retrieves the analysis from a gridded data file 21 | -------------------------------------------------------------------------------- 22 | 23 | -------------------------------------------------------------------------------- 24 | 2. Analyst retrieves an observational parameter from a tabular or a gridded file 25 | -------------------------------------------------------------------------------- 26 | 27 | -------------------------------------------------------------------------------- 28 | 3. Analyst calculates the difference between the observational data and the 29 | analysis 30 | -------------------------------------------------------------------------------- 31 | 32 | -------------------------------------------------------------------------------- 33 | 4. Analyst converts this data to a pandas dataframe and computes the number 34 | of outliers based on the zscore 35 | -------------------------------------------------------------------------------- 36 | """ 37 | 38 | import metview as mv 39 | import numpy as np 40 | from scipy import stats 41 | 42 | t2m_grib = mv.read("./t2m_grib.grib") 43 | 44 | obs_3day = mv.read("./obs_3day.bufr") 45 | 46 | t2m_gpt = mv.obsfilter(parameter="012004", output="geopoints", data=obs_3day) 47 | 48 | diff = t2m_grib - t2m_gpt 49 | 50 | df = diff.to_dataframe() 51 | 52 | print(df) 53 | 54 | outliers = np.abs(stats.zscore(df["value"])) > 1.5 55 | 56 | print("# of outliers:", outliers.sum()) 57 | -------------------------------------------------------------------------------- /examples/UC-07-bufr.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | Metview Python use case 12 | 13 | UC-07. The Analyst compute simple differences between observations and analysis 14 | and plot the values 15 | 16 | BUFR version - BUFR is not tabular or gridded, but we can use Metview Python 17 | framework to extract a particular parameter to a tabular format (geopoints) 18 | 19 | -------------------------------------------------------------------------------- 20 | 1. Analyst retrieves the analysis from a gridded data file 21 | -------------------------------------------------------------------------------- 22 | 23 | -------------------------------------------------------------------------------- 24 | 2. Analyst retrieves an observational parameter from a tabular or a gridded file 25 | -------------------------------------------------------------------------------- 26 | 27 | -------------------------------------------------------------------------------- 28 | 3. Analyst calculates the difference between the observational data and the 29 | analysis and classified the field values according to the magnitude of the 30 | difference 31 | -------------------------------------------------------------------------------- 32 | 33 | -------------------------------------------------------------------------------- 34 | 4. Analyst customises many features of his graph in order to create 35 | publication-quality plots 36 | -------------------------------------------------------------------------------- 37 | 38 | -------------------------------------------------------------------------------- 39 | 5. Analyst plots the data 40 | -------------------------------------------------------------------------------- 41 | """ 42 | 43 | import metview as mv 44 | 45 | # define a view over the area of interest 46 | 47 | area_view = mv.geoview(map_area_definition="corners", area=[45.83, -13.87, 62.03, 8.92]) 48 | 49 | t2m_grib = mv.read("./t2m_grib.grib") 50 | 51 | obs_3day = mv.read("./obs_3day.bufr") 52 | 53 | t2m_gpt = mv.obsfilter(parameter="012004", output="geopoints", data=obs_3day) 54 | 55 | diff = t2m_grib - t2m_gpt 56 | 57 | diff_symb = mv.msymb( 58 | legend=True, 59 | symbol_type="marker", 60 | symbol_table_mode="advanced", 61 | ) 62 | 63 | mv.setoutput(mv.png_output(output_width=1000, output_name="./obsdiff1")) 64 | mv.plot(area_view, diff, diff_symb) 65 | 66 | 67 | # Extract geopoints that are hotter by 1 deg or more 68 | # hotter = mv.filter(diff, diff >= 1) 69 | hotter = diff.filter(diff >= 1) 70 | 71 | # Extract geopoints that are colder by 1 deg or more 72 | # colder = mv.filter(diff, diff <= -1) 73 | colder = diff.filter(diff <= -1) 74 | 75 | # Get geopoints that are within +/-1 76 | # exact = mv.filter(diff, (diff > -1) * (diff < 1)) 77 | exact = diff.filter((diff > -1) * (diff < 1)) 78 | 79 | # Symbol visdefs for each classification 80 | red = mv.msymb(symbol_type="marker", symbol_colour="red") 81 | 82 | blue = mv.msymb(symbol_type="marker", symbol_colour="blue") 83 | 84 | grey = mv.msymb(symbol_type="marker", symbol_colour="grey") 85 | 86 | # plot the 'exact' data set with visdef 'grey', 'hotter' with 'red', etc. 87 | mv.setoutput(mv.png_output(output_width=1000, output_name="./obsdiff2")) 88 | mv.plot(area_view, exact, grey, hotter, red, colder, blue) 89 | -------------------------------------------------------------------------------- /examples/data_uv.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/data_uv.grib -------------------------------------------------------------------------------- /examples/ens_storm.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/ens_storm.grib -------------------------------------------------------------------------------- /examples/fc_storm.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/fc_storm.grib -------------------------------------------------------------------------------- /examples/forecast_ml.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/forecast_ml.grib -------------------------------------------------------------------------------- /examples/joachim_uv.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/joachim_uv.grib -------------------------------------------------------------------------------- /examples/joachim_wind_gust.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/joachim_wind_gust.grib -------------------------------------------------------------------------------- /examples/obs_3day.bufr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/obs_3day.bufr -------------------------------------------------------------------------------- /examples/seaIce_CO2_correlation.py: -------------------------------------------------------------------------------- 1 | # ============================================================================== 2 | # Authors: ralf mueller, stephan siemen 3 | # 4 | # 5 | # Plan is to create a plot similar to the scatter plot for co2 concentration and 6 | # september minimum of sea ice extent 7 | # 8 | # reference: 9 | # https://www.mpg.de/10579957/W004_Environment_climate_062-069.pdf, p. 7 10 | # 11 | # ============================================================================== 12 | import os 13 | from ecmwfapi import ECMWFDataServer 14 | from cdo import Cdo 15 | from multiprocessing import Pool 16 | from tarfile import TarFile 17 | import matplotlib.pyplot as plt 18 | import matplotlib.transforms as mtrans 19 | 20 | # basic setup {{{ =========================================================== 21 | server = ECMWFDataServer() 22 | cdo = Cdo() 23 | cdo.debug = True 24 | tasks = 4 25 | startYear = 1980 26 | endYear = 2014 27 | # }}} ========================================================================== 28 | # helper methods {{{ =========================================================== 29 | def getDataFromTarfile(tarfile): 30 | tf = TarFile(tarfile) 31 | members = [m.name for m in tf.getmembers()] 32 | if list(set([os.path.exists(x) for x in members])) != [True]: 33 | tf.extractall() 34 | tf.close 35 | return members 36 | 37 | 38 | def computeTimeSeries(file, varname, useCellArea=False): 39 | if useCellArea: 40 | ofile = cdo.mul( 41 | input="-fldmean -selname,%s %s -fldsum -gridarea %s" 42 | % (varname, file, file), 43 | options="-b F32", 44 | output="_" + os.path.basename(file), 45 | force=False, 46 | ) 47 | else: 48 | ofile = cdo.fldmean( 49 | input="-selname,%s %s" % (varname, file), 50 | options="-b F32", 51 | output="_" + os.path.basename(file), 52 | force=False, 53 | ) 54 | return ofile 55 | 56 | 57 | def computeTimeSeriesOfFilelist(pool, files, varname, ofile, useCellArea=False): 58 | results = dict() 59 | for file in files: 60 | rfile = pool.apply_async(computeTimeSeries, (file, varname, False)) 61 | results[file] = rfile 62 | pool.close() 63 | pool.join() 64 | 65 | for k, v in results.items(): 66 | results[k] = v.get() 67 | 68 | cdo.yearmean( 69 | input="-cat %s" % (" ".join([results[x] for x in files])), 70 | output=ofile, 71 | force=False, 72 | options="-f nc", 73 | ) 74 | return ofile 75 | 76 | 77 | # }}} ========================================================================== 78 | # Sea Ice Cover retrival + processing {{{ 79 | iceCover_file = "ci_interim_%s-%s-NH.grb" % (startYear, endYear) 80 | if not os.path.exists(iceCover_file): 81 | server.retrieve( 82 | { 83 | "stream": "oper", 84 | "levtype": "sfc", 85 | "param": "31.128", 86 | "dataset": "interim", 87 | "step": "0", 88 | "grid": "0.5/0.5", 89 | "time": "12", 90 | "date": "%s-01-01/to/%s-01-01" % (startYear, endYear), 91 | "type": "an", 92 | "class": "ei", 93 | "area": "90/-180/0/180", 94 | "target": iceCover_file, 95 | } 96 | ) 97 | else: 98 | print("use existing file '%s'" % (iceCover_file)) 99 | # compute the nh ice extent: minimum usually happens in September 100 | iceExtent = "ice_extent_%s-%s-daymean-SeptMin.nc" % (startYear, endYear) 101 | cdo.setattribute( 102 | "sea_ice_extent@unit=m2,sea_ice_extent@standard_name=sea_ice_extent", 103 | input="-setname,sea_ice_extent -yearmin -fldsum -mul -selmon,9 %s -gridarea %s" 104 | % (iceCover_file, iceCover_file), 105 | output=iceExtent, 106 | force=False, 107 | options="-f nc", 108 | ) 109 | iceExtent_ds = cdo.readXDataset(iceExtent) 110 | # }}} ========================================================================== 111 | # {{{ CO2 retrieval + processing =========================================================== 112 | # cams return tarballs of netcdf files 113 | co2_tarball = "co2_totalColumn_%s-%s.tar" % (startYear, endYear) 114 | if not os.path.exists(co2_tarball): 115 | server.retrieve( 116 | { # CO2 117 | "dataset": "cams_ghg_inversions", 118 | "datatype": "ra", 119 | "date": "%s-01-01/to/%s-01-01" % (startYear, endYear), 120 | "frequency": "3h", 121 | "param": "co2", 122 | "quantity": "total_column", 123 | "version": "v16r2", 124 | "target": co2_tarball, 125 | } 126 | ) 127 | else: 128 | print("use existing file '%s'" % (co2_tarball)) 129 | co2_files = getDataFromTarfile(co2_tarball) 130 | co2_timeSeries = "co2_timeseries_%s-%s.nc" % (startYear, endYear) 131 | computeTimeSeriesOfFilelist(Pool(tasks), co2_files, "XCO2", co2_timeSeries, False) 132 | co2_ds = cdo.readXDataset(co2_timeSeries) 133 | 134 | # }}} ========================================================================== 135 | # scatter plot {{{ ============================================================= 136 | # some debugging output 137 | iceExtent_ds.info() 138 | co2_ds.info() 139 | # shaping the data for plotting it 140 | xSelection = co2_ds.sel(time=slice("%s-01-01" % (startYear), "%s-01-01" % (endYear))) 141 | ySelection = iceExtent_ds.sel( 142 | time=slice("%s-01-01" % (startYear), "%s-01-01" % (endYear)) 143 | ) 144 | # create the final scatter plot 145 | fig = plt.figure(figsize=(10, 7)) 146 | ax = plt.subplot(1, 1, 1) 147 | trans_offset = mtrans.offset_copy( 148 | ax.transData, fig=fig, x=0.05, y=-0.20, units="inches" 149 | ) # inches because we are in UK 150 | 151 | x = xSelection.to_array()[1, :, 0, 0, 0] 152 | y = ySelection.to_array()[1, :, 0, 0, 0] 153 | plt.scatter(x, y) 154 | # put years as labels 155 | years = xSelection.time.dt.year 156 | for _x, _y, _year in zip(x, y, years): 157 | plt.text(_x, _y, "%d" % (_year), transform=trans_offset) 158 | 159 | plt.grid(True) 160 | plt.ylabel("sea ice extent [m2]") 161 | plt.xlabel("co2 concentration [ppm]") 162 | plt.title("Correlation of NH Sea Ice extent minimum and CO2 emissions") 163 | plt.savefig("seaIce_CO2_correlation.png") 164 | # }}} ========================================================================== 165 | # vim:fdm=marker 166 | -------------------------------------------------------------------------------- /examples/t2_for_UC-04.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/t2_for_UC-04.grib -------------------------------------------------------------------------------- /examples/t2m_grib.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/t2m_grib.grib -------------------------------------------------------------------------------- /examples/t_n48.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/t_n48.grib -------------------------------------------------------------------------------- /examples/uv.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/uv.grib -------------------------------------------------------------------------------- /examples/wgust_ens.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/wgust_ens.grib -------------------------------------------------------------------------------- /examples/wgust_era5.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/wgust_era5.nc -------------------------------------------------------------------------------- /examples/z500_ens.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/z500_ens.grib -------------------------------------------------------------------------------- /examples/z_for_UC-04.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/examples/z_for_UC-04.grib -------------------------------------------------------------------------------- /metview/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | # requires a Python 3 interpreter 11 | import os 12 | import sys 13 | 14 | if sys.version_info[0] < 3: # pragma: no cover 15 | raise EnvironmentError( 16 | "Metview's Python interface requires Python 3. You are using Python " 17 | + repr(sys.version_info) 18 | ) 19 | 20 | 21 | # if the user has started via "python -m metview selfcheck" 22 | # then we do not want to import anything yet because we want to 23 | # catch errors differently 24 | 25 | if len(sys.argv) != 2 or sys.argv[0] != "-m" or sys.argv[1] != "selfcheck": 26 | try: 27 | from . import bindings as _bindings 28 | 29 | _bindings.bind_functions(globals(), module_name=__name__) 30 | 31 | # Remove "_bindings" from the public API. 32 | del _bindings 33 | 34 | from . import gallery 35 | from . import style 36 | from metview.metviewpy import indexer 37 | from . import title 38 | from . import layout 39 | from . import ui 40 | from . import compat 41 | 42 | except Exception as exp: 43 | if "METVIEW_PYTHON_ONLY" not in os.environ: 44 | raise exp 45 | -------------------------------------------------------------------------------- /metview/__main__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | import argparse 11 | import sys 12 | 13 | 14 | def main(argv=None): 15 | parser = argparse.ArgumentParser() 16 | parser.add_argument("command") 17 | args = parser.parse_args(args=argv) 18 | if args.command == "selfcheck": 19 | sys.argv = [] 20 | print("Trying to connect to a Metview installation...") 21 | try: 22 | from . import bindings as _bindings 23 | except Exception as exp: 24 | print("Could not find a valid Metview installation") 25 | raise (exp) 26 | mv = dict() 27 | _bindings.bind_functions(mv, module_name="mv") 28 | del _bindings 29 | 30 | try: 31 | mv["print"]("Hello world - printed from Metview!") 32 | except Exception as exp: 33 | print("Could not print a greeting from Metview") 34 | raise (exp) 35 | 36 | mv_version_f = mv["version_info"] 37 | mv_version = mv_version_f() 38 | mv_maj = str(int(mv_version["metview_major"])) 39 | mv_min = str(int(mv_version["metview_minor"])) 40 | mv_rev = str(int(mv_version["metview_revision"])) 41 | mv_version_string = mv_maj + "." + mv_min + "." + mv_rev 42 | print("Metview version", mv_version_string, "found") 43 | 44 | print("Your system is ready.") 45 | else: 46 | raise RuntimeError( 47 | "Command not recognised %r. See usage with --help." % args.command 48 | ) 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /metview/compat.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | import numpy as np 11 | 12 | import metview as mv 13 | 14 | 15 | def concat(*args): 16 | if len(args) <= 1: 17 | raise ValueError(f"concat requires at least 2 arguments, got only {len(args)}") 18 | 19 | vals = list(args) 20 | if vals[0] is None: 21 | vals.pop(0) 22 | if len(vals) == 1: 23 | return vals[0] 24 | 25 | # concatenate strings 26 | if isinstance(vals[0], str): 27 | return "".join([str(v) for v in vals]) 28 | elif isinstance(vals[0], np.ndarray): 29 | return np.concatenate(vals) 30 | elif isinstance( 31 | vals[0], 32 | (mv.Fieldset, mv.bindings.Geopoints, mv.bindings.GeopointSet, mv.bindings.Bufr), 33 | ): 34 | return mv.merge(*vals) 35 | elif isinstance(vals[0], list): 36 | first = [] 37 | for v in vals: 38 | if isinstance(v, list): 39 | first.extend(v) 40 | else: 41 | first.append(v) 42 | return first 43 | 44 | raise ValueError(f"concat failed to handle the specified arguments={args}") 45 | 46 | 47 | def index4(vals, start, stop, step, num): 48 | """ 49 | Return a boolean index ndarray to select a subset of elements from ``vals``. 50 | 51 | Parameters 52 | ---------- 53 | vals: ndarray 54 | Input array. 55 | start: int 56 | Start index. 57 | stop: int 58 | Stop index. 59 | step: int 60 | Step. 61 | num: int 62 | Number of elements to be selected for each step. 63 | 64 | Returns 65 | ------- 66 | ndarray 67 | Boolean index array 68 | 69 | Examples 70 | -------- 71 | 72 | >>> import numpy as np 73 | >>> import metview 74 | >>> vals = np.array(list(range(12))) 75 | >>> r = index4(vals, 0, 11, 4, 2) 76 | [ True True True True True True False False False False False False] 77 | a[] 78 | >>> vals[r] 79 | array([0, 1, 4, 5, 8, 9]) 80 | 81 | """ 82 | num = min(num, step) 83 | m = np.zeros(len(vals), dtype=bool) 84 | for i in range(start, stop, step): 85 | m[i : i + num] = 1 86 | return m 87 | -------------------------------------------------------------------------------- /metview/etc/areas.yaml: -------------------------------------------------------------------------------- 1 | - base: 2 | map_area_definition: "corners" 3 | area: [-90,-180,90,180] -------------------------------------------------------------------------------- /metview/etc/dataset_template.yaml: -------------------------------------------------------------------------------- 1 | experiments: 2 | - myexp: 3 | label: "myexp" 4 | dir: __ROOTDIR__/myexp 5 | fname : "*.grib" 6 | - track: 7 | type: track 8 | dir: __ROOTDIR__/track 9 | fname: "*.csv" 10 | skiprows: 10 11 | date_index: 0 12 | time_index: 1 13 | lat_index: 3 14 | lon_index: 4 -------------------------------------------------------------------------------- /metview/etc/map_styles.yaml: -------------------------------------------------------------------------------- 1 | base: 2 | map_coastline_land_shade: "off" 3 | map_coastline_sea_shade: 'off' 4 | base_diff: 5 | map_coastline_colour: CHARCOAL 6 | map_coastline_land_shade: "on" 7 | map_coastline_land_shade_colour: "RGB(0.8549,0.8549,0.8549)" 8 | map_coastline_sea_shade: 'off' 9 | map_coastline_land_shade_colour: WHITE 10 | grey_light_blue: 11 | map_coastline_land_shade: "on" 12 | map_coastline_land_shade_colour: "grey" 13 | map_coastline_sea_shade: "on" 14 | map_coastline_sea_shade_colour: "RGB(0.86,0.94,1)" 15 | map_boundaries: "off" 16 | map_boundaries_colour: "RGB(0.21,0.21,0.21)" 17 | map_disputed_boundaries: "off" 18 | map_administrative_boundaries: "off" 19 | map_grid_colour: "RGB(0.294,0.294,0.2941)" 20 | map_label_colour: "RGB(0.294,0.294,0.2941)" 21 | black_coast_only: 22 | map_coastline: 'on' 23 | map_coastline_land_shade: 'off' 24 | map_coastline_sea_shade: 'off' 25 | # map_coastline_colour: BLACK 26 | black_grey: 27 | map_coastline_land_shade: 'on' 28 | map_coastline_land_shade_colour: black 29 | map_coastline_sea_shade: 'on' 30 | map_coastline_sea_shade_colour: RGB(0.7767,0.7932,0.8076) 31 | map_coastline: 'on' 32 | map_coastline_colour: BACKGROUND 33 | map_coastline_style: solid 34 | map_coastline_thickness: 1 35 | blue_sea_only: 36 | map_coastline: 'on' 37 | map_coastline_sea_shade: 'on' 38 | map_coastline_sea_shade_colour: RGB(0.7373,0.9294,0.9608) 39 | brown: 40 | map_coastline: 'on' 41 | map_coastline_land_shade: 'on' 42 | map_coastline_land_shade_colour: RGB(0.7216,0.6863,0.6471) 43 | map_coastline_sea_shade: 'on' 44 | map_coastline_sea_shade_colour: RGB(0.9059,0.9176,0.9373) 45 | map_coastline_colour: TAN 46 | cream_blue: 47 | map_coastline: 'on' 48 | map_coastline_land_shade: 'on' 49 | map_coastline_land_shade_colour: CREAM 50 | map_coastline_sea_shade: 'on' 51 | map_coastline_sea_shade_colour: SKY 52 | cream_land_only: 53 | map_coastline_land_shade: 'on' 54 | map_coastline_land_shade_colour: CREAM 55 | map_coastline_sea_shade: 'off' 56 | map_coastline_sea_shade_colour: WHITE 57 | map_coastline_colour: BLACK 58 | map_coastline_style: solid 59 | map_coastline_thickness: 1 60 | dark_1: 61 | map_coastline: 'on' 62 | map_coastline_land_shade: 'on' 63 | map_coastline_land_shade_colour: CHARCOAL 64 | map_coastline_sea_shade: 'on' 65 | map_coastline_sea_shade_colour: RGB(0.3677,0.3677,0.4009) 66 | map_coastline_colour: WHITE 67 | dark_2: 68 | map_coastline: 'on' 69 | map_coastline_land_shade: 'on' 70 | map_coastline_land_shade_colour: CHARCOAL 71 | map_coastline_sea_shade: 'on' 72 | map_coastline_sea_shade_colour: RGB(0.2471,0.2471,0.2471) 73 | map_coastline_colour: WHITE 74 | dark_3: 75 | map_coastline: 'on' 76 | map_coastline_land_shade: 'on' 77 | map_coastline_land_shade_colour: RGB(0.4431,0.4431,0.4431) 78 | map_coastline_sea_shade: 'on' 79 | map_coastline_sea_shade_colour: RGB(0.3176,0.3176,0.3176) 80 | green_blue: 81 | map_coastline: 'on' 82 | map_coastline_land_shade: 'on' 83 | map_coastline_land_shade_colour: RGB(0.2233,0.7101,0.3369) 84 | map_coastline_sea_shade: 'on' 85 | map_coastline_sea_shade_colour: RGB(0.4923,0.6884,0.8489) 86 | map_coastline_colour: BLACK 87 | green_land_only: 88 | map_coastline: 'on' 89 | map_coastline_land_shade: 'on' 90 | map_coastline_land_shade_colour: RGB(0.1020,0.4000,0.0000) 91 | map_coastline_sea_shade: 'off' 92 | green_navy: 93 | map_coastline: 'on' 94 | map_coastline_land_shade: 'on' 95 | map_coastline_land_shade_colour: EVERGREEN 96 | map_coastline_sea_shade: 'on' 97 | map_coastline_sea_shade_colour: RGB(0.0000,0.0000,0.5020) 98 | grey_1: 99 | map_coastline: 'on' 100 | map_coastline_land_shade: 'on' 101 | map_coastline_land_shade_colour: RGB(0.3725,0.4157,0.5451) 102 | map_coastline_sea_shade: 'on' 103 | map_coastline_sea_shade_colour: RGB(0.7767,0.7932,0.8076) 104 | map_coastline_colour: BLACK 105 | map_coastline_style: solid 106 | map_coastline_thickness: 1 107 | grey_2: 108 | map_coastline: 'on' 109 | map_coastline_land_shade: 'on' 110 | map_coastline_land_shade_colour: GREY 111 | map_coastline_sea_shade: 'on' 112 | map_coastline_sea_shade_colour: RGB(0.9059,0.9176,0.9373) 113 | map_coastline_colour: CHARCOAL 114 | grey_3: 115 | map_coastline: 'on' 116 | map_coastline_land_shade: 'on' 117 | map_coastline_land_shade_colour: RGB(0.5373,0.5373,0.5373) 118 | map_coastline_sea_shade: 'on' 119 | map_coastline_sea_shade_colour: RGB(0.8392,0.8510,0.8706) 120 | map_coastline_colour: CHARCOAL 121 | grey_4: 122 | map_coastline: 'on' 123 | map_coastline_land_shade: 'on' 124 | map_coastline_land_shade_colour: RGB(0.5569,0.5569,0.5569) 125 | map_coastline_sea_shade: 'on' 126 | map_coastline_sea_shade_colour: RGB(0.8000,0.8000,0.8000) 127 | map_coastline_colour: CHARCOAL 128 | grey_blue: 129 | map_coastline: 'on' 130 | map_coastline_land_shade: 'on' 131 | map_coastline_land_shade_colour: GREY 132 | map_coastline_sea_shade: 'on' 133 | map_coastline_sea_shade_colour: RGB(0.7922,0.8431,0.9412) 134 | map_coastline_colour: CHARCOAL 135 | grey_darker_land_only: 136 | map_coastline: 'on' 137 | map_coastline_land_shade: 'on' 138 | map_coastline_land_shade_colour: RGB(0.5020,0.5020,0.5020) 139 | map_coastline_sea_shade: 'off' 140 | grey_land_only: 141 | map_coastline: 'on' 142 | map_coastline_land_shade: 'on' 143 | map_coastline_land_shade_colour: RGB(0.8549,0.8549,0.8549) 144 | map_coastline_sea_shade: 'off' 145 | white_coast_only: 146 | map_coastline: 'on' 147 | map_coastline_land_shade: 'off' 148 | map_coastline_sea_shade: 'off' 149 | map_coastline_colour: WHITE -------------------------------------------------------------------------------- /metview/etc/map_styles_template.yaml: -------------------------------------------------------------------------------- 1 | base: 2 | map_coastline_resolution: "low" 3 | map_coastline_land_shade: "on" 4 | map_coastline_land_shade_colour: "grey" 5 | map_coastline_sea_shade: "on" 6 | map_coastline_sea_shade_colour: "RGB(0.86,0.94,1)" 7 | map_boundaries: "on" 8 | map_boundaries_colour: "RGB(0.21,0.21,0.21)" 9 | map_disputed_boundaries: "off" 10 | map_administrative_boundaries: "off" 11 | map_grid_latitude_increment: 10 12 | map_grid_longitude_increment: 10 13 | map_grid_colour: "RGB(0.294,0.294,0.2941)" 14 | map_label_colour: "RGB(0.294,0.294,0.2941)" 15 | base_diff: 16 | map_coastline_resolution: "low" 17 | map_coastline_colour: CHARCOAL 18 | map_coastline_land_shade: "on" 19 | map_coastline_land_shade_colour: "RGB(0.8549,0.8549,0.8549)" 20 | map_coastline_sea_shade: "off" 21 | map_coastline_sea_shade_colour: "white" 22 | map_boundaries: "on" 23 | map_boundaries_colour: "RGB(0.21,0.21,0.21)" 24 | map_disputed_boundaries: "off" 25 | map_administrative_boundaries: "off" 26 | map_grid_latitude_increment: 10 27 | map_grid_longitude_increment: 10 28 | map_grid_colour: "RGB(0.294,0.294,0.2941)" 29 | map_label_colour: "RGB(0.294,0.294,0.2941)" -------------------------------------------------------------------------------- /metview/etc/params.yaml: -------------------------------------------------------------------------------- 1 | # -------------------- 2 | # SCALAR PARAMETERS 3 | # -------------------- 4 | # 2m dewpoint temperature 5 | - 6 | styles: 7 | diff: 8 | - diff_t 9 | match: 10 | - 11 | info_name: 2d 12 | - 13 | shortName: 2d 14 | paramId: 168 15 | # 2m temperature 16 | - 17 | styles: 18 | diff: 19 | - diff_t 20 | match: 21 | - 22 | info_name: 2t 23 | - 24 | shortName: 2t 25 | paramId: 167 26 | # 10m wind gust 3h 27 | - 28 | styles: 29 | diff: 30 | - diff_10fg3 31 | match: 32 | - 33 | info_name: 10fg3 34 | - 35 | shortName: 10fg3 36 | paramId: 228028 37 | # 10m wind speed 38 | - 39 | styles: 40 | diff: 41 | - diff_speed_10 42 | match: 43 | - 44 | info_name: 10si 45 | - 46 | shortName: 10si 47 | paramId: 207 48 | # 10m u wind component 49 | - 50 | styles: 51 | basic: 52 | - u_10m 53 | diff: 54 | - diff_u_10 55 | match: 56 | - 57 | info_name: 10u 58 | - 59 | shortName: 10u 60 | paramId: 165 61 | # 10m v wind component 62 | - 63 | styles: 64 | basic: 65 | - u_10m 66 | diff: 67 | - diff_u_10 68 | match: 69 | - 70 | info_name: 10v 71 | - 72 | shortName: 10v 73 | paramId: 166 74 | # absolute vorticity 75 | - 76 | styles: 77 | basic: 78 | - vo 79 | diff: 80 | - diff_vo 81 | scaling: 82 | - xs 83 | match: 84 | - 85 | info_name: absv 86 | - 87 | shortName: absv 88 | paramId: 3041 89 | # equivalent potential temperature 90 | - 91 | styles: 92 | basic: 93 | - pt 94 | diff: 95 | - diff_pt 96 | match: 97 | - 98 | info_name: eqpt 99 | - 100 | shortName: eqpt 101 | paramId: 4 102 | # geopotential 103 | - 104 | styles: 105 | basic: 106 | - z 107 | diff: 108 | - diff_z 109 | match: 110 | - 111 | info_name: z 112 | - 113 | shortName: z 114 | paramId: 129 115 | # mean_sea_level_pressure: 116 | - 117 | styles: 118 | basic: 119 | - msl 120 | diff: 121 | - diff_msl 122 | match: 123 | - 124 | info_name: msl 125 | - 126 | shortName: msl 127 | paramId: 151 128 | # potential temperature 129 | - 130 | styles: 131 | basic: 132 | - pt 133 | diff: 134 | - diff_pt 135 | match: 136 | - 137 | info_name: pt 138 | - 139 | shortName: pt 140 | paramId: 3 141 | # potential vorticity 142 | - 143 | styles: 144 | basic: 145 | - pv 146 | diff: 147 | - diff_pv 148 | scaling: 149 | - xs 150 | match: 151 | - 152 | info_name: pv 153 | # relative humidity 154 | - 155 | styles: 156 | diff: 157 | - diff_r 158 | xs: 159 | - xs_r 160 | match: 161 | - 162 | info_name: r 163 | - 164 | shortName: r 165 | paramId: 157 166 | # relative vorticity 167 | - 168 | styles: 169 | basic: 170 | - vo 171 | diff: 172 | - diff_vo 173 | match: 174 | - 175 | info_name: vo 176 | - 177 | shortName: vo 178 | paramId: 138 179 | scaling: 180 | - xs 181 | # specific humidity 182 | - 183 | styles: 184 | diff: 185 | - diff_q 186 | xs: 187 | - xs_q 188 | scaling: 189 | - xs 190 | match: 191 | - 192 | info_name: q 193 | - 194 | shortName: q 195 | paramId: 133 196 | # sea surface temperature 197 | - 198 | styles: 199 | diff: 200 | - diff_t 201 | match: 202 | - 203 | info_name: sst 204 | - 205 | shortName: sst 206 | paramId: 34 207 | # temperature 208 | - 209 | styles: 210 | diff: 211 | - diff_t 212 | xs: 213 | - xs_t 214 | scaling: 215 | - xs 216 | match: 217 | - 218 | info_name: t 219 | - 220 | shortName: t 221 | paramId: 130 222 | # total precipitation 223 | - 224 | styles: 225 | diff: 226 | - diff_tp 227 | match: 228 | - 229 | info_name: tp 230 | - 231 | shortName: tp 232 | paramId: 228 233 | # vertical (pressure) velocity 234 | - 235 | styles: 236 | basic: 237 | - w 238 | diff: 239 | - diff_w 240 | match: 241 | - 242 | info_name: w 243 | - 244 | shortName: w 245 | paramId: 135 246 | # u wind component 1000hPa 247 | - 248 | styles: 249 | basic: 250 | - u_10m 251 | diff: 252 | - diff_u_10 253 | match: 254 | - 255 | info_name: u 256 | levtype: pl 257 | levelist: 1000 258 | - 259 | shortName: u 260 | paramId: 131 261 | levtype: pl 262 | levelist: 1000 263 | # u wind component around 700 hPa 264 | - 265 | styles: 266 | basic: 267 | - u_700 268 | diff: 269 | - diff_u_700 270 | match: 271 | - 272 | info_name: u 273 | levtype: pl 274 | levelist: [925, 850, 700] 275 | - 276 | shortName: u 277 | paramId: 131 278 | levtype: pl 279 | levelist: [925, 850, 700] 280 | # u wind component around 500 hPa + 100 hPa 281 | - 282 | styles: 283 | basic: 284 | - u_500 285 | diff: 286 | - diff_u_500 287 | match: 288 | - 289 | info_name: u 290 | levtype: pl 291 | levelist: [600, 500, 100] 292 | - 293 | shortName: u 294 | paramId: 131 295 | levtype: pl 296 | levelist: [600, 500, 100] 297 | # u wind component around 200 hPa 298 | - 299 | styles: 300 | basic: 301 | - u_200 302 | diff: 303 | - diff_u_200 304 | match: 305 | - 306 | info_name: u 307 | levtype: pl 308 | levelist: [400, 300, 250, 200, 150] 309 | - 310 | shortName: u 311 | paramId: 131 312 | levtype: pl 313 | levelist: [400, 300, 250, 200, 150] 314 | # v wind component 1000hPa 315 | - 316 | styles: 317 | basic: 318 | - u_10m 319 | diff: 320 | - diff_u_10 321 | match: 322 | - 323 | info_name: v 324 | levtype: pl 325 | levelist: 1000 326 | - 327 | shortName: v 328 | paramId: 132 329 | levtype: pl 330 | levelist: 1000 331 | # v wind component around 700 hPa 332 | - 333 | styles: 334 | basic: 335 | - u_700 336 | diff: 337 | - diff_u_700 338 | match: 339 | - 340 | info_name: v 341 | levtype: pl 342 | levelist: [925, 850, 700] 343 | - 344 | shortName: v 345 | paramId: 132 346 | levtype: pl 347 | levelist: [925, 850, 700] 348 | # v wind component around 500 hPa + 100 hPa 349 | - 350 | styles: 351 | basic: 352 | - u_500 353 | diff: 354 | - diff_u_500 355 | match: 356 | - 357 | info_name: v 358 | levtype: pl 359 | levelist: [600, 500, 100] 360 | - 361 | shortName: v 362 | paramId: 132 363 | levtype: pl 364 | levelist: [600, 500, 100] 365 | # v wind component around 200 hPa 366 | - 367 | styles: 368 | basic: 369 | - u_200 370 | diff: 371 | - diff_u_200 372 | match: 373 | - 374 | info_name: v 375 | levtype: pl 376 | levelist: [400, 300, 250, 200, 150] 377 | - 378 | shortName: v 379 | paramId: 132 380 | levtype: pl 381 | levelist: [400, 300, 250, 200, 150] 382 | # wind speed 1000hPa 383 | - 384 | styles: 385 | diff: 386 | - diff_speed_10 387 | match: 388 | - 389 | info_name: ws 390 | levtype: pl 391 | levelist: 1000 392 | - 393 | shortName: ws 394 | paramId: 10 395 | levtype: pl 396 | levelist: 1000 397 | # wind speed around 700 hPa + 100 hPa 398 | - 399 | styles: 400 | diff: 401 | - diff_speed_700 402 | match: 403 | - 404 | info_name: ws 405 | levtype: pl 406 | levelist: [925, 850, 700, 600, 100] 407 | - 408 | shortName: ws 409 | paramId: 10 410 | levtype: pl 411 | levelist: [925, 850, 700, 600, 100] 412 | # wind speed around 200 hPa 413 | - 414 | styles: 415 | diff: 416 | - diff_speed_200 417 | match: 418 | - 419 | info_name: ws 420 | levtype: pl 421 | levelist: [500, 400, 300, 250, 200, 150] 422 | - 423 | shortName: ws 424 | paramId: 10 425 | levtype: pl 426 | levelist: [500, 400, 300, 250, 200, 150] 427 | # -------------------- 428 | # VECTOR PARAMETERS 429 | # -------------------- 430 | # wind (generic) 431 | - 432 | type: vector 433 | styles: 434 | basic: 435 | - arrow_blue_red 436 | - arrow_black 437 | match: 438 | - 439 | info_name: wind 440 | # wind 10 m 441 | - 442 | type: vector 443 | styles: 444 | basic: 445 | - arrow_blue_red_10 446 | - arrow_black_10 447 | - flag_black_10 448 | match: 449 | - 450 | info_name: wind10m 451 | # wind 1000 hPa 452 | - 453 | type: vector 454 | styles: 455 | basic: 456 | - arrow_blue_red_10 457 | - arrow_black_10 458 | - flag_black_10 459 | match: 460 | - 461 | info_name: wind 462 | levtype: pl 463 | levelist: 1000 464 | # wind 925-850 hPa 465 | - 466 | type: vector 467 | styles: 468 | basic: 469 | - arrow_blue_red_850 470 | - arrow_black_850 471 | - flag_black_upper 472 | match: 473 | - 474 | info_name: wind 475 | levtype: pl 476 | levelist: [925,850] 477 | # wind 700-600 hPa 478 | - 479 | type: vector 480 | styles: 481 | basic: 482 | - arrow_blue_red_700 483 | - arrow_black_700 484 | - flag_black_upper 485 | match: 486 | - 487 | info_name: wind 488 | levtype: pl 489 | levelist: [700, 600] 490 | # wind 500-400 hPa 491 | - 492 | type: vector 493 | styles: 494 | basic: 495 | - arrow_blue_red_500 496 | - arrow_black_500 497 | - flag_black_upper 498 | match: 499 | - 500 | info_name: wind 501 | levtype: pl 502 | levelist: [500, 400] 503 | # wind 300-150 hPa 504 | - 505 | type: vector 506 | styles: 507 | basic: 508 | - arrow_blue_red_200 509 | - arrow_black_200 510 | - flag_black_upper 511 | match: 512 | - 513 | info_name: wind 514 | levtype: pl 515 | levelist: [300, 250, 200, 150] 516 | # wind 100- hPa 517 | - 518 | type: vector 519 | styles: 520 | basic: 521 | - arrow_blue_red_100 522 | - arrow_black_100 523 | - flag_black_upper 524 | match: 525 | - 526 | info_name: wind 527 | levtype: pl 528 | levelist: 100 529 | # wind3d (generic) 530 | - 531 | type: vector 532 | styles: 533 | basic: 534 | - xs_arrow_black 535 | match: 536 | - 537 | info_name: wind3d 538 | # ------------------------------------------- 539 | # PARAMETERS REPRESENTING GEOMETRIC SHAPES 540 | # ------------------------------------------- 541 | - 542 | styles: 543 | basic: 544 | - box 545 | match: 546 | - 547 | info_name: box 548 | - 549 | styles: 550 | basic: 551 | - frame 552 | match: 553 | - 554 | info_name: frame 555 | 556 | # ------------------------------------------- 557 | # TRACKS 558 | # ------------------------------------------- 559 | - 560 | styles: 561 | basic: 562 | - track 563 | match: 564 | - 565 | info_name: track 566 | 567 | # --------------------------------- 568 | # PARAMETERS WITHOUT A SHORTNAME 569 | # --------------------------------- 570 | - 571 | styles: 572 | basic: 573 | - q_flux 574 | diff: 575 | - diff_q_flux 576 | match: 577 | - 578 | info_name: qcl 579 | - 580 | paramId: 110 581 | - 582 | styles: 583 | basic: 584 | - q_flux 585 | diff: 586 | - diff_q_flux 587 | match: 588 | - 589 | info_name: qcon 590 | - 591 | paramId: 106 592 | - 593 | styles: 594 | basic: 595 | - q_flux 596 | diff: 597 | - diff_q_flux 598 | match: 599 | - 600 | info_name: qdyn 601 | - 602 | paramId: 94 603 | - 604 | styles: 605 | basic: 606 | - q_flux 607 | diff: 608 | - diff_q_flux 609 | match: 610 | - 611 | info_name: qvdiff 612 | - 613 | paramId: 99 614 | - 615 | styles: 616 | basic: 617 | - t_flux 618 | diff: 619 | - diff_t_flux 620 | match: 621 | - 622 | info_name: tcl 623 | - 624 | paramId: 109 625 | - 626 | styles: 627 | basic: 628 | - t_flux 629 | diff: 630 | - diff_t_flux 631 | match: 632 | - 633 | info_name: tcon 634 | - 635 | paramId: 105 636 | - 637 | styles: 638 | basic: 639 | - t_flux 640 | diff: 641 | - diff_t_flux 642 | match: 643 | - 644 | info_name: tdyn 645 | - 646 | paramId: 93 647 | - 648 | styles: 649 | basic: 650 | - t_flux 651 | diff: 652 | - diff_t_flux 653 | match: 654 | - 655 | info_name: trad 656 | - 657 | paramId: 95 658 | - 659 | styles: 660 | basic: 661 | - t_flux 662 | diff: 663 | - diff_t_flux 664 | match: 665 | - 666 | info_name: tvdiff 667 | - 668 | paramId: 98 669 | -------------------------------------------------------------------------------- /metview/etc/scaling_ecmwf.yaml: -------------------------------------------------------------------------------- 1 | 10**5/sec: 2 | match: [] 3 | paramId: 4 | - '138' 5 | - '155' 6 | - '3041' 7 | shortName: 8 | - vo 9 | - absv 10 | - d 11 | C: 12 | match: 13 | - centre: '7' 14 | paramId: '11' 15 | - centre: '46' 16 | paramId: '128' 17 | - centre: '46' 18 | paramId: '129' 19 | - centre: '46' 20 | paramId: '158' 21 | - centre: '46' 22 | paramId: '175' 23 | - centre: '46' 24 | paramId: '187' 25 | - centre: '46' 26 | paramId: '188' 27 | - centre: '46' 28 | paramId: '189' 29 | - centre: '46' 30 | paramId: '190' 31 | - centre: '46' 32 | paramId: '191' 33 | - centre: '46' 34 | paramId: '232' 35 | - centre: '46' 36 | paramId: '234' 37 | - centre: '46' 38 | paramId: '243' 39 | - centre: '46' 40 | paramId: '251' 41 | - centre: '46' 42 | paramId: '253' 43 | paramId: 44 | - '34' 45 | - '35' 46 | - '36' 47 | - '37' 48 | - '38' 49 | - '51' 50 | - '52' 51 | - '121' 52 | - '122' 53 | - '130' 54 | - '139' 55 | - '167' 56 | - '168' 57 | - '170' 58 | - '183' 59 | - '201' 60 | - '202' 61 | - '208' 62 | - '235' 63 | - '236' 64 | - '238' 65 | - '228004' 66 | - '228008' 67 | - '228010' 68 | - '228011' 69 | - '228013' 70 | - '228026' 71 | - '228027' 72 | - '260510' 73 | shortName: 74 | - mn2t6 75 | - ltlt 76 | - lmlt 77 | - mn2t24 78 | - skt 79 | - 2d 80 | - mx2t24 81 | - clbt 82 | - lict 83 | - mn2t 84 | - mn2t3 85 | - istl4 86 | - lblt 87 | - mx2t3 88 | - istl3 89 | - stl1 90 | - istl2 91 | - tsn 92 | - mx2t6 93 | - mean2t 94 | - stl2 95 | - mx2t 96 | - sst 97 | - stl4 98 | - stl3 99 | - 2t 100 | - istl1 101 | - t 102 | Dob: 103 | match: [] 104 | paramId: 105 | - '206' 106 | shortName: [] 107 | cm: 108 | match: [] 109 | paramId: 110 | - '44' 111 | - '45' 112 | - '141' 113 | - '149' 114 | - '160' 115 | - '173' 116 | shortName: 117 | - smlt 118 | - es 119 | dam: 120 | match: [] 121 | paramId: 122 | - '129' 123 | - '156' 124 | - '206' 125 | - '171129' 126 | shortName: 127 | - gh 128 | - za 129 | - z 130 | g/kg: 131 | match: [] 132 | paramId: 133 | - '133' 134 | - '203' 135 | - '233' 136 | - '246' 137 | - '247' 138 | shortName: 139 | - q 140 | μg/kg: 141 | match: [] 142 | paramId: 143 | - '217004' 144 | - '210123' 145 | - '210203' 146 | - '210124' 147 | - '217006' 148 | - '217028' 149 | - '210121' 150 | - '217027' 151 | - '217030' 152 | shortName: 153 | - ch4_c 154 | - co 155 | - go3 156 | - hcho 157 | - hno3 158 | - ho2 159 | - no2 160 | - no 161 | - oh 162 | - so2 163 | hPa: 164 | match: [] 165 | paramId: 166 | - '54' 167 | - '134' 168 | - '151' 169 | shortName: 170 | - msl 171 | - sp 172 | kg m**-2 h**-1: 173 | match: [] 174 | paramId: 175 | - '3059' 176 | - '3064' 177 | - '174142' 178 | - '174143' 179 | - '228218' 180 | - '228219' 181 | - '228220' 182 | - '228221' 183 | - '228222' 184 | - '228223' 185 | - '228224' 186 | - '228225' 187 | - '228226' 188 | - '228227' 189 | - '260048' 190 | shortName: 191 | - mxtpr 192 | - crr 193 | - mxtpr3 194 | - mntpr6 195 | - srweq 196 | - mntpr3 197 | - lsrr 198 | - mntpr 199 | - tprate 200 | - crfrate 201 | - lsrrate 202 | - lssfr 203 | - prate 204 | - mxtpr6 205 | - csfr 206 | mm: 207 | match: [] 208 | paramId: 209 | - '8' 210 | - '9' 211 | - '140' 212 | - '142' 213 | - '143' 214 | - '144' 215 | - '171' 216 | - '182' 217 | - '184' 218 | - '198' 219 | - '205' 220 | - '228' 221 | - '237' 222 | - '239' 223 | - '240' 224 | - '244' 225 | - '174008' 226 | - '174009' 227 | - '228216' 228 | shortName: 229 | - ssro 230 | - fzra 231 | - lsp 232 | - csf 233 | - sf 234 | - tp 235 | - cp 236 | - sro 237 | - ro 238 | - lsf 239 | ppbv: 240 | match: [] 241 | paramId: 242 | - '210062' 243 | - '217004' 244 | shortName: 245 | - ch4_c 246 | - ch4 247 | pv units: 248 | match: [] 249 | paramId: 250 | - '60' 251 | shortName: 252 | - pv 253 | -------------------------------------------------------------------------------- /metview/etc/units-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "C": [ 3 | { 4 | "from" : "K", 5 | "to" : "C", 6 | "offset" : -273.15, 7 | "scaling" : 1 8 | } 9 | ], 10 | "pv units" : [ 11 | { 12 | "from" : "K m**2 kg**-1 s**-1", 13 | "offset" : -0.0, 14 | "to" : "pv units", 15 | "scaling" : 1000000.0 16 | } 17 | ], 18 | "10**5/sec" : [ 19 | { 20 | "from" : "s**-1", 21 | "to" : "10**5/sec", 22 | "offset" : -0.0, 23 | "scaling" : 100000.0 24 | } 25 | ], 26 | "g/kg" : [ 27 | { 28 | "from" : "kg kg**-1", 29 | "to" : "g/kg", 30 | "offset" : -0.0, 31 | "scaling" : 1000.0 32 | }, 33 | { 34 | "from" : "1", 35 | "to" : "gr kg-1", 36 | "offset" : 0.0, 37 | "scaling" : 1000.0 38 | } 39 | ], 40 | "μg/kg" : [ 41 | { 42 | "from" : "kg kg**-1", 43 | "to" : "μg/kg", 44 | "offset" : -0.0, 45 | "scaling" : 1e9 46 | }, 47 | { 48 | "from" : "1", 49 | "to" : "μgr kg-1", 50 | "offset" : 0.0, 51 | "scaling" : 1e9 52 | } 53 | ], 54 | "mm" : [ 55 | { 56 | "from" : "m", 57 | "to" : "mm", 58 | "offset" : -0.0, 59 | "scaling" : 1000.0 60 | }, 61 | { 62 | "from" : "m of water equivalent", 63 | "to" : "mm", 64 | "offset" : -0.0, 65 | "scaling" : 1000.0 66 | }, 67 | { 68 | "from" : "m of water", 69 | "to" : "mm", 70 | "offset" : -0.0, 71 | "scaling" : 1000.0 72 | } 73 | ], 74 | "cm" : [ 75 | { 76 | "from" : "m", 77 | "to" : "cm", 78 | "offset" : -0.0, 79 | "scaling" : 100.0 80 | }, 81 | { 82 | "from" : "m of water equivalent", 83 | "to" : "cm", 84 | "offset" : -0.0, 85 | "scaling" : 100.0 86 | } 87 | ], 88 | "dam" : [ 89 | { 90 | "from" : "m**2 s**-2", 91 | "offset" : -0.0, 92 | "to" : "dam", 93 | "scaling" : 0.0101971621297793 94 | }, 95 | { 96 | "from" : "m", 97 | "offset" : -0.0, 98 | "to" : "dam", 99 | "scaling" : 10.0 100 | }, 101 | { 102 | "from" : "gpm", 103 | "offset" : -0.0, 104 | "to" : "dam", 105 | "scaling" : 10.0 106 | } 107 | ], 108 | "hPa" : [ 109 | { 110 | "from" : "Pa", 111 | "offset" : -0.0, 112 | "to" : "hPa", 113 | "scaling" : 0.01 114 | } 115 | ], 116 | "kg m**-2 h**-1" : [ 117 | { 118 | "from" : "kg m**-2 s**-1", 119 | "offset" : -0.0, 120 | "to" : "kg m**-2 h**-1", 121 | "scaling" : 3600.0 122 | } 123 | ], 124 | "Dob" : [ 125 | { 126 | "from" : "kg m**-2", 127 | "offset" : -0.0, 128 | "to" : "Dob", 129 | "scaling" : 46696.2409526033 130 | } 131 | ] 132 | } 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /metview/gallery.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | import os 11 | import zipfile 12 | 13 | import metview as mv 14 | 15 | 16 | def load_dataset(filename, check_local=False): 17 | def _simple_download(url, target): 18 | import requests 19 | 20 | r = requests.get(url, allow_redirects=True) 21 | r.raise_for_status() 22 | open(target, "wb").write(r.content) 23 | 24 | if check_local and os.path.exists(filename): 25 | try: 26 | return mv.read(filename) 27 | except: 28 | return None 29 | 30 | base_url = "https://get.ecmwf.int/test-data/metview/gallery/" 31 | try: 32 | # d = mv.download(url=base_url + filename, target=filename) 33 | _simple_download(os.path.join(base_url, filename), filename) 34 | except Exception as e: 35 | raise Exception( 36 | f"Could not download file={filename} from the download server. {e}" 37 | ) 38 | 39 | d = None 40 | if filename.endswith(".zip"): 41 | with zipfile.ZipFile(filename, "r") as f: 42 | f.extractall() 43 | else: 44 | try: 45 | d = mv.read(filename) 46 | except: 47 | pass 48 | return d 49 | -------------------------------------------------------------------------------- /metview/layout.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2020- ECMWF. 3 | # 4 | # This software is licensed under the terms of the Apache Licence Version 2.0 5 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # In applying this licence, ECMWF does not waive the privileges and immunities 8 | # granted to it by virtue of its status as an intergovernmental organisation 9 | # nor does it submit to any jurisdiction. 10 | # 11 | 12 | import logging 13 | import math 14 | 15 | import metview as mv 16 | 17 | LOG = logging.getLogger(__name__) 18 | 19 | 20 | class Layout: 21 | 22 | GRID_DEF = { 23 | 1: [1, 1], 24 | 2: [2, 1], 25 | 3: [3, 1], 26 | 4: [2, 2], 27 | 8: [3, 3], 28 | 9: [3, 3], 29 | 10: [4, 3], 30 | } 31 | 32 | def _grid_row_col(self, page_num=0, rows=None, columns=None, layout=None): 33 | if rows is None and columns is None: 34 | if layout is not None and layout != "": 35 | v = layout.split("x") 36 | if len(v) == 2: 37 | try: 38 | r = int(v[0]) 39 | c = int(v[1]) 40 | # LOG.debug(f"r{r} c={c}") 41 | except: 42 | raise Exception(f"Invalid layout specification ({layout}") 43 | if page_num > 0 and r * c < page_num: 44 | raise Exception( 45 | f"Layout specification={layout} does not match number of scenes={page_num}" 46 | ) 47 | else: 48 | if page_num in self.GRID_DEF: 49 | r, c = self.GRID_DEF[page_num] 50 | elif page_num >= 1: 51 | r, c = self._compute_row_col(page_num) 52 | else: 53 | raise Exception(f"Cannot create a layout for {page_num} pages!") 54 | return r, c 55 | 56 | def build_grid(self, page_num=0, rows=None, columns=None, layout=None, view=None): 57 | r, c = self._grid_row_col( 58 | page_num=page_num, rows=rows, columns=columns, layout=layout 59 | ) 60 | 61 | if isinstance(view, mv.style.GeoView): 62 | v = view.to_request() 63 | else: 64 | v = view 65 | 66 | return self._build_grid(rows=r, columns=c, view=v) 67 | 68 | def _build_grid(self, rows=1, columns=1, view=None): 69 | assert rows >= 1 and columns >= 1 70 | if rows == 1 and columns == 1: 71 | return mv.plot_superpage(pages=[mv.plot_page(view=view)]) 72 | else: 73 | return mv.plot_superpage( 74 | pages=mv.mvl_regular_layout( 75 | view, columns, rows, 1, 1, [5, 100, 15, 100] 76 | ) 77 | ) 78 | 79 | def _compute_row_col(self, page_num): 80 | # has been checked for 100 81 | r = int(math.sqrt(page_num)) 82 | c = r 83 | if c * r < page_num: 84 | if (page_num - c * r) % c == 0: 85 | c += (page_num - c * r) // c 86 | else: 87 | c += 1 + (page_num - c * r) // c 88 | return (r, c) 89 | 90 | def build_diff(self, view): 91 | page_1 = mv.plot_page(top=5, bottom=50, left=25, right=75, view=view) 92 | 93 | page_2 = mv.plot_page(top=52, bottom=97, right=50, view=view) 94 | 95 | page_3 = mv.plot_page(top=52, bottom=97, left=50, right=100, view=view) 96 | 97 | return mv.plot_superpage(pages=[page_1, page_2, page_3]) 98 | 99 | def build_xs(self, line, map_view): 100 | xs_view = mv.mxsectview( 101 | line=line, 102 | bottom_level=1000, 103 | top_level=150 104 | # wind_perpendicular : "off", 105 | # wind_parallel :"on", 106 | # wind_intensity :"off" 107 | ) 108 | 109 | page_1 = mv.plot_page(top=35 if map_view is not None else 5, view=xs_view) 110 | 111 | if map_view is not None: 112 | page = mv.plot_page(top=5, bottom=35, view=map_view) 113 | return mv.plot_superpage(pages=[page_1, page]) 114 | else: 115 | return mv.plot_superpage(pages=[page_1]) 116 | 117 | def build_xs_avg( 118 | self, 119 | area, 120 | direction, 121 | bottom_level, 122 | top_level, 123 | vertical_scaling, 124 | axis_label_height=0.4, 125 | ): 126 | axis = mv.maxis(axis_tick_label_height=axis_label_height) 127 | 128 | return mv.maverageview( 129 | area=area, 130 | direction=direction, 131 | bottom_level=bottom_level, 132 | top_level=top_level, 133 | vertical_scaling=vertical_scaling, 134 | horizontal_axis=axis, 135 | vertical_axis=axis, 136 | ) 137 | 138 | def build_stamp(self, page_num=0, layout="", view=None): 139 | 140 | if True: 141 | coast_empty = mv.mcoast( 142 | map_coastline="off", map_grid="off", map_label="off" 143 | ) 144 | 145 | empty_view = mv.geoview( 146 | page_frame="off", 147 | subpage_frame="off", 148 | coastlines=coast_empty, 149 | subpage_x_position=40, 150 | subpage_x_length=10, 151 | subpage_y_length=10, 152 | ) 153 | 154 | title_page = mv.plot_page( 155 | top=0, bottom=5, left=30, right=70, view=empty_view 156 | ) 157 | 158 | r, c = self._grid_row_col(page_num=page_num, layout=layout) 159 | 160 | pages = mv.mvl_regular_layout(view, c, r, 1, 1, [5, 100, 15, 100]) 161 | pages.append(title_page) 162 | return mv.plot_superpage(pages=pages) 163 | 164 | # g = self.build_grid(page_num=page_num, layout=layout, view=view) 165 | # return g 166 | 167 | def build_rmse(self, xmin, xmax, ymin, ymax, xtick, ytick, xtitle, ytitle): 168 | 169 | horizontal_axis = mv.maxis( 170 | axis_type="date", 171 | axis_orientation="horizontal", 172 | axis_position="bottom", 173 | # axis_title : 'on', 174 | # axis_title_height : 0.4, 175 | axis_grid="on", 176 | axis_grid_colour="grey", 177 | # axis_title_text : xtitle, 178 | # axis_title_quality : 'high', 179 | axis_tick_interval=xtick, 180 | axis_tick_label_height=0.6, 181 | axis_date_type="days", 182 | axis_years_label="off", 183 | axis_months_label="off", 184 | axis_days_label_height="0.3", 185 | ) 186 | 187 | vertical_axis = mv.maxis( 188 | axis_orientation="vertical", 189 | axis_position="left", 190 | axis_grid="on", 191 | axis_grid_colour="grey", 192 | axis_title="on", 193 | axis_title_height=0.4, 194 | axis_title_text=ytitle, 195 | # axis_title_quality : 'high', 196 | axis_tick_interval=ytick, 197 | axis_tick_label_height=0.3, 198 | ) 199 | 200 | cview = mv.cartesianview( 201 | page_frame="off", 202 | x_axis_type="date", 203 | y_axis_type="regular", 204 | y_automatic="off", 205 | x_automatic="off", 206 | y_min=ymin, 207 | y_max=ymax, 208 | x_date_min=xmin, 209 | x_date_max=xmax, 210 | horizontal_axis=horizontal_axis, 211 | vertical_axis=vertical_axis, 212 | ) 213 | 214 | return cview 215 | 216 | def build_xy(self, xmin, xmax, ymin, ymax, xtick, ytick, xtitle, ytitle): 217 | 218 | horizontal_axis = mv.maxis( 219 | axis_orientation="horizontal", 220 | axis_position="bottom", 221 | axis_title="on", 222 | axis_title_height=0.5, 223 | axis_title_text=xtitle, 224 | # axis_title : 'on', 225 | # axis_title_height : 0.4, 226 | axis_grid="on", 227 | axis_grid_colour="grey", 228 | # axis_title_text : xtitle, 229 | # axis_title_quality : 'high', 230 | axis_tick_interval=xtick, 231 | axis_tick_label_height=0.6, 232 | axis_date_type="days", 233 | axis_years_label="off", 234 | axis_months_label="off", 235 | axis_days_label_height="0.3", 236 | ) 237 | 238 | vertical_axis = mv.maxis( 239 | axis_orientation="vertical", 240 | axis_position="left", 241 | axis_grid="on", 242 | axis_grid_colour="grey", 243 | axis_title="on", 244 | axis_title_height=0.6, 245 | axis_title_text=ytitle, 246 | # axis_title_quality : 'high', 247 | axis_tick_interval=ytick, 248 | axis_tick_label_height=0.3, 249 | ) 250 | 251 | cview = mv.cartesianview( 252 | page_frame="off", 253 | x_axis_type="regular", 254 | y_axis_type="regular", 255 | y_automatic="off", 256 | x_automatic="off", 257 | y_min=ymin, 258 | y_max=ymax, 259 | x_min=xmin, 260 | x_max=xmax, 261 | horizontal_axis=horizontal_axis, 262 | vertical_axis=vertical_axis, 263 | ) 264 | 265 | return cview 266 | 267 | @staticmethod 268 | def compute_axis_range(v_min, v_max): 269 | count = 15 270 | d = math.fabs(v_max - v_min) 271 | if d > 0: 272 | b = d / count 273 | n = math.floor(math.log10(b)) 274 | v = b / math.pow(10, n) 275 | # print("d={} b={} n={} v={}".format(d,b,n,v)) 276 | 277 | if v <= 1: 278 | v = 1 279 | elif v <= 2: 280 | v = 2 281 | elif v <= 5: 282 | v = 5 283 | else: 284 | v = 10 285 | 286 | bin_size = v * math.pow(10, n) 287 | bin_start = math.ceil(v_min / bin_size) * bin_size 288 | if bin_start >= v_min and math.fabs(bin_start - v_min) < bin_size / 10000: 289 | bin_start = bin_start - bin_size / 100000 290 | av = v_min 291 | else: 292 | bin_start = bin_start - bin_size 293 | av = bin_start 294 | 295 | max_iter = 100 296 | act_iter = 0 297 | while av < v_max: 298 | av += bin_size 299 | act_iter += 1 300 | if act_iter > max_iter: 301 | return (0, v_min, v_max) 302 | 303 | bin_end = av # + bin_size / 100000 304 | return bin_size, bin_start, bin_end 305 | else: 306 | return (0, v_min, v_max) 307 | -------------------------------------------------------------------------------- /metview/metview.h: -------------------------------------------------------------------------------- 1 | 2 | struct MvRequest; 3 | typedef struct MvRequest* MvRequest_p; 4 | 5 | struct CList; 6 | typedef struct CList* CList_p; 7 | 8 | struct CVector; 9 | typedef struct CVector* CVector_p; 10 | 11 | 12 | struct Value; 13 | typedef struct Value* Value_p; 14 | 15 | 16 | int mp_init(int argc, char **argv); 17 | void p_init(); 18 | const char* p_hello_world(int argc); 19 | const char* p_call_function(const char* name, int arity); 20 | void p_push_number(double n); 21 | void p_push_string(const char *str); 22 | void p_push_request(void *req); 23 | void p_push_value(Value_p val); 24 | void p_push_datestring(const char *str); 25 | void p_push_list(CList_p lst); 26 | void p_push_vector_from_double_array(double *vals, int size, double numpy_missing_value); 27 | void p_push_vector_from_float32_array(float *vals, int size, float numpy_missing_value); 28 | void p_push_nil(); 29 | int p_result_type(void); 30 | const char *p_value_as_string(Value_p); 31 | double p_value_as_number(Value_p); 32 | char* p_value_as_datestring(Value_p); 33 | CVector_p p_value_as_vector(Value_p, double numpy_missing_value); 34 | int p_vector_count(CVector_p); 35 | int p_vector_elem_size(CVector_p); 36 | double *p_vector_double_array(CVector_p); 37 | float *p_vector_float32_array(CVector_p); 38 | const char *p_error_message(Value_p); 39 | CList_p p_value_as_list(Value_p); 40 | Value_p p_result_as_value(); 41 | int p_value_type(Value_p val); 42 | CList_p p_new_list(int); 43 | void p_add_value_from_pop_to_list(CList_p, int); 44 | int p_list_count(CList_p); 45 | Value_p p_list_element_as_value(CList_p, int); 46 | MvRequest_p p_new_request(const char *verb); 47 | void p_set_value(MvRequest_p req, const char *param, const char *value); 48 | void p_add_value(MvRequest_p req, const char *param, const char *value); 49 | void p_set_request_value_from_pop(MvRequest_p req, const char *param); 50 | const char *p_get_req_verb(Value_p req); 51 | int p_get_req_num_params(Value_p req); 52 | const char *p_get_req_param(Value_p req, int i); 53 | const char *p_get_req_value(Value_p req, const char *param); 54 | void p_set_subvalue(Value_p, int, Value_p); 55 | void p_set_subvalue_from_arg_stack(Value_p); 56 | void p_set_temporary(Value_p val, int flag); 57 | const char *p_data_path(Value_p val); 58 | void p_destroy_value(Value_p val); 59 | -------------------------------------------------------------------------------- /metview/metviewpy/__init__.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 os 12 | 13 | 14 | # if we're running pytest, it will need the fieldset functionality, so detect if it 15 | # is running and if so, import the user-facing functions 16 | 17 | 18 | def running_from_pytest(): 19 | from inspect import stack 20 | 21 | call_stack = [s.function for s in stack()] 22 | return "pytest_collection" in call_stack 23 | 24 | 25 | if "METVIEW_PYTHON_ONLY" in os.environ or running_from_pytest(): 26 | 27 | from . import fieldset 28 | 29 | fieldset.bind_functions(globals(), module_name=__name__) 30 | -------------------------------------------------------------------------------- /metview/metviewpy/ipython.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2017- ECMWF. 3 | # 4 | # This software is licensed under the terms of the Apache Licence Version 2.0 5 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # In applying this licence, ECMWF does not waive the privileges and immunities 8 | # granted to it by virtue of its status as an intergovernmental organisation 9 | # nor does it submit to any jurisdiction. 10 | # 11 | 12 | """ 13 | ipython is not None when running a notebook 14 | """ 15 | 16 | import logging 17 | import sys 18 | 19 | 20 | ipython_active = None 21 | 22 | 23 | def is_ipython_active(): 24 | global ipython_active 25 | if ipython_active is None: 26 | try: 27 | from IPython import get_ipython 28 | 29 | ipython_active = get_ipython() is not None 30 | except Exception: 31 | ipython_active = False 32 | return ipython_active 33 | 34 | 35 | def import_widgets(): 36 | try: 37 | widgets = __import__("ipywidgets", globals(), locals()) 38 | return widgets 39 | except ImportError as imperr: 40 | print("Could not import ipywidgets module - animation widget will not appear") 41 | print("Call setoutput('jupyter', plot_widget=False) to suppress this message") 42 | return None 43 | -------------------------------------------------------------------------------- /metview/metviewpy/maths.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | import numpy as np 10 | 11 | 12 | def neg(x): 13 | return -x 14 | 15 | 16 | def pos(x): 17 | return x 18 | 19 | 20 | # def abs(x): 21 | # return np.abs(x) 22 | 23 | 24 | def not_func(x): 25 | return (~(x != 0)).astype(int) 26 | 27 | 28 | def add(x, y): 29 | return x + y 30 | 31 | 32 | def sub(x, y): 33 | return x - y 34 | 35 | 36 | def mul(x, y): 37 | return x * y 38 | 39 | 40 | def div(x, y): 41 | return x / y 42 | 43 | 44 | def pow(x, y): 45 | return x**y 46 | 47 | 48 | def ge(x, y): 49 | return (x >= y).astype(int) 50 | 51 | 52 | def gt(x, y): 53 | return (x > y).astype(int) 54 | 55 | 56 | def le(x, y): 57 | return (x <= y).astype(int) 58 | 59 | 60 | def lt(x, y): 61 | return (x < y).astype(int) 62 | 63 | 64 | def eq(x, y): 65 | return (x == y).astype(int) 66 | 67 | 68 | def ne(x, y): 69 | return (x != y).astype(int) 70 | 71 | 72 | def and_func(x, y): 73 | return ne(x, 0) * ne(y, 0) 74 | 75 | 76 | def or_func(x, y): 77 | return np.clip(ne(x, 0) + ne(y, 0), 0, 1) 78 | 79 | 80 | def set_from_other(x, y): 81 | return y 82 | 83 | 84 | # single argument functions 85 | 86 | 87 | def abs(x): 88 | return np.fabs(x) 89 | 90 | 91 | def acos(x): 92 | return np.arccos(x) 93 | 94 | 95 | def asin(x): 96 | return np.arcsin(x) 97 | 98 | 99 | def atan(x): 100 | return np.arctan(x) 101 | 102 | 103 | def cos(x): 104 | return np.cos(x) 105 | 106 | 107 | def exp(x): 108 | return np.exp(x) 109 | 110 | 111 | def log(x): 112 | return np.log(x) 113 | 114 | 115 | def log10(x): 116 | return np.log10(x) 117 | 118 | 119 | def sgn(x): 120 | return np.sign(x) 121 | 122 | 123 | def square(x): 124 | return np.square(x) 125 | 126 | 127 | def sqrt(x): 128 | return np.sqrt(x) 129 | 130 | 131 | def sin(x): 132 | return np.sin(x) 133 | 134 | 135 | def tan(x): 136 | return np.tan(x) 137 | 138 | 139 | # double argument functions 140 | 141 | 142 | def atan2(x, y): 143 | return np.arctan2(x, y) 144 | 145 | 146 | def floor_div(x, y): 147 | return np.floor_divide(x, y) 148 | 149 | 150 | def mod(x, y): 151 | return np.mod(x, y) 152 | 153 | 154 | # bitmapping 155 | 156 | 157 | def bitmap(x, y): 158 | if isinstance(y, (int, float)): 159 | x[x == y] = np.nan 160 | return x 161 | elif isinstance(y, np.ndarray): 162 | x[np.isnan(y)] = np.nan 163 | return x 164 | 165 | 166 | def nobitmap(x, y): 167 | x[np.isnan(x)] = y 168 | return x 169 | -------------------------------------------------------------------------------- /metview/metviewpy/temporary.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | # code copied from ecmwf/climetlab 11 | 12 | import os 13 | import tempfile 14 | 15 | 16 | class TmpFile: 17 | """The TmpFile objets are designed to be used for temporary files. 18 | It ensures that the file is unlinked when the object is 19 | out-of-scope (with __del__). 20 | Parameters 21 | ---------- 22 | path : str 23 | Actual path of the file. 24 | """ 25 | 26 | def __init__(self, path: str): 27 | self.path = path 28 | 29 | def __del__(self): 30 | self.cleanup() 31 | 32 | # def __enter__(self): 33 | # return self.path 34 | 35 | # def __exit__(self, *args, **kwargs): 36 | # self.cleanup() 37 | 38 | def cleanup(self): 39 | if self.path is not None: 40 | os.unlink(self.path) 41 | self.path = None 42 | 43 | 44 | def temp_file(extension=".tmp"): 45 | """Create a temporary file with the given extension . 46 | Parameters 47 | ---------- 48 | extension : str, optional 49 | By default ".tmp" 50 | Returns 51 | ------- 52 | TmpFile 53 | """ 54 | 55 | fd, path = tempfile.mkstemp(suffix=extension) 56 | os.close(fd) 57 | return TmpFile(path) 58 | 59 | 60 | def is_temp_file(path): 61 | return tempfile.gettempdir() in path 62 | 63 | 64 | # class TmpDirectory(tempfile.TemporaryDirectory): 65 | # @property 66 | # def path(self): 67 | # return self.name 68 | 69 | 70 | # def temp_directory(): 71 | # return TmpDirectory() 72 | -------------------------------------------------------------------------------- /metview/metviewpy/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2017- ECMWF. 3 | # 4 | # This software is licensed under the terms of the Apache Licence Version 2.0 5 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # In applying this licence, ECMWF does not waive the privileges and immunities 8 | # granted to it by virtue of its status as an intergovernmental organisation 9 | # nor does it submit to any jurisdiction. 10 | # 11 | 12 | import datetime 13 | import getpass 14 | import glob 15 | import logging 16 | import math 17 | from pathlib import Path 18 | import shutil 19 | import os 20 | import re 21 | import tempfile 22 | 23 | import numpy as np 24 | 25 | LOG = logging.getLogger(__name__) 26 | 27 | 28 | CACHE_DIR = os.path.join(tempfile.gettempdir(), f"mpy-{getpass.getuser()}") 29 | 30 | 31 | def deacc(fs, key=None, skip_first=False, mark_derived=False): 32 | r = None 33 | if key is None or key == "": 34 | if len(fs) > 1: 35 | v = fs[1:] - fs[:-1] 36 | if not skip_first: 37 | r = fs[0] * 0 38 | r = r.merge(v) 39 | else: 40 | r = v 41 | else: 42 | if not isinstance(key, str): 43 | raise TypeError(f"deacc(): key must be a str (got {type(key)})!") 44 | fs._get_db().load([key]) 45 | key_vals = fs._unique_metadata(key) 46 | if key_vals: 47 | v = fs.select({key: key_vals[0]}) 48 | gr_num = len(v) 49 | r = None 50 | if not skip_first: 51 | r = v * 0 52 | for i in range(1, len(key_vals)): 53 | v_next = fs.select({key: key_vals[i]}) 54 | if len(v_next) != gr_num: 55 | raise ValueError( 56 | f"deacc(): unexpected number of fields (={len(v_next)}) found for {key}={key_vals[i]}! For each {key} value the number of fields must be the same as for {key}={key_vals[0]} (={gr_num})!" 57 | ) 58 | if r is None: 59 | r = v_next - v 60 | else: 61 | # print(f"i={i}") 62 | # v.ls() 63 | # v_next.ls() 64 | r.append(v_next - v) 65 | v = v_next 66 | if not mark_derived: 67 | r = r.grib_set_long(["generatingProcessIdentifier", 148]) 68 | return r 69 | 70 | 71 | def date_from_str(d_str): 72 | # yyyymmdd 73 | if len(d_str) == 8: 74 | return datetime.datetime.strptime(d_str, "%Y%m%d") 75 | # yyyy-mm-dd .... 76 | elif len(d_str) >= 10 and d_str[4] == "-" and d_str[7] == "-": 77 | # yyyy-mm-dd 78 | if len(d_str) == 10: 79 | return datetime.datetime.strptime(d_str, "%Y-%m-%d") 80 | # yyyy-mm-dd hh 81 | elif len(d_str) == 13: 82 | return datetime.datetime.strptime(d_str, "%Y-%m-%d %H") 83 | # yyyy-mm-dd hh:mm 84 | elif len(d_str) == 16: 85 | return datetime.datetime.strptime(d_str, "%Y-%m-%d %H:%M") 86 | # yyyy-mm-dd hh:mm:ss 87 | elif len(d_str) == 19: 88 | return datetime.datetime.strptime(d_str, "%Y-%m-%d %H:%M:%S") 89 | # yyyymmdd.decimal_day 90 | elif len(d_str) > 8 and d_str[8] == ".": 91 | f = float("0" + d_str[8:]) 92 | if f >= 1: 93 | raise ValueError 94 | else: 95 | return datetime.datetime.strptime(d_str[:8], "%Y%m%d") + datetime.timedelta( 96 | seconds=int(f * 86400) 97 | ) 98 | # mmdd or mdd (as in daily climatologies) 99 | elif len(d_str) in [3, 4]: 100 | # try to convert to datatime to see if it is valid date 101 | d = datetime.datetime.strptime("0004" + d_str.rjust(4, "0"), "%Y%m%d") 102 | # we just return a tuple since datetime cannot have an invalid date 103 | return (d.month, d.day) 104 | # b-dd e.g. apr-02 (as in daily climatologies) 105 | elif len(d_str) == 6 and d_str[3] == "-": 106 | months = [ 107 | "jan", 108 | "feb", 109 | "mar", 110 | "apr", 111 | "may", 112 | "jun", 113 | "jul", 114 | "aug", 115 | "sep", 116 | "nov", 117 | "dec", 118 | ] 119 | m = d_str[0:3].lower() 120 | try: 121 | m_num = months.index(m) + 1 122 | except: 123 | raise ValueError(f"Invalid month={m} specified in date={d_str}!") 124 | # try to convert to datatime to see if it is valid date 125 | d = datetime.datetime.strptime("0004" + f"{m_num:02}" + d_str[4:6], "%Y%m%d") 126 | # we just return a tuple since datetime cannot have an invalid date 127 | return (d.month, d.day) 128 | 129 | 130 | def time_from_str(t_str): 131 | h = m = 0 132 | if not ":" in t_str: 133 | # formats: h[mm], hh[mm] 134 | if len(t_str) in [1, 2]: 135 | h = int(t_str) 136 | elif len(t_str) in [3, 4]: 137 | r = int(t_str) 138 | h = int(r / 100) 139 | m = int(r - h * 100) 140 | else: 141 | raise Exception(f"Invalid time={t_str}") 142 | else: 143 | # formats: h:mm, hh:mm 144 | lst = t_str.split(":") 145 | if len(lst) >= 2: 146 | h = int(lst[0]) 147 | m = int(lst[1]) 148 | else: 149 | raise Exception(f"Invalid time={t_str}") 150 | 151 | return datetime.time(hour=h, minute=m) 152 | 153 | 154 | def date_from_ecc_keys(d, t): 155 | try: 156 | return datetime.datetime.combine( 157 | date_from_str(str(d)).date(), time_from_str(str(t)) 158 | ) 159 | except: 160 | return None 161 | 162 | 163 | def is_fieldset_type(thing): 164 | # will return True for binary or python fieldset objects 165 | return "Fieldset" in thing.__class__.__name__ 166 | 167 | 168 | def get_file_list(path, file_name_pattern=None): 169 | m = None 170 | # if isinstance(file_name_pattern, re.Pattern): 171 | # m = file_name_pattern.match 172 | # elif isinstance(file_name_pattern, str): 173 | if isinstance(file_name_pattern, str): 174 | if file_name_pattern.startswith('re"'): 175 | m = re.compile(file_name_pattern[3:-1]).match 176 | 177 | # print(f"path={path} file_name_pattern={file_name_pattern}") 178 | 179 | if m is not None: 180 | return [os.path.join(path, f) for f in filter(m, os.listdir(path=path))] 181 | else: 182 | if isinstance(file_name_pattern, str) and file_name_pattern != "": 183 | path = os.path.join(path, file_name_pattern) 184 | if not has_globbing(path): 185 | return [path] 186 | else: 187 | return sorted(glob.glob(path)) 188 | 189 | 190 | def has_globbing(text): 191 | for x in ["*", "?"]: 192 | if x in text: 193 | return True 194 | if "[" in text and "]" in text: 195 | return True 196 | else: 197 | return False 198 | 199 | 200 | def unpack(file_path, remove=False): 201 | if any(file_path.endswith(x) for x in [".tar", ".tar.gz", ".tar.bz2"]): 202 | target_dir = os.path.dirname(file_path) 203 | LOG.debug(f"file_path={file_path} target_dir={target_dir}") 204 | shutil.unpack_archive(file_path, target_dir) 205 | if remove: 206 | os.remove(file_path) 207 | 208 | 209 | def download(url, target): 210 | from tqdm import tqdm 211 | import requests 212 | 213 | resp = requests.get(url, stream=True) 214 | total = int(resp.headers.get("content-length", 0)) 215 | with open(target, "wb") as file, tqdm( 216 | desc=target, 217 | total=total, 218 | unit="iB", 219 | unit_scale=True, 220 | unit_divisor=1024, 221 | ) as bar: 222 | for data in resp.iter_content(chunk_size=1024): 223 | size = file.write(data) 224 | bar.update(size) 225 | 226 | 227 | def simple_download(url, target): 228 | import requests 229 | 230 | r = requests.get(url, allow_redirects=True) 231 | r.raise_for_status() 232 | open(target, "wb").write(r.content) 233 | 234 | 235 | def _smooth_core(fs, repeat, m_func, m_arg, **kwargs): 236 | """ 237 | Performs spatial smoothing on each field in fs with the given callable 238 | """ 239 | # extract metadata for each field 240 | meta = fs.grib_get(["gridType", "Ni", "generatingProcessIdentifier"]) 241 | 242 | # the resulting fieldset. We cannot use the Fieldset constructor here! 243 | res = type(fs)() 244 | 245 | # build result in a loop 246 | for fld, fld_meta in zip(fs, meta): 247 | # smoothing only works for regular latlon grids 248 | if fld_meta[0] == "regular_ll": 249 | ncol = int(fld_meta[1]) 250 | val = fld.values() 251 | val = np.reshape(val, (-1, ncol)) 252 | for _ in range(repeat): 253 | val = m_func(val, m_arg, **kwargs) 254 | r = fld.set_values(val.flatten()) 255 | if fld_meta[2] is not None: 256 | try: 257 | r = r.grib_set_long( 258 | ["generatingProcessIdentifier", int(fld_meta[2])] 259 | ) 260 | except: 261 | pass 262 | res.append(r) 263 | else: 264 | raise ValueError( 265 | f"Unsupported gridType={fld_meta[0]} in field={i}. Only regular_ll is accepted!" 266 | ) 267 | 268 | return res 269 | 270 | 271 | def convolve(fs, weight, repeat=1, **kwargs): 272 | """ 273 | Performs spatial smoothing on each field in fs with a convolution 274 | """ 275 | from scipy.ndimage.filters import convolve 276 | 277 | m_arg = weight 278 | return _smooth_core(fs, repeat, convolve, m_arg, **kwargs) 279 | 280 | 281 | def smooth_n_point(fs, n=9, repeat=1, **kwargs): 282 | """ 283 | Performs spatial smoothing on each field in fs with an n-point averaging 284 | """ 285 | from scipy.ndimage.filters import convolve 286 | 287 | if n == 9: 288 | weights = np.array( 289 | [[0.0625, 0.125, 0.0625], [0.125, 0.25, 0.125], [0.0625, 0.125, 0.0625]], 290 | dtype=float, 291 | ) 292 | m_arg = weights 293 | elif n == 5: 294 | weights = np.array( 295 | [[0.0, 0.125, 0.0], [0.125, 0.5, 0.125], [0.0, 0.125, 0.0]], dtype=float 296 | ) 297 | m_arg = weights 298 | else: 299 | raise ValueError("smooth_n_point: n must be either 5 or 9!") 300 | 301 | return _smooth_core(fs, repeat, convolve, m_arg, **kwargs) 302 | 303 | 304 | def smooth_gaussian(fs, sigma=1, repeat=1, **kwargs): 305 | """ 306 | Performs spatial smoothing on each field in fs with a Gaussian filter 307 | """ 308 | from scipy.ndimage.filters import gaussian_filter 309 | 310 | m_arg = sigma 311 | return _smooth_core(fs, repeat, gaussian_filter, m_arg, **kwargs) 312 | 313 | 314 | class Cache: 315 | ROOT_DIR = os.path.join(tempfile.gettempdir(), f"mpy_ds_{getpass.getuser()}") 316 | 317 | def all_exists(self, items, path): 318 | for name in items: 319 | p = os.path.join(path, name) 320 | # print(f"p={p}") 321 | if not os.path.exists(p): 322 | return False 323 | elif os.path.isdir(p): 324 | cont_file = os.path.join(path, f".content_{name}") 325 | if os.path.exists(cont_file): 326 | with open(cont_file, "r") as f: 327 | try: 328 | for item in f.read().split("\n"): 329 | if item and not os.path.exists( 330 | os.path.join(path, item) 331 | ): 332 | return False 333 | except: 334 | return False 335 | else: 336 | return False 337 | return True 338 | 339 | def make_reference(self, items, path): 340 | for name in items: 341 | p = os.path.join(path, name) 342 | if os.path.isdir(p): 343 | cont_file = os.path.join(path, f".content_{name}") 344 | with open(cont_file, "w") as f: 345 | for item in Path(p).rglob("*"): 346 | if item.is_file(): 347 | f.write(item.relative_to(path).as_posix() + "\n") 348 | 349 | 350 | CACHE = Cache() 351 | -------------------------------------------------------------------------------- /metview/scaling.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 json 12 | import logging 13 | import os 14 | import sys 15 | 16 | import yaml 17 | 18 | LOG = logging.getLogger(__name__) 19 | 20 | ETC_PATH = os.path.join(os.path.dirname(__file__), "etc") 21 | 22 | 23 | class UnitsScalingMethod: 24 | """ 25 | Performs units scaling of values 26 | """ 27 | 28 | def __init__(self, scaling=1.0, offset=0.0, from_units="", to_units=""): 29 | self.scaling = scaling 30 | self.offset = offset 31 | self.from_units = from_units 32 | self.to_units = to_units 33 | 34 | def scale_value(self, value): 35 | return self.scaling * value + self.offset 36 | 37 | def inverse_scale_value(self, value): 38 | return (value - self.offset) / self.scaling 39 | 40 | def need_scaling(self, meta, scaling_retrieved, scaling_derived): 41 | gen_id = meta.get("generatingProcessIdentifier", 0) 42 | try: 43 | gen_id = int(gen_id) 44 | except: 45 | gen_id = 0 46 | 47 | return (scaling_retrieved and gen_id != 254) or ( 48 | scaling_derived and gen_id == 254 49 | ) 50 | 51 | def __str__(self): 52 | return "scaling: {} offset:{} from_units:{} to_units:{}".format( 53 | self.scaling, self.offset, self.from_units, self.to_units 54 | ) 55 | 56 | 57 | class ScalingRule: 58 | """ 59 | Defines what countour scaling should be applied to a given field based 60 | on its metadata 61 | """ 62 | 63 | def __init__(self, to_units, conf): 64 | self.to_units = to_units 65 | self.units_methods = [ 66 | it for it in Scaling.methods if it.to_units == self.to_units 67 | ] 68 | self.conf = conf 69 | 70 | def find_method(self, meta): 71 | from_units = meta.get("units", "") 72 | if from_units == "": 73 | return None 74 | 75 | method = None 76 | for item in self.units_methods: 77 | if item.from_units == from_units: 78 | method = item 79 | break 80 | 81 | if method is None: 82 | return None 83 | 84 | for m in self.conf.get("match", []): 85 | match = False 86 | for k, v in m.items(): 87 | if meta.get(k, None) == v: 88 | match = True 89 | else: 90 | break 91 | 92 | if match: 93 | return method 94 | 95 | param_id = meta.get("paramId", "") 96 | if param_id and param_id in self.conf.get("paramId", []): 97 | return method 98 | 99 | short_name = meta.get("shortName", "") 100 | if short_name and short_name in self.conf.get("shortName", []): 101 | return method 102 | 103 | return None 104 | 105 | def __str__(self): 106 | return "to_units:{}".format(self.to_units) 107 | 108 | 109 | class Scaling: 110 | methods = [] 111 | rules = [] 112 | loaded = False 113 | 114 | @staticmethod 115 | def find_item(meta): 116 | if not Scaling.loaded: 117 | Scaling._load_def() 118 | Scaling.loaded = True 119 | 120 | for item in Scaling.rules: 121 | d = item.find_method(meta) 122 | if d: 123 | return d 124 | return None 125 | 126 | @staticmethod 127 | def _load_def(): 128 | # load units conversion definition 129 | file_name = os.path.join(ETC_PATH, "units-rules.json") 130 | with open(file_name) as f: 131 | data = json.load(f) 132 | for k, v in data.items(): 133 | for item in v: 134 | item["to_units"] = item.pop("to") 135 | item["from_units"] = item.pop("from") 136 | Scaling.methods.append(UnitsScalingMethod(**item)) 137 | 138 | # load rules defining when to apply scaling on a parameter 139 | file_name = os.path.join(ETC_PATH, "scaling_ecmwf.yaml") 140 | # print(f"file_name={file_name}") 141 | with open(file_name) as f: 142 | data = yaml.load(f, Loader=yaml.SafeLoader) 143 | for to_units, item in data.items(): 144 | Scaling.rules.append(ScalingRule(to_units, item)) 145 | -------------------------------------------------------------------------------- /metview/title.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2017- ECMWF. 3 | # 4 | # This software is licensed under the terms of the Apache Licence Version 2.0 5 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # In applying this licence, ECMWF does not waive the privileges and immunities 8 | # granted to it by virtue of its status as an intergovernmental organisation 9 | # nor does it submit to any jurisdiction. 10 | # 11 | 12 | import logging 13 | 14 | import metview as mv 15 | 16 | LOG = logging.getLogger(__name__) 17 | 18 | FC_TIME_PART = "Step: +h Valid: " 19 | LEVEL_PART = "Lev: ()" 20 | 21 | 22 | class Title: 23 | def __init__(self, font_size=0.4): 24 | self.font_size = font_size 25 | 26 | def _make_font_size(self, font_size): 27 | if font_size is None: 28 | return self.font_size 29 | else: 30 | return font_size 31 | 32 | def build(self, data, font_size=None): 33 | font_size = self._make_font_size(font_size) 34 | label = "" 35 | if data: 36 | if not isinstance(data, list): 37 | data = [data] 38 | 39 | lines = [] 40 | lines = {"text_line_count": len(data)} 41 | for i, d_item in enumerate(data): 42 | # print(f"d_item={d_item}") 43 | if isinstance(d_item, tuple): 44 | d = d_item[0] 45 | data_id = d_item[1] 46 | else: 47 | d = d_item 48 | data_id = None 49 | 50 | param = d.ds_param_info 51 | if param is not None: 52 | if param.meta.get("typeOfLevel", "") == "surface": 53 | # lines.append(self.build_surface_fc(d.experiment.label, d.param.name, condition=cond)) 54 | lines[f"text_line_{i+1}"] = self.build_surface_fc( 55 | d.label, param.name, data_id=data_id 56 | ) 57 | else: 58 | # lines.append(self.build_upper_fc(d.experiment.label, d.param.name, condition=cond)) 59 | lines[f"text_line_{i+1}"] = self.build_upper_fc( 60 | d.label, param.name, data_id=data_id 61 | ) 62 | 63 | # print(f"line={lines}") 64 | # return mv.mtext(text_lines=lines, text_font_size=font_size) 65 | return mv.mtext(**lines, text_font_size=font_size) 66 | 67 | return mv.mtext( 68 | { 69 | "text_line_1": f"""{label} Par: Lev: () Step: +h Valid: """, 70 | "text_font_size": font_size, 71 | } 72 | ) 73 | 74 | def _build_condition_str(self, condition): 75 | if condition: 76 | t = "where=" 77 | for k, v in condition.items(): 78 | t += f"'{k}={v}'" 79 | return t 80 | return str() 81 | 82 | def _add_grib_info(self, t, data_id): 83 | if data_id is not None and data_id != "": 84 | t = t.replace("h Valid: """, 144 | "text_font_size": self.font_size, 145 | } 146 | ) 147 | 148 | return mv.mtext( 149 | { 150 | "text_font_size": self.font_size, 151 | } 152 | ) 153 | 154 | def build_rmse(self, ref, data): 155 | if data: 156 | if not isinstance(data, list): 157 | data = [data] 158 | 159 | lines = [] 160 | for d in data: 161 | line = "RMSE" 162 | if ref.label: 163 | line += f"(ref={ref.label})" 164 | if d.label: 165 | line += f" {d.label}" 166 | 167 | # print(f"label={d.label}") 168 | param = d.ds_param_info 169 | if param is not None: 170 | # print(f"meta={param.meta}") 171 | line += f" Par: {param.name}" 172 | meta = param.meta 173 | if meta.get("mars.type", "") == "an": 174 | pass 175 | else: 176 | r = meta.get("date", 0) 177 | h = meta.get("time", 0) 178 | line += f" Run: {r} {h} UTC" 179 | lines.append(line) 180 | 181 | return mv.mtext(text_lines=lines, text_font_size=self.font_size) 182 | 183 | return mv.mtext(text_font_size=self.font_size) 184 | 185 | def build_cdf(self, data): 186 | if data: 187 | if not isinstance(data, list): 188 | data = [data] 189 | lines = [] 190 | for d in data: 191 | line = f"CDF {d.label}" 192 | # print(f"label={d.label}") 193 | param = d.ds_param_info 194 | if param is not None: 195 | # print(f"meta={param.meta}") 196 | line += f" Par: {param.name}" 197 | meta = param.meta 198 | if meta.get("mars.type", "") == "an": 199 | pass 200 | else: 201 | r = meta.get("date", 0) 202 | h = meta.get("time", 0) 203 | line += f" Run: {r} {h} UTC" 204 | lines.append(line) 205 | 206 | return mv.mtext(text_lines=lines, text_font_size=self.font_size) 207 | 208 | return mv.mtext(text_font_size=self.font_size) 209 | -------------------------------------------------------------------------------- /metview/track.py: -------------------------------------------------------------------------------- 1 | # 2 | # (C) Copyright 2017- ECMWF. 3 | # 4 | # This software is licensed under the terms of the Apache Licence Version 2.0 5 | # which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 6 | # 7 | # In applying this licence, ECMWF does not waive the privileges and immunities 8 | # granted to it by virtue of its status as an intergovernmental organisation 9 | # nor does it submit to any jurisdiction. 10 | # 11 | 12 | from datetime import datetime 13 | 14 | import metview as mv 15 | import pandas as pd 16 | 17 | from metview.style import Style 18 | 19 | 20 | class Track: 21 | def __init__( 22 | self, 23 | path, 24 | skiprows=None, 25 | sep=None, 26 | date_index=None, 27 | time_index=None, 28 | lat_index=None, 29 | lon_index=None, 30 | ): 31 | self.path = path 32 | self.skiprows = 0 if skiprows is None else skiprows 33 | self.sep = sep 34 | if self.sep == " ": 35 | self.sep = r"\s+" 36 | self.date_index = 0 if date_index is None else date_index 37 | self.time_index = 1 if time_index is None else time_index 38 | self.lat_index = 3 if lat_index is None else lat_index 39 | self.lon_index = 2 if lon_index is None else lon_index 40 | 41 | def style(self): 42 | return mv.style.get_db().get_style("track").clone() 43 | 44 | def build(self, style=None): 45 | style = [] if style is None else style 46 | df = pd.read_csv( 47 | filepath_or_buffer=self.path, 48 | sep=self.sep, 49 | skiprows=self.skiprows, 50 | header=None, 51 | engine="python", 52 | ) 53 | 54 | # print(df) 55 | 56 | v_date = df.iloc[:, self.date_index] 57 | v_time = df.iloc[:, self.time_index] 58 | val = [" {}/{:02d}".format(str(d)[-2:], t) for d, t in zip(v_date, v_time)] 59 | lon = df.iloc[:, self.lon_index].values 60 | lat = df.iloc[:, self.lat_index].values 61 | idx_val = list(range(len(val))) 62 | 63 | # print(f"lon={lon}") 64 | # print(f"lat={lat}") 65 | # print(f"val={val}") 66 | # for x in style: 67 | # print(f"style={x}") 68 | 69 | if len(style) == 0: 70 | s = mv.style.get_db().get_style("track").clone() 71 | if len(style) == 1 and isinstance(style[0], Style): 72 | s = style[0].clone() 73 | else: 74 | assert all(not isinstance(x, Style) for x in style) 75 | s = mv.style.get_db().get_style("track").clone() 76 | 77 | for vd in s.visdefs: 78 | if vd.verb == "msymb": 79 | vd.change_symbol_text_list(val, idx_val) 80 | 81 | r = s.to_request() 82 | 83 | vis = mv.input_visualiser( 84 | input_plot_type="geo_points", 85 | input_longitude_values=lon, 86 | input_latitude_values=lat, 87 | input_values=idx_val, 88 | ) 89 | 90 | return [vis, *r] 91 | -------------------------------------------------------------------------------- /metview/ui.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | import metview as mv 11 | 12 | # this module is meant to expose some ui specific functions 13 | 14 | 15 | def dialog(*args): 16 | res = mv._dialog(*args) 17 | return {k: v for k, v in res.items() if not k.startswith("_")} 18 | 19 | 20 | def any(**kwargs): 21 | return mv._any(**kwargs) 22 | 23 | 24 | def colour(**kwargs): 25 | return mv._colour(**kwargs) 26 | 27 | 28 | def icon(**kwargs): 29 | return mv._icon(**kwargs) 30 | 31 | 32 | def option_menu(**kwargs): 33 | return mv._option_menu(**kwargs) 34 | 35 | 36 | def slider(**kwargs): 37 | return mv._slider(**kwargs) 38 | 39 | 40 | def toggle(**kwargs): 41 | return mv._toggle(**kwargs) 42 | -------------------------------------------------------------------------------- /mpy.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "build_systems": 3 | [ 4 | { 5 | "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)", 6 | "name": "Anaconda Python Builder", 7 | "selector": "source.python", 8 | "shell_cmd": "\"python\" -u \"$file\"" 9 | } 10 | ], 11 | "folders": 12 | [ 13 | { 14 | "path": "." 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /requirements/requirements-dev.txt: -------------------------------------------------------------------------------- 1 | check-manifest 2 | detox 3 | flake8 4 | ipython 5 | matplotlib 6 | notebook 7 | pip-tools 8 | pytest-mypy 9 | setuptools 10 | tox 11 | tox-pyenv 12 | wheel 13 | zest.releaser 14 | -------------------------------------------------------------------------------- /requirements/requirements-docs.in: -------------------------------------------------------------------------------- 1 | Sphinx 2 | pytest-runner 3 | -------------------------------------------------------------------------------- /requirements/requirements-docs.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements/requirements-docs.txt setup.py requirements/requirements-docs.in 6 | # 7 | alabaster==0.7.10 8 | # via sphinx 9 | babel==2.9.1 10 | # via sphinx 11 | certifi==2024.7.4 12 | # via requests 13 | charset-normalizer==3.3.2 14 | # via requests 15 | docutils==0.14 16 | # via sphinx 17 | idna==3.7 18 | # via requests 19 | imagesize==1.0.0 20 | # via sphinx 21 | jinja2==3.1.6 22 | # via sphinx 23 | markupsafe==2.1.5 24 | # via jinja2 25 | packaging==17.1 26 | # via sphinx 27 | pygments==2.18.0 28 | # via sphinx 29 | pyparsing==2.2.0 30 | # via packaging 31 | pytest-runner==4.2 32 | # via -r requirements-docs.in 33 | pytz==2018.3 34 | # via babel 35 | requests==2.32.2 36 | # via sphinx 37 | six==1.11.0 38 | # via 39 | # packaging 40 | # sphinx 41 | snowballstemmer==1.2.1 42 | # via sphinx 43 | sphinx==1.7.2 44 | # via -r requirements-docs.in 45 | sphinxcontrib-websupport==1.0.1 46 | # via sphinx 47 | urllib3==1.26.19 48 | # via requests 49 | -------------------------------------------------------------------------------- /requirements/requirements-tests.in: -------------------------------------------------------------------------------- 1 | cfgrib 2 | netCDF4 3 | pytest 4 | pytest-cov 5 | pytest-flakes 6 | pytest-mccabe 7 | pytest-pep8 8 | pytest-runner 9 | xarray 10 | -------------------------------------------------------------------------------- /requirements/requirements-tests.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements/requirements-tests.txt setup.py requirements/requirements-tests.in 6 | # 7 | apipkg==1.4 # via execnet 8 | atomicwrites==1.1.5 # via pytest 9 | attrs==20.3.0 # via cfgrib, pytest 10 | cffi==1.11.5 11 | cfgrib==0.9.4.2 12 | cftime==1.0.3.4 # via netcdf4 13 | coverage==4.5.1 # via pytest-cov 14 | execnet==1.5.0 # via pytest-cache 15 | future==0.18.3 # via cfgrib 16 | mccabe==0.6.1 # via pytest-mccabe 17 | more-itertools==4.2.0 # via pytest 18 | netcdf4==1.4.2 19 | numpy==1.22 20 | pandas==0.23.0 21 | pep8==1.7.1 # via pytest-pep8 22 | pluggy==0.6.0 # via pytest 23 | py==1.11.0 # via pytest 24 | pycparser==2.18 # via cffi 25 | pyflakes==2.0.0 # via pytest-flakes 26 | pytest-cache==1.0 # via pytest-flakes, pytest-mccabe, pytest-pep8 27 | pytest-cov==2.5.1 28 | pytest-flakes==3.0.2 29 | pytest-mccabe==0.1 30 | pytest-pep8==1.0.6 31 | pytest-runner==4.2 32 | pytest==3.6.0 33 | python-dateutil==2.7.3 # via pandas 34 | pytz==2018.4 # via pandas 35 | six==1.11.0 # via more-itertools, pytest, python-dateutil 36 | typing==3.6.6 # via cfgrib 37 | xarray==0.11.0 38 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [aliases] 5 | test = pytest 6 | 7 | [tool:pytest] 8 | norecursedirs = 9 | build 10 | dist 11 | .tox 12 | .docker-tox 13 | .eggs 14 | tests/.ipynb_checkpoints 15 | #pep8maxlinelength = 99 16 | #mccabe-complexity = 10 17 | #pep8ignore = W503 E221 E241 E203 E272 18 | 19 | [coverage:run] 20 | branch = True 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2017- European Centre for Medium-Range Weather Forecasts (ECMWF). 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import io 18 | import os 19 | 20 | import setuptools 21 | 22 | 23 | def read(fname): 24 | file_path = os.path.join(os.path.dirname(__file__), fname) 25 | file_handle = io.open(file_path, encoding="utf-8") 26 | contents = file_handle.read() 27 | file_handle.close() 28 | return contents 29 | 30 | 31 | version = None 32 | for line in read("metview/bindings.py").split("\n"): 33 | if line.startswith("__version__"): 34 | version = line.split("=")[-1].strip()[1:-1] 35 | assert version 36 | 37 | 38 | setuptools.setup( 39 | name="metview", 40 | version=version, 41 | description="Metview Python API.", 42 | long_description=read("README.rst"), 43 | author="European Centre for Medium-Range Weather Forecasts (ECMWF)", 44 | author_email="software.support@ecmwf.int", 45 | license="Apache License Version 2.0", 46 | url="https://github.com/ecmwf/metview-python", 47 | packages=setuptools.find_packages(), 48 | include_package_data=True, 49 | setup_requires=[ 50 | "pytest-runner", 51 | ], 52 | install_requires=[ 53 | "cffi", 54 | "numpy", 55 | "pandas", 56 | "PyYAML", 57 | "requests", 58 | ], 59 | tests_require=[ 60 | "eccodes", 61 | "pytest", 62 | ], 63 | test_suite="tests", 64 | zip_safe=True, 65 | classifiers=[ 66 | "Development Status :: 5 - Production/Stable", 67 | "Intended Audience :: Developers", 68 | "License :: OSI Approved :: Apache Software License", 69 | "Programming Language :: Python :: 3", 70 | "Programming Language :: Python :: 3.4", 71 | "Programming Language :: Python :: 3.5", 72 | "Programming Language :: Python :: 3.6", 73 | "Programming Language :: Python :: 3.7", 74 | "Programming Language :: Python :: 3.8", 75 | "Programming Language :: Python :: Implementation :: CPython", 76 | "Programming Language :: Python :: Implementation :: PyPy", 77 | "Operating System :: OS Independent", 78 | ], 79 | ) 80 | -------------------------------------------------------------------------------- /tests/MVPROFILEVIEW.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/MVPROFILEVIEW.png -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/__init__.py -------------------------------------------------------------------------------- /tests/all_missing_vals.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/all_missing_vals.grib -------------------------------------------------------------------------------- /tests/args.py: -------------------------------------------------------------------------------- 1 | import metview as mv 2 | 3 | a = mv.arguments() 4 | print(a) 5 | -------------------------------------------------------------------------------- /tests/daily_clims.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/daily_clims.grib -------------------------------------------------------------------------------- /tests/ds/an/10u_sfc.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/an/10u_sfc.grib -------------------------------------------------------------------------------- /tests/ds/an/10v_sfc.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/an/10v_sfc.grib -------------------------------------------------------------------------------- /tests/ds/an/msl_sfc.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/an/msl_sfc.grib -------------------------------------------------------------------------------- /tests/ds/an/pv_pt.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/an/pv_pt.grib -------------------------------------------------------------------------------- /tests/ds/an/t_pl.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/an/t_pl.grib -------------------------------------------------------------------------------- /tests/ds/an/u_pl.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/an/u_pl.grib -------------------------------------------------------------------------------- /tests/ds/an/v_pl.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/an/v_pl.grib -------------------------------------------------------------------------------- /tests/ds/an/w_pl.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/an/w_pl.grib -------------------------------------------------------------------------------- /tests/ds/oper/10u_sfc.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/oper/10u_sfc.grib -------------------------------------------------------------------------------- /tests/ds/oper/10v_sfc.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/oper/10v_sfc.grib -------------------------------------------------------------------------------- /tests/ds/oper/msl_sfc.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/oper/msl_sfc.grib -------------------------------------------------------------------------------- /tests/ds/oper/pv_pt.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/oper/pv_pt.grib -------------------------------------------------------------------------------- /tests/ds/oper/t_pl.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/oper/t_pl.grib -------------------------------------------------------------------------------- /tests/ds/oper/tp_sfc.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/oper/tp_sfc.grib -------------------------------------------------------------------------------- /tests/ds/oper/u_pl.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/oper/u_pl.grib -------------------------------------------------------------------------------- /tests/ds/oper/v_pl.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/oper/v_pl.grib -------------------------------------------------------------------------------- /tests/ds/oper/w_pl.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ds/oper/w_pl.grib -------------------------------------------------------------------------------- /tests/geo_ncols_8.gpt: -------------------------------------------------------------------------------- 1 | #GEO 2 | #FORMAT NCOLS 3 | #COLUMNS 4 | latitude longitude time date t2 o3 td rh octa 5 | #DATA 6 | 32.55 35.85 0600 20120218 273.9 35 280.3 75 5 7 | 31.72 35.98 1800 20120218 274.9 24 290.4 68 1 8 | 51.93 8.32 1200 20140218 278.9 28 300.5 34 8 9 | 41.1 20.82 1200 20150218 279.9 83 310.6 42 6 10 | -------------------------------------------------------------------------------- /tests/geopointset_1.gpts: -------------------------------------------------------------------------------- 1 | #GEOPOINTSET 2 | #GEO 3 | # lat lon height date time value 4 | # Missing values represented by 3e+38 (not user-changeable) 5 | #DATA 6 | 69.6523 18.9057 0 20130512 0 100869.8625 7 | 63.4882 10.8795 0 20130512 0 100282.3392 8 | 63.5657 10.694 0 20130512 0 100241.1666 9 | 61.2928 5.0443 0 20130512 0 99852.18932 10 | 61.122 9.063 0 20130512 0 100502.3411 11 | 60.7002 10.8695 0 20130512 0 100757.699 12 | 60.7733 10.8055 0 20130512 0 100743.0044 13 | 61.455 10.1857 0 20130512 0 100591.8456 14 | 58.7605 5.6505 0 20130512 0 99978.0849 15 | 58.34 8.5225 0 20130512 0 100567.7705 16 | 59.6193 10.215 0 20130512 0 100870.8625 17 | #GEO 18 | # lat lon height date time value 19 | # Missing values represented by 3e+38 (not user-changeable) 20 | #METADATA 21 | mykey1=val1 22 | mykey2=5 23 | #DATA 24 | 60.82 23.5 0 20130512 600 101045.8 25 | #GEO 26 | # lat lon height date time value 27 | # Missing values represented by 3e+38 (not user-changeable) 28 | #DATA 29 | 55.01 8.41 0 20130513 0 100949.1809 30 | 54.33 8.6 0 20130513 0 101027.9101 31 | 53.71 7.15 0 20130513 0 100846.619 32 | 53.53 8.58 0 20130513 0 101070.8935 33 | 53.87 8.71 0 20130513 0 101071.725 34 | 53.39 7.23 0 20130513 0 100892.5829 35 | 53.06 7.9 0 20130513 0 101012.5006 36 | 53.05 8.8 0 20130513 0 101124.296 37 | 52.52 7.31 0 20130513 0 100999.5499 38 | 52.08 6.94 0 20130513 0 101015.0074 39 | 52.32 8.17 0 20130513 0 101102.8098 40 | 52.14 7.7 0 20130513 0 101075.1045 41 | 52.59 8.35 0 20130513 0 101100.667 42 | 52.11 8.75 0 20130513 0 101168.1776 43 | 51.3 6.77 0 20130513 0 101104.2186 44 | 51.41 6.97 0 20130513 0 101101.2162 45 | 51.25 7.64 0 20130513 0 101155.3776 46 | 51.58 7.89 0 20130513 0 101140.1571 47 | 51.18 8.49 0 20130513 0 101203.2271 48 | 50.8 6.03 0 20130513 0 101146.1206 49 | 50.36 6.87 0 20130513 0 101226.5559 50 | 50.87 7.16 0 20130513 0 101175.0056 51 | 50.74 7.19 0 20130513 0 101191.0374 52 | 50.42 7.42 0 20130513 0 101228.3253 53 | 50.66 7.96 0 20130513 0 101221.0611 54 | 50.6 8.64 0 20130513 0 101242.7862 55 | 49.75 6.66 0 20130513 0 101306.9112 56 | 49.76 7.06 0 20130513 0 101301.611 57 | 49.95 7.26 0 20130513 0 101278.3211 58 | 49.98 7.95 0 20130513 0 101274.389 59 | 50.22 8.45 0 20130513 0 101258.7422 60 | 50.05 8.6 0 20130513 0 101269.8424 61 | 50.09 8.79 0 20130513 0 101268.2491 62 | 49.27 6.69 0 20130513 0 101367.6812 63 | 49.47 7.04 0 20130513 0 101332.9676 64 | 49.21 7.11 0 20130513 0 101362.2254 65 | 49.38 8.12 0 20130513 0 101316.9526 66 | 49.51 8.55 0 20130513 0 101301.8117 67 | 48.97 8.33 0 20130513 0 101339.6649 68 | 48.02 7.84 0 20130513 0 101440.8873 69 | 48.37 7.83 0 20130513 0 101410.5892 70 | 48.45 8.41 0 20130513 0 101366.6555 71 | 48.11 8.76 0 20130513 0 101361.2035 72 | 47.88 8 0 20130513 0 101439.38 73 | #GEO 74 | # lat lon height date time value 75 | # Missing values represented by 3e+38 (not user-changeable) 76 | #DATA 77 | 69.6523 18.9057 0 20130513 1200 100356.475 78 | 63.4882 10.8795 0 20130513 1200 99514.9966 79 | 63.5657 10.694 0 20130513 1200 99475.0062 80 | 61.2928 5.0443 0 20130513 1200 99331.675 81 | 61.122 9.063 0 20130513 1200 99354.675 82 | 60.7002 10.8695 0 20130513 1200 99523.1486 83 | 60.7733 10.8055 0 20130513 1200 99509.4414 84 | 61.455 10.1857 0 20130513 1200 99388.30636 85 | 58.7605 5.6505 0 20130513 1200 99584.3246 86 | 58.34 8.5225 0 20130513 1200 99673.443 87 | 59.6193 10.215 0 20130513 1200 99453.12176 88 | #GEO 89 | # lat lon height date time value 90 | # Missing values represented by 3e+38 (not user-changeable) 91 | #DATA 92 | 60.82 23.5 0 20130514 0 99965.4125 93 | #GEO 94 | # lat lon height date time value 95 | # Missing values represented by 3e+38 (not user-changeable) 96 | #DATA 97 | 55.01 8.41 0 20130515 0 100184.7247 98 | 54.33 8.6 0 20130515 0 100223.6367 99 | 53.71 7.15 0 20130515 0 99933.67669 100 | 53.53 8.58 0 20130515 0 100219.5407 101 | 53.87 8.71 0 20130515 0 100246.1647 102 | 53.39 7.23 0 20130515 0 99945.06069 103 | 53.06 7.9 0 20130515 0 100079.2767 104 | 53.05 8.8 0 20130515 0 100260.5967 105 | 52.52 7.31 0 20130515 0 99961.44469 106 | 52.08 6.94 0 20130515 0 99882.66869 107 | 52.32 8.17 0 20130515 0 100136.5727 108 | 52.14 7.7 0 20130515 0 100040.3167 109 | 52.59 8.35 0 20130515 0 100171.4367 110 | 52.11 8.75 0 20130515 0 100254.3567 111 | 51.3 6.77 0 20130515 0 99848.85269 112 | 51.41 6.97 0 20130515 0 99889.81269 113 | 51.25 7.64 0 20130515 0 100027.0287 114 | 51.58 7.89 0 20130515 0 100079.2287 115 | 51.18 8.49 0 20130515 0 100201.1087 116 | 50.8 6.03 0 20130515 0 99697.30069 117 | 50.36 6.87 0 20130515 0 99862.33269 118 | 50.87 7.16 0 20130515 0 99928.72469 119 | 50.74 7.19 0 20130515 0 99934.86869 120 | 50.42 7.42 0 20130515 0 99974.97269 121 | 50.66 7.96 0 20130515 0 100092.5647 122 | 50.6 8.64 0 20130515 0 100231.8287 123 | 49.75 6.66 0 20130515 0 99819.32469 124 | 49.76 7.06 0 20130515 0 99901.24469 125 | 49.95 7.26 0 20130515 0 99942.20469 126 | 49.98 7.95 0 20130515 0 100083.5167 127 | 50.22 8.45 0 20130515 0 100185.9167 128 | 50.05 8.6 0 20130515 0 100216.6367 129 | 50.09 8.79 0 20130515 0 100255.5487 130 | 49.27 6.69 0 20130515 0 99817.46869 131 | 49.47 7.04 0 20130515 0 99889.14869 132 | 49.21 7.11 0 20130515 0 99903.48469 133 | 49.38 8.12 0 20130515 0 100111.3327 134 | 49.51 8.55 0 20130515 0 100206.3967 135 | 48.97 8.33 0 20130515 0 100154.3407 136 | 48.02 7.84 0 20130515 0 100046.9887 137 | 48.37 7.83 0 20130515 0 100044.9407 138 | 48.45 8.41 0 20130515 0 100163.7247 139 | 48.11 8.76 0 20130515 0 100234.4047 140 | 47.88 8 0 20130515 0 100079.7567 141 | -------------------------------------------------------------------------------- /tests/ml_data.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ml_data.grib -------------------------------------------------------------------------------- /tests/monthly_avg.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/monthly_avg.grib -------------------------------------------------------------------------------- /tests/obs_3day.bufr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/obs_3day.bufr -------------------------------------------------------------------------------- /tests/request.req: -------------------------------------------------------------------------------- 1 | GRIB, 2 | PATH = 't.grib' 3 | 4 | mcont, 5 | contour_line_colour = 'red', 6 | CONTOUR_HIGHLIGHT_FREQUENCY = 2, 7 | contour_level_selection_type = LEVEL_LIST, 8 | CONTOUR_LEVEL_LIST = -10/0/10, 9 | contour_shade_colour_list = 'RGB(0.5,0.2,0.8)'/'RGB(0.8,0.7,0.3)'/'RGB(0.4,0.8,0.3)' 10 | -------------------------------------------------------------------------------- /tests/rgg_small_subarea_cellarea_ref.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/rgg_small_subarea_cellarea_ref.grib -------------------------------------------------------------------------------- /tests/sample.csv: -------------------------------------------------------------------------------- 1 | h1,h2,h3,name 2 | 1,4,9,harry 3 | 2,5,8,george 4 | 3,6,7,charlie 5 | 4,7,6,grumbleweed 6 | 5,8,5,sirius 7 | 6,9,4,loopy 8 | -------------------------------------------------------------------------------- /tests/sample_metadata.csv: -------------------------------------------------------------------------------- 1 | type=FLEXTRA cfl=2.00 cflt=2.00 date=20111114 dx=1.00 dy=1.00 east=30.00 west=-10.00 integration=PETTERSSEN interpolation=LINEAR max_w_int=21600SECONDS model_comment=FLEXTRA north=75.00 south=30.00 time=90000 tr_type=3-DIMENSIONAL 2 | SECS LONGIT LATIT ETA PRESS Z Z-ORO PV THETA 3 | 0 -0.8300 51.5700 0.9518 963.5 500.0 405.7 1.450 282.8 4 | 3600 -1.1573 51.6643 0.9532 964.1 491.8 393.7 1.095 282.7 5 | 7200 -1.4730 51.7426 0.9540 964.0 489.7 387.3 0.781 282.7 6 | 10800 -1.7778 51.8056 0.9541 963.3 493.5 386.1 0.509 282.8 7 | 14400 -2.0913 51.8501 0.9548 961.8 502.9 380.4 0.468 282.9 8 | 18000 -2.4282 51.8955 0.9564 960.3 511.7 366.1 0.499 283.1 9 | 21600 -2.7885 51.9420 0.9590 959.0 519.3 343.5 0.596 283.3 10 | -------------------------------------------------------------------------------- /tests/small_odb.odb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/small_odb.odb -------------------------------------------------------------------------------- /tests/sort/date.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/date.csv.gz -------------------------------------------------------------------------------- /tests/sort/date_level.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/date_level.csv.gz -------------------------------------------------------------------------------- /tests/sort/default.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/default.csv.gz -------------------------------------------------------------------------------- /tests/sort/default_desc.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/default_desc.csv.gz -------------------------------------------------------------------------------- /tests/sort/keys.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/keys.csv.gz -------------------------------------------------------------------------------- /tests/sort/level.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/level.csv.gz -------------------------------------------------------------------------------- /tests/sort/level_asc.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/level_asc.csv.gz -------------------------------------------------------------------------------- /tests/sort/level_desc.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/level_desc.csv.gz -------------------------------------------------------------------------------- /tests/sort/level_units.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/level_units.csv.gz -------------------------------------------------------------------------------- /tests/sort/multi_asc.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/multi_asc.csv.gz -------------------------------------------------------------------------------- /tests/sort/multi_desc.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/multi_desc.csv.gz -------------------------------------------------------------------------------- /tests/sort/multi_mixed.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/multi_mixed.csv.gz -------------------------------------------------------------------------------- /tests/sort/number.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/number.csv.gz -------------------------------------------------------------------------------- /tests/sort/paramId.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/paramId.csv.gz -------------------------------------------------------------------------------- /tests/sort/shortName.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/shortName.csv.gz -------------------------------------------------------------------------------- /tests/sort/sort_data.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/sort_data.grib -------------------------------------------------------------------------------- /tests/sort/step.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/step.csv.gz -------------------------------------------------------------------------------- /tests/sort/time.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/time.csv.gz -------------------------------------------------------------------------------- /tests/sort/units.csv.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/sort/units.csv.gz -------------------------------------------------------------------------------- /tests/t1000_LL_2x2.grb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/t1000_LL_2x2.grb -------------------------------------------------------------------------------- /tests/t1000_LL_7x7.grb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/t1000_LL_7x7.grb -------------------------------------------------------------------------------- /tests/t2m_3day.gpt: -------------------------------------------------------------------------------- 1 | #GEO 2 | PARAMETER = 12004 3 | #lat long level date time value 4 | #DATA 5 | 49.43 -2.6 0 20170425 1200 282.4 6 | 52.12 0.96 0 20170425 1200 281.7 7 | 53.03 -0.5 0 20170425 1200 280.7 8 | 53.09 -0.17 0 20170425 1200 281 9 | 51.2 -1.81 0 20170425 1200 281.2 10 | 51.15 -1.57 0 20170425 1200 281.8 11 | 51.55 -0.42 0 20170425 1200 282.6 12 | 54.09 -4.63 0 20170425 1200 281.9 13 | 53.25 -4.54 0 20170425 1200 282.3 14 | 51.76 -1.58 0 20170425 1200 281.5 15 | 51.24 -0.94 0 20170425 1200 280.9 16 | 49.21 -2.19 0 20170425 1200 282.8 17 | 57.71 -3.32 0 20170425 1200 276.6 18 | 50.08 -5.26 0 20170425 1200 282.5 19 | 51.62 -1.1 0 20170425 1200 282.1 20 | 52.79 -2.66 0 20170425 1200 280.4 21 | 53.18 -0.52 0 20170425 1200 280.8 22 | 54.3 -1.53 0 20170425 1200 281.4 23 | 50.22 -5.33 0 20170425 1200 281.4 24 | 51.16 -1.75 0 20170425 1200 281.1 25 | 52.65 0.57 0 20170425 1200 281.2 26 | 54.05 -1.25 0 20170425 1200 281.5 27 | 51.01 -2.64 0 20170425 1200 282.2 28 | 55.31 -3.21 0 20170425 1200 279.1 29 | 60.14 -1.18 0 20170425 1200 277.1 30 | 51.09 2.65 0 20170425 1200 280.7 31 | 51.2 2.89 0 20170425 1200 280.3 32 | 50.14 1.83 0 20170425 1200 283.2 33 | 49.73 -1.94 0 20170425 1200 282.9 34 | 48.83 -3.47 0 20170425 1200 284.5 35 | 48.72 2.38 0 20170425 1200 281.6 36 | 48.47 -5.06 0 20170425 1200 283.3 37 | 48.68 -4.33 0 20170425 1200 283.3 38 | 48.77 2.01 0 20170425 1200 280.8 39 | 49.02 2.53 0 20170425 1200 282.2 40 | 50.73 1.6 0 20170425 1200 280.8 41 | 49.51 0.07 0 20170425 1200 281 42 | 56.54 3.22 0 20170425 1200 278.7 43 | 61.2 2.27 0 20170425 1200 278.8 44 | 51.85 -8.48 0 20170425 1200 283 45 | 52.7 -8.92 0 20170425 1200 282.6 46 | 53.3 -6.43 0 20170425 1200 282.5 47 | 53.43 -6.25 0 20170425 1200 282.1 48 | 53.9 -8.82 0 20170425 1200 279.1 49 | 59.53 -1.63 0 20170425 1200 276.5 50 | -------------------------------------------------------------------------------- /tests/t_for_xs.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/t_for_xs.grib -------------------------------------------------------------------------------- /tests/t_time_series.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/t_time_series.grib -------------------------------------------------------------------------------- /tests/t_with_missing.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/t_with_missing.grib -------------------------------------------------------------------------------- /tests/temp_u.odb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/temp_u.odb -------------------------------------------------------------------------------- /tests/test.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/test.grib -------------------------------------------------------------------------------- /tests/test_bindings.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | import os 11 | 12 | from metview import bindings 13 | 14 | 15 | PATH = os.path.dirname(__file__) 16 | MAX_VALUE = 316.06060791015625 17 | SEMI_EQUATOR = 20001600.0 18 | MAX_SQRT_GPT = 16.867127793433 19 | MAX_GPT = 284.5 20 | 21 | 22 | def file_in_testdir(filename): 23 | return os.path.join(PATH, filename) 24 | 25 | 26 | def test_push_number(): 27 | bindings.lib.p_push_number(5) 28 | bindings.lib.p_push_number(4) 29 | 30 | 31 | def test_dict_to_pushed_request(): 32 | dict = { 33 | "param1": True, 34 | "param2": False, 35 | "param3": 10, 36 | "param4": 10.5, 37 | "param5": "metview", 38 | "param6": ["1", "2", "3"], 39 | } 40 | bindings.dict_to_pushed_args(dict) 41 | 42 | 43 | def test_bind_functions(): 44 | namespace = {} 45 | 46 | bindings.bind_functions(namespace, module_name="metview") 47 | result = namespace["dictionary"] 48 | 49 | assert "dictionary" in namespace 50 | assert result.__name__ == result.__qualname__ == "dictionary" 51 | assert result.__module__ == "metview" 52 | 53 | 54 | def test_lists_as_input(): 55 | my_list = [1, 5, 6] 56 | assert bindings.count(my_list) == 3 57 | 58 | 59 | def test_tuples_as_input(): 60 | my_tuple = [1, 0, 5, 6] 61 | assert bindings.count(my_tuple) == 4 62 | -------------------------------------------------------------------------------- /tests/test_dataset.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 os 12 | import shutil 13 | import tempfile 14 | 15 | import metview as mv 16 | 17 | 18 | PATH = os.path.dirname(__file__) 19 | DS_DIR = "" 20 | 21 | 22 | def build_dataset(): 23 | # make conf file 24 | global DS_DIR 25 | DS_DIR = tempfile.mkdtemp() 26 | # DS_DIR = "/var/folders/ng/g0zkhc2s42xbslpsywwp_26m0000gn/T/tmpa_i2t_y6" 27 | # print(f"DS_DIR={DS_DIR}") 28 | data_dir = os.path.join(DS_DIR, "data") 29 | an_dir = os.path.join("__ROOTDIR__", "an") 30 | oper_dir = os.path.join("__ROOTDIR__", "oper") 31 | 32 | ds_def = rf""" 33 | experiments: 34 | - an: 35 | label: "an" 36 | dir: {an_dir} 37 | fname : re"[A-Za-z0-9]+_[A-z]+\.grib" 38 | - oper: 39 | label: "oper" 40 | dir: {oper_dir} 41 | fname : re"[A-Za-z0-9]+_[A-z]+\.grib" 42 | """ 43 | with open(os.path.join(DS_DIR, "data.yaml"), "w") as f: 44 | f.write(ds_def) 45 | 46 | shutil.copytree(os.path.join(PATH, "ds"), os.path.join(DS_DIR, "data")) 47 | conf_dir = os.path.join(DS_DIR, "conf") 48 | if not os.path.exists(conf_dir): 49 | os.mkdir(conf_dir) 50 | 51 | 52 | def remove_dataset(): 53 | global DS_DIR 54 | if DS_DIR and os.path.exists(DS_DIR) and not DS_DIR in ["/", "."]: 55 | shutil.rmtree(DS_DIR) 56 | DS_DIR = "" 57 | 58 | 59 | def test_dataset(): 60 | build_dataset() 61 | 62 | ds = mv.load_dataset(DS_DIR) 63 | assert list(ds.field_conf.keys()) == ["an", "oper"] 64 | assert ds.name == os.path.basename(DS_DIR) 65 | 66 | # indexing 67 | ds.scan() 68 | index_dir = os.path.join(DS_DIR, "index") 69 | assert os.path.exists(index_dir) 70 | for comp in ["an", "oper"]: 71 | for f in [ 72 | "datafiles.yaml", 73 | "scalar.csv.gz", 74 | "wind10m.csv.gz", 75 | "wind.csv.gz", 76 | "wind3d.csv.gz", 77 | ]: 78 | assert os.path.exists(os.path.join(index_dir, comp, f)) 79 | 80 | # reload ds 81 | ds = mv.load_dataset(DS_DIR) 82 | 83 | # Analysis 84 | d = ds["an"].select( 85 | dateTime=[mv.date("2016-09-25 00:00"), mv.date("2016-09-26 00:00")] 86 | ) 87 | 88 | assert isinstance(d, mv.Fieldset) 89 | assert set(ds["an"].blocks.keys()) == set(["scalar", "wind10m", "wind", "wind3d"]) 90 | assert set(d._db.blocks.keys()) == set(["scalar", "wind10m", "wind", "wind3d"]) 91 | 92 | v = d["msl"] 93 | assert isinstance(v, mv.Fieldset) 94 | assert len(v) == 2 95 | assert mv.grib_get(v, ["shortName", "date:l", "time:l"]) == [ 96 | ["msl", 20160925, 0], 97 | ["msl", 20160926, 0], 98 | ] 99 | 100 | v = d["t500"] 101 | assert isinstance(v, mv.Fieldset) 102 | assert len(v) == 2 103 | assert mv.grib_get(v, ["shortName", "date:l", "time:l", "level:l"]) == [ 104 | ["t", 20160925, 0, 500], 105 | ["t", 20160926, 0, 500], 106 | ] 107 | 108 | v = d["pv320K"] 109 | assert isinstance(v, mv.Fieldset) 110 | assert len(v) == 2 111 | assert mv.grib_get( 112 | v, ["shortName", "date:l", "time:l", "level:l", "typeOfLevel"] 113 | ) == [["pv", 20160925, 0, 320, "theta"], ["pv", 20160926, 0, 320, "theta"]] 114 | 115 | v = d["wind850"] 116 | assert isinstance(v, mv.Fieldset) 117 | assert len(v) == 4 118 | assert mv.grib_get( 119 | v, ["shortName", "date:l", "time:l", "level:l", "typeOfLevel"] 120 | ) == [ 121 | ["u", 20160925, 0, 850, "isobaricInhPa"], 122 | ["v", 20160925, 0, 850, "isobaricInhPa"], 123 | ["u", 20160926, 0, 850, "isobaricInhPa"], 124 | ["v", 20160926, 0, 850, "isobaricInhPa"], 125 | ] 126 | 127 | v = d["wind10m"] 128 | assert isinstance(v, mv.Fieldset) 129 | assert len(v) == 4 130 | assert mv.grib_get(v, ["shortName", "date:l", "time:l", "typeOfLevel"]) == [ 131 | ["10u", 20160925, 0, "surface"], 132 | ["10v", 20160925, 0, "surface"], 133 | ["10u", 20160926, 0, "surface"], 134 | ["10v", 20160926, 0, "surface"], 135 | ] 136 | 137 | v = d["wind"] 138 | assert isinstance(v, mv.Fieldset) 139 | assert len(v) == 8 140 | assert mv.grib_get( 141 | v, ["shortName", "date:l", "time:l", "level:l", "typeOfLevel"] 142 | ) == [ 143 | ["u", 20160925, 0, 500, "isobaricInhPa"], 144 | ["v", 20160925, 0, 500, "isobaricInhPa"], 145 | ["u", 20160925, 0, 850, "isobaricInhPa"], 146 | ["v", 20160925, 0, 850, "isobaricInhPa"], 147 | ["u", 20160926, 0, 500, "isobaricInhPa"], 148 | ["v", 20160926, 0, 500, "isobaricInhPa"], 149 | ["u", 20160926, 0, 850, "isobaricInhPa"], 150 | ["v", 20160926, 0, 850, "isobaricInhPa"], 151 | ] 152 | 153 | v = d["wind3d"] 154 | assert isinstance(v, mv.Fieldset) 155 | assert len(v) == 12 156 | assert mv.grib_get( 157 | v, ["shortName", "date:l", "time:l", "level:l", "typeOfLevel"] 158 | ) == [ 159 | ["u", 20160925, 0, 500, "isobaricInhPa"], 160 | ["v", 20160925, 0, 500, "isobaricInhPa"], 161 | ["w", 20160925, 0, 500, "isobaricInhPa"], 162 | ["u", 20160925, 0, 850, "isobaricInhPa"], 163 | ["v", 20160925, 0, 850, "isobaricInhPa"], 164 | ["w", 20160925, 0, 850, "isobaricInhPa"], 165 | ["u", 20160926, 0, 500, "isobaricInhPa"], 166 | ["v", 20160926, 0, 500, "isobaricInhPa"], 167 | ["w", 20160926, 0, 500, "isobaricInhPa"], 168 | ["u", 20160926, 0, 850, "isobaricInhPa"], 169 | ["v", 20160926, 0, 850, "isobaricInhPa"], 170 | ["w", 20160926, 0, 850, "isobaricInhPa"], 171 | ] 172 | 173 | # Oper 174 | run = mv.date("2016-09-25 00:00") 175 | d = ds["oper"].select(date=run.date(), time=run.time(), step=[120]) 176 | 177 | assert isinstance(d, mv.Fieldset) 178 | assert set(ds["oper"].blocks.keys()) == set(["scalar", "wind10m", "wind", "wind3d"]) 179 | assert set(d._db.blocks.keys()) == set(["scalar", "wind10m", "wind", "wind3d"]) 180 | 181 | v = d["msl"] 182 | assert isinstance(v, mv.Fieldset) 183 | assert len(v) == 1 184 | assert mv.grib_get(v, ["shortName", "date:l", "time:l", "step:l"]) == [ 185 | ["msl", 20160925, 0, 120] 186 | ] 187 | 188 | remove_dataset() 189 | 190 | 191 | def test_dataset_create_template(): 192 | 193 | global DS_DIR 194 | DS_DIR = tempfile.mkdtemp() 195 | 196 | mv.dataset.create_dataset_template(DS_DIR) 197 | 198 | for d in ["conf", "data", "index"]: 199 | d_path = os.path.join(DS_DIR, d) 200 | assert os.path.isdir(d_path) 201 | 202 | for f in ["params.yaml", "param_styles.yaml", "areas.yaml", "map_styles"]: 203 | f_path = os.path.join(DS_DIR, "conf", f) 204 | assert os.path.exists(d_path) 205 | 206 | f_path = os.path.join(DS_DIR, "data.yaml") 207 | assert os.path.exists(f_path) 208 | 209 | remove_dataset() 210 | -------------------------------------------------------------------------------- /tests/test_style.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 | 12 | import metview as mv 13 | from metview import bindings 14 | from metview.style import Style, Visdef 15 | 16 | 17 | def test_visdef_object(): 18 | vd = Visdef( 19 | "msymb", 20 | { 21 | "symbol_type": "text", 22 | "symbol_table_mode": "advanced", 23 | "symbol_advanced_table_text_font_colour": "black", 24 | }, 25 | ) 26 | 27 | # clone 28 | v = vd.clone() 29 | assert v.verb == vd.verb 30 | assert v.params == vd.params 31 | assert id(v) != id(vd) 32 | assert id(v.params) != id(vd.params) 33 | 34 | # change 35 | vd.change("msymb", "symbol_advanced_table_text_font_colour", "red") 36 | assert vd.params["symbol_advanced_table_text_font_colour"] == "red" 37 | vd.change("mcont", "symbol_advanced_table_text_font_colour", "blue") 38 | assert vd.params["symbol_advanced_table_text_font_colour"] == "red" 39 | 40 | # from request 41 | r = mv.msymb( 42 | { 43 | "symbol_type": "text", 44 | "symbol_table_mode": "advanced", 45 | "symbol_advanced_table_text_font_colour": "black", 46 | } 47 | ) 48 | 49 | v = Visdef.from_request(r) 50 | assert v.verb == vd.verb 51 | for k, v in v.params.items(): 52 | assert r[k.upper()].lower() == v.lower() 53 | 54 | 55 | def test_style_object(): 56 | vd = Visdef( 57 | "msymb", 58 | { 59 | "symbol_type": "text", 60 | "symbol_table_mode": "advanced", 61 | "symbol_advanced_table_text_font_colour": "black", 62 | }, 63 | ) 64 | 65 | # create 66 | s = Style("super", vd) 67 | assert s.name == "super" 68 | assert len(s.visdefs) == 1 69 | assert s.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "black" 70 | 71 | # update 72 | ns = s.update({"symbol_advanced_table_text_font_colour": "red"}) 73 | assert id(ns) != id(s) 74 | assert s.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "black" 75 | assert ns.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "red" 76 | 77 | ns = s.update({"symbol_advanced_table_text_font_colour": "red"}, verb="mcont") 78 | assert id(ns) != id(s) 79 | assert s.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "black" 80 | assert ns.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "black" 81 | 82 | ns = s.update({"symbol_advanced_table_text_font_colour": "red"}, verb="msymb") 83 | assert id(ns) != id(s) 84 | assert s.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "black" 85 | assert ns.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "red" 86 | 87 | # multiple visdefs 88 | 89 | vd1 = Visdef( 90 | "msymb", 91 | { 92 | "symbol_type": "text", 93 | "symbol_table_mode": "advanced", 94 | "symbol_advanced_table_text_font_colour": "black", 95 | }, 96 | ) 97 | vd2 = Visdef("mgraph", {"graph_line_colour": "blue", "graph_line_thickness": 4}) 98 | 99 | s = Style("super", [vd1, vd2]) 100 | assert s.name == "super" 101 | assert len(s.visdefs) == 2 102 | assert s.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "black" 103 | assert s.visdefs[1].params["graph_line_colour"] == "blue" 104 | 105 | # update 106 | ns = s.update({"symbol_advanced_table_text_font_colour": "red"}, verb="msymb") 107 | assert id(ns) != id(s) 108 | assert s.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "black" 109 | assert ns.visdefs[0].params["symbol_advanced_table_text_font_colour"] == "red" 110 | 111 | ns = s.update( 112 | { 113 | "graph_line_colour": "yellow", 114 | }, 115 | verb="mgraph", 116 | ) 117 | assert id(ns) != id(s) 118 | assert s.visdefs[1].params["graph_line_colour"] == "blue" 119 | assert ns.visdefs[1].params["graph_line_colour"] == "yellow" 120 | 121 | 122 | def test_style_set_grib_id(): 123 | # mcont 124 | vd = Visdef( 125 | "mcont", 126 | { 127 | "legend": "on", 128 | }, 129 | ) 130 | 131 | s = Style("super", vd) 132 | ns = s.set_data_id("11") 133 | assert id(s) != id(ns) 134 | assert ns.visdefs[0].verb == "mcont" 135 | assert ns.visdefs[0].params["grib_id"] == "11" 136 | assert s.visdefs[0].params.get("grib_id", None) is None 137 | 138 | # mwind 139 | vd = Visdef( 140 | "mwind", 141 | { 142 | "legend": "on", 143 | }, 144 | ) 145 | 146 | s = Style("super", vd) 147 | ns = s.set_data_id("12") 148 | assert id(s) != id(ns) 149 | assert ns.visdefs[0].verb == "mwind" 150 | assert ns.visdefs[0].params["grib_id"] == "12" 151 | assert s.visdefs[0].params.get("grib_id", None) is None 152 | 153 | # other 154 | vd = Visdef( 155 | "msymb", 156 | { 157 | "legend": "on", 158 | }, 159 | ) 160 | 161 | s = Style("super", vd) 162 | ns = s.set_data_id("10") 163 | assert id(s) == id(ns) 164 | assert s.visdefs[0].params.get("grib_id", None) is None 165 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # (C) Copyright 2017- ECMWF. 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 datetime 12 | 13 | from metview.metviewpy import utils 14 | 15 | 16 | def test_utils_date(): 17 | 18 | d = utils.date_from_str("20210204") 19 | assert d == datetime.datetime(2021, 2, 4) 20 | 21 | d = utils.date_from_str("2021-02-04") 22 | assert d == datetime.datetime(2021, 2, 4) 23 | 24 | d = utils.date_from_str("2021-02-04 06") 25 | assert d == datetime.datetime(2021, 2, 4, 6, 0, 0) 26 | 27 | d = utils.date_from_str("2021-02-04 06:14") 28 | assert d == datetime.datetime(2021, 2, 4, 6, 14, 0) 29 | 30 | d = utils.date_from_str("2021-02-04 06:14:32") 31 | assert d == datetime.datetime(2021, 2, 4, 6, 14, 32) 32 | 33 | d = utils.date_from_str("20210204.25") 34 | assert d == datetime.datetime(2021, 2, 4, 6, 0, 0) 35 | 36 | d = utils.date_from_str("402") 37 | assert d == (4, 2) 38 | 39 | d = utils.date_from_str("0402") 40 | assert d == (4, 2) 41 | 42 | try: 43 | d = utils.date_from_str("0432") 44 | assert False 45 | except: 46 | pass 47 | 48 | d = utils.date_from_str("apr-02") 49 | assert d == (4, 2) 50 | 51 | d = utils.date_from_str("Apr-02") 52 | assert d == (4, 2) 53 | 54 | try: 55 | d = utils.date_from_str("afr-02") 56 | assert False 57 | except: 58 | pass 59 | 60 | 61 | def test_utils_time(): 62 | 63 | d = utils.time_from_str("6") 64 | assert d == datetime.time(6, 0, 0) 65 | 66 | d = utils.time_from_str("06") 67 | assert d == datetime.time(6, 0, 0) 68 | 69 | d = utils.time_from_str("14") 70 | assert d == datetime.time(14, 0, 0) 71 | 72 | d = utils.time_from_str("600") 73 | assert d == datetime.time(6, 0, 0) 74 | 75 | d = utils.time_from_str("612") 76 | assert d == datetime.time(6, 12, 0) 77 | 78 | d = utils.time_from_str("1100") 79 | assert d == datetime.time(11, 0, 0) 80 | 81 | d = utils.time_from_str("1457") 82 | assert d == datetime.time(14, 57, 0) 83 | 84 | d = utils.time_from_str("6:12") 85 | assert d == datetime.time(6, 12, 0) 86 | 87 | d = utils.time_from_str("06:12") 88 | assert d == datetime.time(6, 12, 0) 89 | 90 | d = utils.time_from_str("14:57") 91 | assert d == datetime.time(14, 57, 0) 92 | 93 | 94 | def test_has_globbing(): 95 | 96 | assert utils.has_globbing("a*") == True 97 | assert utils.has_globbing("a?") == True 98 | assert utils.has_globbing("my_path/a*.grib") == True 99 | assert utils.has_globbing("my_path/[Aa].grib") == True 100 | assert utils.has_globbing("my_path/[a-m].grib") == True 101 | assert utils.has_globbing("my_path/test.grib") == False 102 | assert utils.has_globbing("my_path/te[st.grib") == False 103 | assert utils.has_globbing("my_path/tes]t.grib") == False 104 | # assert utils.has_globbing("my_path/t]e[st.grib") == False 105 | -------------------------------------------------------------------------------- /tests/tuv_pl.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/tuv_pl.grib -------------------------------------------------------------------------------- /tests/uv.gpt: -------------------------------------------------------------------------------- 1 | #GEO 2 | #FORMAT XY_VECTOR 3 | # lat lon height date time u v 4 | # Missing values represented by 3e+38 (not user-changeable) 5 | #DATA 6 | 70 -30 500 20180514 1200 -7.288507462 3.663342476 7 | 70 -25 500 20180514 1200 -9.912530899 -0.06614971161 8 | 70 -20 500 20180514 1200 -10.82464027 -3.044665337 9 | 70 -15 500 20180514 1200 -11.08049965 3.341076851 10 | 70 -10 500 20180514 1200 -10.8656559 17.58912373 11 | 70 -5 500 20180514 1200 -8.620538712 27.3293581 12 | 70 0 500 20180514 1200 -4.039484024 26.13453388 13 | 70 5 500 20180514 1200 0.866765976 19.76539326 14 | 70 10 500 20180514 1200 3.803289413 12.84888935 15 | 70 15 500 20180514 1200 3.626531601 4.424572945 16 | 70 20 500 20180514 1200 1.357000351 -2.718981743 17 | 70 25 500 20180514 1200 0.132390976 -4.273669243 18 | 70 30 500 20180514 1200 3.050359726 -3.629626274 19 | 65 -30 500 20180514 1200 -11.85100746 6.419690132 20 | 65 -25 500 20180514 1200 -5.773859024 9.360608101 21 | 65 -20 500 20180514 1200 4.273015976 16.69459248 22 | 65 -15 500 20180514 1200 15.77399254 18.51295185 23 | 65 -10 500 20180514 1200 21.24274254 13.67066669 24 | 65 -5 500 20180514 1200 17.69196129 11.58228779 25 | 65 0 500 20180514 1200 11.28571129 14.20777607 26 | 65 5 500 20180514 1200 7.269109726 17.74927998 27 | 65 10 500 20180514 1200 6.036687851 15.79566669 28 | 65 15 500 20180514 1200 6.829656601 5.564221382 29 | 65 20 500 20180514 1200 9.539617538 -0.9421262741 30 | 65 25 500 20180514 1200 12.9937191 -0.9699583054 31 | 65 30 500 20180514 1200 13.19293785 -2.011462212 32 | 60 -30 500 20180514 1200 9.377508163 13.03345966 33 | 60 -25 500 20180514 1200 14.9077816 24.24488544 34 | 60 -20 500 20180514 1200 17.94293785 22.37427998 35 | 60 -15 500 20180514 1200 15.46637535 16.33863544 36 | 60 -10 500 20180514 1200 14.17828941 9.505139351 37 | 60 -5 500 20180514 1200 10.58356285 6.734143257 38 | 60 0 500 20180514 1200 7.986883163 10.21851826 39 | 60 5 500 20180514 1200 5.930242538 9.086194038 40 | 60 10 500 20180514 1200 2.287664413 8.728772163 41 | 60 15 500 20180514 1200 -0.2513980865 5.30543232 42 | 60 20 500 20180514 1200 -2.472101212 1.494397163 43 | 60 25 500 20180514 1200 -2.373468399 1.931408882 44 | 60 30 500 20180514 1200 1.873601913 -0.7023801804 45 | -------------------------------------------------------------------------------- /tests/xs_date_mv5.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/xs_date_mv5.nc -------------------------------------------------------------------------------- /tests/xyv.gpt: -------------------------------------------------------------------------------- 1 | #GEO 2 | # extracted from GRIB data, on 20180515, by grib_to_geo 3 | # original parameter 131.128 4 | #FORMAT XYV 5 | # warning: XYV is not suitable for some macro computations! 6 | # X=lon Y=lat V=val 7 | #DATA 8 | -30 70 -7.2885 9 | -25 70 -9.91252 10 | -20 70 -10.8246 11 | -15 70 -11.0805 12 | -10 70 -10.8656 13 | -5 70 -8.62053 14 | 0 70 -4.03947 15 | 5 70 0.866776 16 | 10 70 3.8033 17 | 15 70 3.62654 18 | 20 70 1.35701 19 | 25 70 0.132401 20 | 30 70 3.05037 21 | 35 70 10.9674 22 | 40 70 21.6246 23 | -30 65 -11.851 24 | -25 65 -5.77385 25 | -20 65 4.27303 26 | -15 65 15.774 27 | -10 65 21.2428 28 | -5 65 17.692 29 | 0 65 11.2857 30 | 5 65 7.26912 31 | 10 65 6.0367 32 | 15 65 6.82967 33 | 20 65 9.53963 34 | 25 65 12.9937 35 | 30 65 13.1929 36 | 35 65 12.3326 37 | 40 65 14.0601 38 | -30 60 9.37752 39 | -25 60 14.9078 40 | -20 60 17.9429 41 | -15 60 15.4664 42 | -10 60 14.1783 43 | -5 60 10.5836 44 | 0 60 7.98689 45 | 5 60 5.93025 46 | 10 60 2.28767 47 | 15 60 -0.251389 48 | 20 60 -2.47209 49 | 25 60 -2.37346 50 | 30 60 1.87361 51 | 35 60 4.06111 52 | 40 60 1.84627 53 | -30 55 9.9576 54 | -25 55 16.4556 55 | -20 55 21.8521 56 | -15 55 20.192 57 | -10 55 10.7633 58 | -5 55 4.6031 59 | 0 55 4.02205 60 | 5 55 -3.17131 61 | 10 55 -8.42229 62 | 15 55 -15.2211 63 | 20 55 -20.7992 64 | 25 55 -20.4613 65 | 30 55 -16.9721 66 | 35 55 -9.77678 67 | 40 55 -1.64201 68 | -------------------------------------------------------------------------------- /tests/zt_multi_expvers.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/zt_multi_expvers.grib -------------------------------------------------------------------------------- /tests/ztu_multi_dim.grib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecmwf/metview-python/eea2973847ca25904ae7403880cdcf2744ff150d/tests/ztu_multi_dim.grib -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = qc, docs, py36, py35, py34, pypy3 3 | 4 | [testenv] 5 | passenv = WHEELHOUSE PIP_FIND_LINKS PIP_WHEEL_DIR PIP_INDEX_URL 6 | setenv = PYTHONPATH = {toxinidir} 7 | deps = -r{toxinidir}/requirements/requirements-tests.txt 8 | commands = pytest -v --flakes --cache-clear --basetemp={envtmpdir} {posargs} 9 | 10 | [testenv:docs] 11 | deps = -r{toxinidir}/requirements/requirements-docs.txt 12 | commands = sphinx-build -W -b html docs build/sphinx/html 13 | 14 | [testenv:qc] 15 | basepython = python3 16 | # needed for pytest-cov 17 | usedevelop = true 18 | commands = pytest -v --pep8 --mccabe --cov=metview --cov-report=html --cache-clear --basetemp={envtmpdir} metview tests 19 | --------------------------------------------------------------------------------